How to Rails
Create the app
$ rails new imageshare
$ cd imageshare
Adding gems
Add the following to your Gemfile
gem 'bootstrap-sass'
gem 'bcrypt', '~> 3.1.7'
gem 'devise'
gem 'paperclip'
gem 'pg'
Creating databases
Edit your config/database.yml file to look like the following
# SQLite version 3.x
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem 'sqlite3'
#
default: &default
adapter: postgresql
pool: 5
timeout: 5000
development:
<<: *default
database: imageshare_dev
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: imageshare_test
production:
<<: *default
database: imageshare_prod
Then run the following commands
$ bundle install
$ rake db:create
Creating the User model
We're not gonna bother with Devise for this, since it's too restrictive for our purposes
$ rails g model User first_name:string last_name:string email:string username:string password_digest:string dob:date
$ rake db:migrate
Creating sessions
Navigate to /app/controllers/application_controller.rb and edit it to look like the following
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def authorize
redirect_to login_path unless current_user
end
end
Then create a sessions controller
$ rails g controller sessions new create destroy
In your new sessions controller (located at /app/controllers/sessions_controller.rb), edit it to look like the following
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_username(params[:username])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_path
else
redirect_to login_path
end
end
def destroy
session[:user_id] = nil
redirect_to login_path
end
end
To log in a user, you have to create a user session. What the code for the create method does is find a user with the username, uses a helper method built into the User model through bcrypt's has_secure_password (which we'll get to later), then if the user exists and is successfully authenticated, the user will have a session, and the app will redirect to its root path, which we'll also get to later.
After that, create a users controller.
$ rails g controller users new create destroy
In your new users controller (located at /app/controllers/users_controller.rb), edit it to look like the following.
class UsersController < ApplicationController
def index
@users = User.all
end
def new
end
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to root_path
else
redirect_to signup_path
end
end
private
def user_params
params.require(:user).permit(
:username,
:first_name,
:last_name,
:email,
:dob,
:password,
:password_confirmation
)
end
end
The index method indicates necessary code for when the /app/views/users/index.html.erb page is shown. After that page is implemented, when the user navigates to localhost:3000/users, the user will be able to see a list of users, because they have access to a variable that displays all users. For the new method, that essentially just takes from create for the purpose of this controller. For the create method, a new user is created from the given permitted parameters in the user_params method. If the user is saved, the user will automatically be logged in and redirected to the root path. Otherwise they'll just be redirected to signup. Finally, the user_params method is just a method that allows for only certain fields to be passed in through a form so hackers don't try to inject SQL or something.
Next, navigate to /app/models/user.rb and add the following line inside the User class
has_secure_password
This comes from the bcrypt gem we added to our gemfile. This stores a password digest, an encrypted version of the password, in the database, so even if a hacker gets access to your database, they'd still have to decrypt the passwords to make any sense of it. This also adds the authenticate helper method to verify a user with a password.
Now we want to make a view such that users can actually sign up for this app. Navigate to /app/views/users/new.html.erb and edit it to look like the following
<%= form_for :user, url: '/users' do |f| %>
First Name: <%= f.text_field :first_name %><br />
Last Name: <%= f.text_field :last_name %><br />
Username <%= f.text_field :username %><br />
Email: <%= f.text_field :email %><br />
Date of Birth: <%= f.date_select :dob, :start_year => Date.today.year - 100, :end_year => Date.today.year %><br />
Password: <%= f.password_field :password %><br />
Password Confirmation: <%= f.password_field :password_confirmation %><br />
<%= f.submit "Sign Up" %>
<% end %>
This creates a form, allows for users to add in each of the required fields to create a user, and submits it. Upon submission, it will call the create method from the users controller and go through that code to create the user.
Now that a user can sign up, we want a way for them to log in as well. So we're gonna navigate to /app/views/sessions/new.html.erb and create a login view.
<h1>Login</h1>
<%= form_tag '/login' do %>
Email: <%= text_field_tag :email %>
Password: <%= password_field_tag :password %>
<%= submit_tag "Submit" %>
<% end %>
This routes the submission to the login path, which will be defined in config/routes.rb as sessions#create, which will call the create method in the sessions controller and create a user session.
Now, just so that users have somewhere to navigate to and so that we have an adequate root page, Finally, you need to add your new routes such that everything will point to the right thing. Navigate to /config/routes.rb and edit it to look like the following.
Rails.application.routes.draw do
get '/dashboard' => 'pages#dashboard'
get '/tutorial' => 'pages#tutorial'
root 'pages#dashboard'
get '/login' => 'sessions#new'
post '/login' => 'sessions#create'
get '/logout' => 'sessions#destroy'
get '/signup' => 'users#new'
post '/users' => 'users#create'
end
This essentially says "when the user navigates to /dashboard, it will link to the Rails route described by the command 'rake routes' as 'pages#dashboard'. It essentially renames the routes to those pages to something more user-friendly." Speaking of user-friendly, open up /app/views/layouts/application.html.erb and add the following in the tags.
<% if current_user %>
Signed in as <%= current_user.username %> | <%= link_to "Logout", logout_path %>
<% else %>
<%= link_to 'Login', login_path %> | <%= link_to 'Signup', signup_path %>
<% end %>
Therefore, on every page, you will be able to login and logout. Stuff in application.html.erb will be visible and accessible everywhere in the web app. Now we need to one last thing. Go back to your terminal and type the following.
$ rails g controller pages dashboard tutorial
And now you have all the pages you need! Just run 'rails s' and you'll have your user model and authentication working!
Adding a user avatar
This is an image sharing website, right? Wouldn't it only be right for a user to have a profile picture? Let's make that now.
$ rails g migration AddAvatarToUsers
For this one, we're gonna manually write the migration for this. Navigate to /db/migrate/_add_avatar_to_users.rb, and then edit it to look like the following.
class AddAvatarToUsers < ActiveRecord::Migration[5.0]
def self.up
change_table :users do |t|
t.attachment :avatar
end
end
def self.down
drop_attached_file :users, :avatar
end
end
Self.up tells Rails what to do when the attachment is created. It adds a field of type attachment called avatar to the users table. Conversly, self.down tells Rails what to do when the attachment is destroyed, it just drops the field from the table. Now that you've changed the database schema, you need to migrate your database.
$ rake db:migrate
Next, we need to update the model to accept and validate an attachment, as the Paperclip gem requires. Navigate back to /app/models/user.rb, and beneath 'has_secure_password', add the following lines.
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100#" }, :default_url => "/images/404.jpg"
validates_attachment_content_type :avatar, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
Now that the model has been updated, we need to update the controller and the view to allow for a user to upload an avatar. So remember the users_controller.rb file we edited earlier? We need to add another field to the user_params method.
Navigate to /app/controllers/users_controller.rb and edit the user_params method such that it looks like this.
def user_params
params.require(:user).permit(
:username,
:first_name,
:last_name,
:email,
:dob,
:password,
:password_confirmation,
:avatar
)
end
The avatar field needs to be allowed by the users controller within the permitted parameters. Finally, you need to add an avatar field to the view so a user can actually interact with that field. We're gonna go back into /app/views/users/new.html and plop in a file field.
<%= form_for :user, url: '/users' do |f| %>
First Name: <%= f.text_field :first_name %><br />
Last Name: <%= f.text_field :last_name %><br />
Username <%= f.text_field :username %><br />
Avatar: <%= f.file_field :avatar, :accept => 'image/png,image/gif,image/jpeg' %><br />
Email: <%= f.text_field :email %><br />
Date of Birth: <%= f.date_select :dob, :start_year => Date.today.year - 100, :end_year => Date.today.year %><br />
Password: <%= f.password_field :password %><br />
Password Confirmation: <%= f.password_field :password_confirmation %><br />
<%= f.submit "Sign Up" %>
<% end %>
Now your user signup and authentication should be ready to go!
Set up image uploads
It's back to the console to generate the model.
$ rails g model Image img:attachment title:string caption:string user_id:integer
$ rake db:migrate
Now that you have an Image table in the database, you can fix up the models such that you can access an image's user through image.user and a user's images through user.images! Navigate to /app/models/user.rb and append the following line within the User class.
has_many :images
Next, navigate to /app/models/image.rb and add the following lines within the Image class
belongs_to :user
has_attached_file :img, :styles => { :medium => "300x300>", :thumb => "100x100#" }, :default_url => "/images/404.jpg"
validates_attachment_content_type :img, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
This creates the relationship between Image and User, and validates the image, as is necessary with the Paperclip gem. After that, create a new file as /app/controllers/images_controller.rb and edit it to look like the following.
class ImagesController < ApplicationController
before_action :authorize
def new
@image = Image.new
end
def show
@image = Image.find(params[:id])
end
def create
@image = Image.new image_params
@image.user_id = current_user.id
if @image.save
redirect_to root_path
else
redirect_to new_image_path
end
end
def destroy
@image = Image.find(params[:id])
if image.destroy
redirect_to root_path
else
redirect_to image_path(image.id)
end
end
protected
def image_params
params.require(:image).permit(
:title,
:img,
:caption,
:user_id
)
end
end
Next, add the following line to /config/routes.rb.
resources :images
What this does is generate the necessary routes you specified in the controller once you run the rake routes command.
To make use of this model and controller, we need views to create the items. Create a new file as /app/views/images/new.html.erb and edit the form to look like so.
<h1>Upload an image</h1>
<%= form_for :image, url: "/images" do |f| %>
Title <%= f.text_field :title %><br />
Image: <%= f.file_field :img, :accept => 'image/png,image/gif,image/jpeg' %><br />
Caption: <%= f.text_field :caption %><br />
<%= f.submit "Upload" %>
<% end %>
In the convention of resources :images, the "/images" URL is routed to posting a new image and run the create method from the images controller. Next, we wanna be able to show some information about an image when we link to it. Create a new file /app/views/images/show.html.erb.
<h1><%= @image.title %></h1>
<p>Uploaded by: <%= link_to @image.user.username, users_path(@image.user) %></p>
<p><%= image_tag(@image.img) %>
<p><%= @image.caption %></p>
This will pull in the @image instance variable we created in the show method in the images controller and gather its information to display.
Finally, we want to fix the dashboard so that it will show a user's images and display a button that will allow you to navigate to the image upload page. Add the following lines to /app/views/pages/dashboard.html.erb
<h2><%= current_user.first_name %>'s images</h2>
<%= button_to "Upload an image", new_image_path, method: :get %>
<% current_user.images.each do |image| %>
<p><%= link_to image_tag(image.img.url(:thumb)), image_path(image) %></p>
<% end %>
And there you have it, you have a working image upload functionality!
Log in or sign up for Devpost to join the conversation.