-
Stwórz nowy prohjek
cd ˜/workspace rails _5.0.0_ new sample_app cd sample_app/
rails server
-
Modify Gemfile
source 'https://rubygems.org' gem 'rails', '5.1.4' gem 'puma', '3.9.1' gem 'sass-rails', '5.0.6' gem 'uglifier', '3.2.0' gem 'coffee-rails', '4.2.2' gem 'jquery-rails', '4.3.1' gem 'turbolinks', '5.0.1' gem 'jbuilder', '2.7.0' group :development, :test do gem 'sqlite3', '1.3.13' gem 'byebug', '9.0.6', platform: :mri end group :development do gem 'web-console', '3.5.1' gem 'listen', '3.1.5' gem 'spring', '2.0.2' gem 'spring-watcher-listen', '2.0.1' end group :production do gem 'pg', '0.20.0' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
-
bundle install --without production
-
git init git add . git commit -m "Initialize repository"
-
Adding a hello action to the Application controller. app/controllers/application_controller.rb
def hello render html: "hello, world!" end
-
Setting the root route. config/routes.rb
root 'application#hello'
-
git commit -am "Add hello" git push heroku create git push heroku master
-
Static pages
-
To get started with static pages, we’ll first generate a controller
rails generate controller StaticPages home help
-
-
The Static Pages controller automatically updates the routes file (config/routes.rb)
See ... static_pages/home
-
Layouts
-
Dodaj do app/views/static_pages/home.html.erb
<% provide(:title, "Home") %> <h1>Sample App</h1> <p> This is the home page </p>
-
Setting the root route to the Home page. config/routes.rb
Rails.application.routes.draw do root 'static_pages#home' get 'static_pages/home' get 'static_pages/help' get 'static_pages/about' end
-
git add . git commit -m "Finish static page" # git push git push heroku
-
Site navigation
<!DOCTYPE html> <html> <head> <title><%= yield(:title) %></title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <!--[if lt IE 9]> <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js"> </script> <![endif]--> </head> <body> <header class="navbar navbar-fixed-top navbar-inverse"> <div class="container"> <%= link_to "sample app", '#', id: "logo" %> <nav> <ul class="nav navbar-nav navbar-right"> <li><%= link_to "Home", '#' %></li> <li><%= link_to "Help", '#' %></li> <li><%= link_to "Log in", '#' %></li> </ul> </nav> </div> </header> <div class="container"> <%= yield %> </div> </body> </html>
-
Downloading an image rails.png.
curl -o app/assets/images/rails.png -OL railstutorial.org/rails.png
-
Add hyperlink by image_tag
<%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/' %>
-
Bootstrap and custom CSSAdding the bootstrap-sass gem to the Gemfile
gem 'bootstrap-sass', '3.3.7'
and runbundle install
-
Add styleshit to app/assets/stylesheets/custom.scss
touch app/assets/stylesheets/custom.scss
and add@import "bootstrap-sprockets"; @import "bootstrap"; /* universal */ body { padding-top: 60px; } section { overflow: auto; } textarea { resize: vertical; } .center { text-align: center; } .center h1 { margin-bottom: 10px; }
-
Dodaj do app/views/static_pages/home.html.erb
<div class="center jumbotron"> <h1>Welcome to the Sample App</h1> <h2> This is the home page. </h2> <%= link_to "Sign up now!", '#', class: "btn btn-lg btn-primary" %> </div> <%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/' %>
-
Add to app/assets/stylesheets/custom.scss
/* typography */ h1, h2, h3, h4, h5, h6 { line-height: 1; } h1 { font-size: 3em; letter-spacing: -2px; margin-bottom: 30px; text-align: center; } h2 { font-size: 1.2em; letter-spacing: -1px; margin-bottom: 30px; text-align: center; font-weight: normal; color: #777; } p { font-size: 1.1em; line-height: 1.7em; }
-
Converts the text of logo to uppercase and modifies its size, color, and placement add to app/assets/stylesheets/custom.scss
/* header */ #logo { float: left; margin-right: 10px; font-size: 1.7em; color: #fff; text-transform: uppercase; letter-spacing: -1px; padding-top: 9px; font-weight: bold; } #logo:hover { color: #fff; text-decoration: none; }
-
Partials
Add ne filles:
app/views/layouts/_shim.html.erb
app/views/layouts/_header.html.erb
app/views/layouts/_footer.html.erb
<!--[if lt IE 9]> <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js"> </script> <![endif]-->
-
<%= render 'layouts/shim' %>
-
<header class="navbar navbar-fixed-top navbar-inverse"> <div class="container"> <%= link_to "sample app", '#', id: "logo" %> <nav> <ul class="nav navbar-nav navbar-right"> <li><%= link_to "Home", '#' %></li> <li><%= link_to "Help", '#' %></li> <li><%= link_to "Log in", '#' %></li> </ul> </nav> </div> </header>
-
<footer class="footer"> <small> Footer </small> <nav> <ul> <li><%= link_to "About", '#' %></li> <li><%= link_to "Contact", '#' %></li> </ul> </nav> </footer>
-
Change app/views/layouts/application.html.erb
<!DOCTYPE html> <html> <head> <title><%= yield(:title) %></title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%= render 'layouts/shim' %> </head> <body> <%= render 'layouts/header' %> <div class="container"> <%= yield %> <%= render 'layouts/footer' %> </div> </body> </html>
-
Add
/* footer */ footer { margin-top: 45px; padding-top: 5px; border-top: 1px solid #eaeaea; color: #777; } footer a { color: #555; } footer a:hover { color: #222; } footer small { float: left; } footer ul { float: right; list-style: none; } footer ul li { float: left; margin-left: 15px; }
-
Layout links
<%= link_to "Help", help_path %>
and add config/routes.rbget '/help', to: 'static_pages#help'
-
User signup: A first step
rails generate controller Users new
-
Add to config/routes.rb
get '/signup', to: 'users#new'
-
<%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
-
<% provide(:title, 'Sign up') %>
-
Modeling users
-
Command for making a model is generate model, which we can use to generate a User model with name and email attribute
rails generate model User name:string email:string
One of the results of the generate command is a new file called a migration.
The full data model represented by the migration:
We can run the migration, known as “migrating up”, using the db:migrate command as follows: -
rails db:migrate
-
We can see the structure of the database by opening development.sqlite3
with DB Browser for SQLite http://sqlitebrowser.org/
rails console --sandbox User.new user = User.new(name: "Przemysław Spurek", email: "spurek@example.com") user.valid? user.save user ser.name user.email user.updated_at User.create(name: "Aaa", email: "aaa@example.org") foo = User.create(name: "Bbb", email: "bbb@example.org") foo.destroy
-
Finding user objects
User.find(1) User.find(3) User.find_by(email: "spurek@example.com") User.first User.all
-
Updating user objects
user # Just a reminder about our user's attributes user.email = "mhartl@example.net" user.save user user.created_at user.updated_at
-
user.update_attributes(name: "The Dude", email: "dude@abides.org") user.name user.email
-
user.update_attribute(:name, "El Duderino")
-
User validations
Add tests to test/models/user_test.rbrequire 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com") end test "should be valid" do assert @user.valid? end end
-
rails test:models
-
Perhaps the most elementary validation is presence, which simply verifies that a given attribute is present.
test "name should be present" do @user.name = " " assert_not @user.valid? end
-
rails test:models
-
Add to app/models/user.rb
class User < ApplicationRecord validates :name, presence: true end
-
rails test:models
-
rails console --sandbox user = User.new(name: "", email: "mhartl@example.com") user.valid? user.errors.full_messages user.save
- ZADANIE Dodaj test validację
test "email should be present" do @user.email = " " assert_not @user.valid? end
-
validates :email, presence: true
-
maximum length validation
test "name should not be too long" do @user.name = "a" * 51 assert_not @user.valid? end test "email should not be too long" do @user.email = "a" * 244 + "@example.com" assert_not @user.valid? end
-
validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }
-
Format validation
test "email validation should accept valid addresses" do valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org first.last@foo.jp alice+bob@baz.cn] valid_addresses.each do |valid_address| @user.email = valid_address assert @user.valid?, "#{valid_address.inspect} should be valid" end end test "email validation should reject invalid addresses" do invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com] invalid_addresses.each do |invalid_address| @user.email = invalid_address assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" end end
-
rails test
-
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
http://www.rubular.com/ -
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }
-
rails test:models
-
Uniqueness validation
test "email addresses should be unique" do duplicate_user = @user.dup @user.save assert_not duplicate_user.valid? end
-
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true
-
rails test
-
case-insensitivity
test "email addresses should be unique" do duplicate_user = @user.dup duplicate_user.email = @user.email.upcase @user.save assert_not duplicate_user.valid? end
-
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
-
rails test
- Database indices When creating a column in a database, it is important to consider whether we will need to find records by that column. Consider, for example, the email attribute created by the migration. When we allow users to log in to the sample app, we will need to find the user record corresponding to the submitted email address. Unfortunately, based on the naive data model, the only way to find a user by email address is to look through each user row in the database and compare its email attribute to the given email—which means we might have to examine every row (since the user could be the last one in the database). This is known in the database business as a full-table scan, and for a real site with thousands of users it is a Bad Thing.
-
The email index represents an update to our data modeling
rails generate migration add_index_to_users_email
-
Add to db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[5.0] def change add_index :users, :email, unique: true end end
-
rails db:migrate
-
rails test
-
Delete all from test/fixtures/users.yml
-
rails test
-
Ensuring email uniqueness by downcasing the email attribute
before_save { self.email = email.downcase }
-
Adding a secure password
Most of the secure password machinery will be implemented using a single Rails method called has_secure_password, which we’ll include in the User model. The only requirement for has_secure_password to work its magic is for the corresponding model to have an attribute called password_digest.
rails generate migration add_password_digest_to_users password_digest:string
-
rails db:migrate
-
To make the password digest, has_secure_password uses a state-of-the-art hash function called
bcrypt.
To use bcrypt in the sample application, we need to add the bcrypt gem to our Gemfilegem 'bcrypt', '3.1.12'
-
bundle install
-
Add to app/models/user.rb
has_secure_password
-
rails test
-
Add to test/models/user_test.rb
def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end
-
rails test
-
Minimum password standards
test "password should be present (nonblank)" do @user.password = @user.password_confirmation = " " * 6 assert_not @user.valid? end test "password should have a minimum length" do @user.password = @user.password_confirmation = "a" * 5 assert_not @user.valid? end
-
rails test
-
validates :password, presence: true, length: { minimum: 6 }
-
rails test
-
Creating and authenticating a user
rails console User.create(name: "Michael Hartl", email: "mhartl@example.com", password: "foobar", password_confirmation: "foobar") user = User.find_by(email: "mhartl@example.com") user.password_digest user.authenticate("not_the_right_password") user.authenticate("foobaz") user.authenticate("foobar") !!user.authenticate("foobar")
-
Adding some debug information to the site layout
<%= debug(params) if Rails.env.development? %>
-
Make the debug output look nice
/* mixins, variables, etc. */ $gray-medium-light: #eaeaea; @mixin box_sizing { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } /* miscellaneous */ .debug_dump { clear: both; float: left; width: 100%; margin-top: 45px; @include box_sizing; }
-
Adding a Users resource to the routes file config/routes.rb
Rails.application.routes.draw do root 'static_pages#home' get '/help', to: 'static_pages#help' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' get '/signup', to: 'users#new' resources :users end
-
-
Create app/views/users/show.html.erb
< @user.name %>, <%= @user.email %>
-
Add to app/controllers/users_controller.rb
def show end
-
Add to app/controllers/users_controller.rb
def show @user = User.find(params[:id]) end
-
Add a “globally recognized avatar”, or Gravatar.
Add to app/views/users/show.html.erb<% provide(:title, @user.name) %> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1>
-
Add to app/helpers/users_helper.rb
# Returns the Gravatar for the given user. def gravatar_for(user) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end
-
<% provide(:title, @user.name) %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1> </section> </aside> </div>
-
Add to app/assets/stylesheets/custom.scss
/* sidebar */ aside { section.user_info { margin-top: 20px; } section { padding: 10px 0; margin-top: 20px; &:first-child { border: 0; padding-top: 0; } span { display: block; margin-bottom: 3px; line-height: 1; } h1 { font-size: 1.4em; text-align: left; letter-spacing: -1px; margin-bottom: 3px; margin-top: 0px; } } } .gravatar { float: left; margin-right: 10px; } .gravatar_edit { margin-top: 15px; }
-
Signup form
Add to app/controllers/users_controller.rbdef new @user = User.new end
-
A form to sign up new users
<% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div>
-
app/assets/stylesheets/custom.scss
/* forms */ input, textarea, select, .uneditable-input { border: 1px solid #bbb; width: 100%; margin-bottom: 15px; @include box_sizing; } input { height: auto !important; }
-
def create @user = User.new(params[:user]) # Not the final implementation! if @user.save # Handle a successful save. else render 'new' end end
-
Since user_params will only be used internally by the Users controller and need not
be exposed to external users via the web, we’ll make it private using Ruby’s private keyword
def create @user = User.new(user_params) if @user.save # Handle a successful save. else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end
-
Signup error messages
Add to app/views/users/new.html.erb<% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div>
-
mkdir app/views/shared
-
Add to app/views/shared/_error_messages.html.erb
<% if @user.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(@user.errors.count, "error") %>. </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>
-
#error_explanation { color: red; ul { color: red; margin: 0 0 30px 0; } } .field_with_errors { @extend .has-error; .form-control { color: $state-danger-text; } }
-
Adding a signup route responding to POST requests
Add to config/routes.rbRails.application.routes.draw do root 'static_pages#home' get '/help', to: 'static_pages#help' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' get '/signup', to: 'users#new' post '/signup', to: 'users#create' resources :users end
-
add to app/views/users/new.html.erb
<% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user, url: signup_path) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div>
-
Add to app/controllers/users_controller.rb
def create @user = User.new(user_params) if @user.save redirect_to @user else render 'new' end end
-
The flash
def create @user = User.new(user_params) if @user.save flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end
-
The first signup
rails db:migrate:reset
-
add to app/views/layouts/application.html.erb
<% flash.each do |message_type, message| %> <%= content_tag(:div, message, class: "alert alert-#{message_type}") %> <% end %>
-
Sessions
HTTP is a stateless protocol, treating each request as an independent transaction that is unable to use information from any previous requests. This means there is no way within the hypertext transfer protocol to remember a user’s identity from page to page; instead, web applications requiring user login must use a session, which is a semi-permanent connection between two computers (such as a client computer running a web browser and a server running Rails).
The most common techniques for implementing sessions in Rails involve using cookies,rails generate controller Sessions new
-
Add
Rails.application.routes.draw do root 'static_pages#home' get '/help', to: 'static_pages#help' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' get '/signup', to: 'users#new' get '/login', to: 'sessions#new' post '/login', to: 'sessions#create' delete '/logout', to: 'sessions#destroy' resources :users end
-
rails routes
-
Login form
<% provide(:title, "Log in") %> <h1>Log in</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div>
-
Finding and authenticating a user
Add to app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) # Log the user in and redirect to the user's show page. else # Create an error message. flash[:danger] = 'Invalid email or password combination' # Not quite right! render 'new' end end def destroy end end
-
Including the Sessions helper module into the Application controller.
class ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper end
-
Add to app/helpers/sessions_helper.rb
module SessionsHelper # Logs in the given user. def log_in(user) session[:user_id] = user.id end end
-
Add
def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end
-
Finding the current user in the session.
app/helpers/sessions_helper.rb# Returns the current logged-in user (if any). def current_user @current_user ||= User.find_by(id: session[:user_id]) end
-
Changing the layout links
<% if logged_in? %> # Links for logged-in users <% else %> # Links for non-logged-in-users <% end %>
-
Add to app/helpers/sessions_helper.rb
# Returns true if the user is logged in, false otherwise. def logged_in? !current_user.nil? end
-
<header class="navbar navbar-fixed-top navbar-inverse"> <div class="container"> <%= link_to "sample app", root_path, id: "logo" %> <nav> <ul class="nav navbar-nav navbar-right"> <li><%= link_to "Home", root_path %></li> <li><%= link_to "Help", help_path %></li> <% if logged_in? %> <li><%= link_to "Users", '#' %></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> Account <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><%= link_to "Profile", current_user %></li> <li><%= link_to "Settings", '#' %></li> <li class="divider"></li> <li> <%= link_to "Log out", logout_path, method: :delete %> </li> </ul> </li> <% else %> <li><%= link_to "Log in", login_path %></li> <% end %> </ul> </nav> </div> </header>
-
Adding the Bootstrap JavaScript library to application.js.
app/assets/javascripts/application.js
//= require jquery //= require bootstrap
-
Although our authentication system is now working, newly registered users might be confused, as they are not logged in by default.
Add app/controllers/users_controller.rbdef create @user = User.new(user_params) if @user.save log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end
-
The log_out method.
Add to app/helpers/sessions_helper.rb# Logs out the current user. def log_out session.delete(:user_id) @current_user = nil end
-
Add to app/controllers/sessions_controller.rb
def destroy log_out redirect_to root_url end
-
Add to app/controllers/users_controller.rb
def edit @user = User.find(params[:id]) end
-
Create app/views/users/edit.html.erb
<% provide(:title, "Edit user") %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Save changes", class: "btn btn-primary" %> <% end %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="http://gravatar.com/emails" target="_blank">change</a> </div> </div> </div>
-
Add
<%= link_to "Settings", edit_user_path(current_user) %>
-
Add to app/controllers/users_controller.rb
def update @user = User.find(params[:id]) if @user.update_attributes(user_params) # Handle a successful update. else render 'edit' end end
-
Successful edits (with TDD)
def update @user = User.find(params[:id]) if @user.update_attributes(user_params) flash[:success] = "Profile updated" redirect_to @user else render 'edit' end end
-
Allowing empty passwords on update
app/models/user.rbvalidates :password, presence: true, length: { minimum: 6 }, allow_nil: true
-
Authorization
Although the edit and update actions are functionally complete, they suffer from a ridiculous security flaw: they allow anyone (even non-logged-in users) to access either action and update the information for any user. -
Add to app/controllers/users_controller.rb
before_action :logged_in_user, only: [:edit, :update]
-
def logged_in_user unless logged_in? flash[:danger] = "Please log in." redirect_to login_url end end
-
Requiring the right user
Add to app/controllers/users_controller.rbbefore_action :correct_user, only: [:edit, :update]
-
# Confirms the correct user. def correct_user @user = User.find(params[:id]) redirect_to(root_url) unless @user == current_user end
- We can add the current_user? method.
Add to app/helpers/sessions_helper.rb
def current_user?(user) user == current_user end
-
and change correct_user method in app/controllers/users_controller.rb
# Confirms the correct user. def correct_user @user = User.find(params[:id]) redirect_to(root_url) unless current_user?(@user) end
-
Showing all users
Add to app/controllers/users_controller.rbbefore_action :logged_in_user, only: [:index, :edit, :update] def index end
-
Zadanie Co musimy dodać do funkcji index
def index @users = User.all end
-
Add app/views/users/index.html.erb
<% provide(:title, 'All users') %> <h1>All users</h1> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul>
-
Add Gravatar helper specifying a size other than the default
app/helpers/users_helper.rbmodule UsersHelper # Returns the Gravatar for the given user. def gravatar_for(user, options = { size: 80 }) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) size = options[:size] gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end
-
Add to app/assets/stylesheets/custom.scss
/* Users index */ .users { list-style: none; margin: 0; li { overflow: auto; padding: 10px 0; border-bottom: 1px solid $gray-lighter; } }
-
Adding the URL to the users link
app/views/layouts/_header.html.erb
<li><%= link_to "Users", users_path %></li>
-
Sample users
gem 'faker', '1.7.3'
-
bundle install
-
Add to db/seeds.rb
User.create!(name: "Example User", email: "ppp@ppp.pl", password: "123456", password_confirmation: "123456") 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@ror.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password) end
-
rails db:migrate:reset rails db:seed
-
Pagination
gem 'will_paginate', '3.1.6' gem 'bootstrap-will_paginate', '1.0.0'
-
bundle install
-
Add to app/views/users/index.html.erb
<%= will_paginate %> ... <%= will_paginate %>
-
def index @users = User.paginate(page: params[:page]) end
-
Administrative users and Deleting users
rails generate migration add_admin_to_users admin:boolean
-
Add to db/migrate/[timestamp]_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration[5.0] def change add_column :users, :admin, :boolean, default: false end end
-
rails db:migrate
-
Add admin to some user in db/seeds.rb
User.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", admin: true) 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password) end
-
rails db:migrate:reset rails db:seed
-
Add to
<% if current_user.admin? && !current_user?(user) %> | <%= link_to "delete", user, method: :delete, data: { confirm: "You sure?" } %> <% end %>
-
Add to app/controllers/users_controller.rb
before_action :logged_in_user, only: [:index, :edit, :update, :destroy] def destroy User.find(params[:id]).destroy flash[:success] = "User deleted" redirect_to users_url end
-
Add to app/controllers/users_controller.rb
before_action :admin_user, only: :destroy ... private ... # Confirms an admin user. def admin_user redirect_to(root_url) unless current_user.admin? end
-
git add . git commit -m "Finish user edit, update, index, and destroy actions" git push heroku heroku pg:reset DATABASE heroku run rails db:migrate heroku run rails db:seed heroku restart
-
rails generate model Micropost content:text user:references
-
class Micropost < ApplicationRecord belongs_to :user end
-
class CreateMicroposts < ActiveRecord::Migration[5.0] def change create_table :microposts do |t| t.text :content t.references :user, foreign_key: true t.timestamps end add_index :microposts, [:user_id, :created_at] end end
-
rails db:migrate
-
class Micropost < ActiveRecord::Base belongs_to :user validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } end
-
has_many :microposts
-
Ordering the microposts with add to app/models/micropost.rb
class Micropost < ApplicationRecord belongs_to :user default_scope -> { order(created_at: :desc) } validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } end
-
Ensuring that a user’s microposts are destroyed along with the user.
add to app/models/user.rbhas_many :microposts, dependent: :destroy
-
Showing microposts
rails db:migrate:reset rails db:seed
-
rails generate controller Microposts
-
add to app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"><%= micropost.content %></span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. </span> </li>
-
<%= will_paginate @microposts %> <%= will_paginate %>
- ot users_controller.rb we add
def show @user = User.find(params[:id]) @microposts = @user.microposts.paginate(page: params[:page]) end
-
Add to app/views/users/show.html.erb
<% provide(:title, @user.name) %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1> </section> </aside> <div class="col-md-8"> <% if @user.microposts.any? %> <h3>Microposts (<%= @user.microposts.count %>)</h3> <ol class="microposts"> <%= render @microposts %> </ol> <%= will_paginate @microposts %> <% end %> </div> </div>
-
add to db/seeds.rb
users = User.order(:created_at).take(6) 50.times do content = Faker::Lorem.sentence(5) users.each { |user| user.microposts.create!(content: content) } end
-
rails db:migrate:reset rails db:seed
-
/* microposts */ .microposts { list-style: none; padding: 0; li { padding: 10px 0; border-top: 1px solid #e8e8e8; } .user { margin-top: 5em; padding-top: 0; } .content { display: block; margin-left: 60px; img { display: block; padding: 5px 0; } } .timestamp { color: $gray-light; display: block; margin-left: 60px; } .gravatar { float: left; margin-right: 10px; margin-top: 5px; } } aside { textarea { height: 100px; margin-bottom: 5px; } } span.picture { margin-top: 10px; input { border: 0; } }
Zad -
Account activation
At present, newly registered users immediately have full access to their accounts (Chapter 7); in this chapter, we’ll implement an account activation step to verify that the user controls the email address they used to sign up.
1. Start users in an “unactivated” state.
2. When a user signs up, generate an activation token and corresponding activation digest.
3. Save the activation digest to the database, and then send an email to the user with a link containing the activation token and user’s email address.
4. When the user clicks the link, find the user by email address, and then authenticate the token by comparing with the activation digest.
5. If the user is authenticated, change the status from “unactivated” to “activated”.
-
rails generate controller AccountActivations
-
add to config/routes.rb
resources :account_activations, only: [:edit]
-
Modeling users
-
rails generate migration add_activation_to_users activation_digest:string activated:boolean activated_at:datetime
-
add to db/migrate/[timestamp]_add_activation_to_users.rb
add_column :users, :activation_digest, :string add_column :users, :activated, :boolean, default: false add_column :users, :activated_at, :datetime
-
rails db:migrate
-
Now we have to decide what to use as a remember token.
rails console SecureRandom.urlsafe_base64
The password digest is created using bcrypt (via has_secure_password), so we’ll need to create the fixture password using the same method.
-
add to app/models/user.rb
attr_accessor :activation_token before_save :downcase_email before_create :create_activation_digest ... # Returns the hash digest of the given string. def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # Returns a random token. def User.new_token SecureRandom.urlsafe_base64 end private # Converts email to all lower-case. def downcase_email self.email = email.downcase end # Creates and assigns the activation token and digest. def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end
-
Seed and fixture users
-
add to db/seeds.rb
User.create!(name: "Example User", email: "ppp@ppp.pl", password: "123456", password_confirmation: "123456", admin: true, activated: true, activated_at: Time.zone.now) 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password, activated: true, activated_at: Time.zone.now) end
-
rails db:migrate:reset rails db:seed
-
rails generate mailer UserMailer account_activation password_reset
See -
add to app/mailers/user_mailer.rb
def account_activation(user) @user = user mail to: user.email, subject: "Account activation" end
-
add to app/views/user_mailer/account_activation.text.erb
Hi <%= @user.name %>, Welcome to the Sample App! Click on the link below to activate your account: <%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
-
add to app/views/user_mailer/account_activation.html.erb
<h1>Sample App</h1> <p>Hi <%= @user.name %>,</p> <p> Welcome to the Sample App! Click on the link below to activate your account: </p> <%= link_to "Activate", edit_account_activation_url(@user.activation_token, email: @user.email) %>
-
add to config/environments/development.rb
host = 'localhost:3000' # Local server config.action_mailer.default_url_options = { host: host, protocol: 'http' }
-
add to test/mailers/previews/user_mailer_preview.rb
def account_activation user = User.first user.activation_token = User.new_token UserMailer.account_activation(user) end
-
wisit http://localhost:3000//rails/mailers/user_mailer/account_activation.html
-
add to app/controllers/users_controller.rb
def create @user = User.new(user_params) if @user.save UserMailer.account_activation(@user).deliver_now flash[:info] = "Please check your email to activate your account." redirect_to root_url else render 'new' end end
-
add to app/models/user.rb
# Returns true if the given token matches the digest. def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end
-
add to app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now) log_in user flash[:success] = "Account activated!" redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end end
-
add to app/controllers/sessions_controller.rb
def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) if user.activated? log_in user redirect_to user else message = "Account not activated. " message += "Check your email for the activation link." flash[:warning] = message redirect_to root_url end else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end
-
source <(curl -sL https://cdn.learnenough.com/heroku_install) heroku create gem install bundler bundle config --delete without bundle config --delete with bundle install --without production git add . git commit -am "Add hello" git push heroku master heroku pg:reset DATABASE heroku run rails db:migrate heroku run rails db:seed heroku restart
-
To send email in production, we’ll use SendGrid, which is available as an add-on at Heroku for verified accounts. (This requires adding credit card information to your Heroku account, but there is no charge when verifying an account.)
heroku addons:create sendgrid:starter
-
We can use gmail
https://accounts.google.com/DisplayUnlockCaptcha
https://myaccount.google.com/lesssecureapps?pli=1 -
add to config/environments/development.rb
config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', port: 587, domain: 'example.com', user_name: '...', password: '...', authentication: 'plain', # enable_starttls_auto: true }
-
add to config/routes.rb
resources :microposts, only: [:create, :destroy]
-
add to app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] def create end def destroy end end
-
add to app/controllers/microposts_controller.rb
def create @micropost = current_user.microposts.build(micropost_params) if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else render 'static_pages/home' end end ... private def micropost_params params.require(:micropost).permit(:content) end
-
add to app/controllers/application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper private # Confirms a logged-in user. def logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end end end
-
add to app/views/static_pages/home.html.erb
<% if logged_in? %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section class="micropost_form"> <%= render 'shared/micropost_form' %> </section> </aside> </div> <% else %> <div class="center jumbotron"> <h1>Welcome to the Sample App</h1> <h2> This is the home page for the <a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a> sample application. </h2> <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> </div> <%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/' %> <% end %>
-
Create app/views/shared/_user_info.html.erb
<%= link_to gravatar_for(current_user, size: 50), current_user %> <h1><%= current_user.name %></h1> <span><%= link_to "view my profile", current_user %></span> <span><%= pluralize(current_user.microposts.count, "micropost") %></span>
-
Create app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %> <%= render 'shared/error_messages', object: f.object %> <div class="field"> <%= f.text_area :content, placeholder: "Compose new micropost..." %> </div> <%= f.submit "Post", class: "btn btn-primary" %> <% end %>
-
Add to app/controllers/static_pages_controller.rb
def home @micropost = current_user.microposts.build if logged_in? end
-
Add to app/views/shared/_error_messages.html.erb
<% if object.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(object.errors.count, "error") %>. </div> <ul> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>
Change in
- app/views/users/new.html.erb
- app/views/users/edit.html.erb
Manipulating microposts
Account activation emails
app/views/user_mailer/account_activation.text.erb
app/views/user_mailer/account_activation.html.erb
Manipulating microposts
Creating microposts
To build a form for creating microposts, we use home direction
<%= render 'shared/error_messages', object: f.object %>