over 2 years ago
  1. 要不要讓沒有註冊的網友看到user index? 現在的設定是:
    Controller/Users_Controller:沒有註冊的人,會直接跑到註冊頁面。
    if user_signed_in?
        render :index
    else
        redirect_to new_user_registration_path
    end
    
    如果是這樣設定,user index就不用這樣設了:
    Users/index.html.erb :如果有登入,顯示配對率,如果沒有登入,不用顯示。
    <% if current_user %>
        看到配對率
    <% else %>
        只有大頭照
    <% end %>
    </div>
    <%= will_paginate @paginate, :page_links => false if @paginate %>
    </div>
    
  2. 這是user_helper.rb的設定
    user_helper.rb
    module UsersHelper
        #由view那邊傳來current_user和user兩個變數。
    
        def match_percentage(current_user, user)
            #計算非登入用戶的option數量
    
        other_user_option = user.option
            number_other_user_option =  other_user_option.length.to_f
            #計算登入用戶的option數量
    
        current_user_option = current_user.option
            number_current_user_option =  current_user_option.length.to_f
        #計算非登入用戶與登入用戶,相同option的數量。
    
            number_same_option = (other_user_option&current_user_option).length.to_f
            #比重計算
    
        var_match_current = (number_same_option)/(number_current_user_option)
            var_match_other   = (number_same_option)/(number_other_user_option)
            #配對率計算式
    
        @match = number_to_percentage(Math.sqrt(var_match_current  var_match_other)100, precision: 0)
            #回傳配對率
    
        @match
        end
    end
    
  3. 這邊會有兩個問題,如果非登入用戶沒回答過問題,或者是,如果登入用戶也沒回答過問題
  4. ## 最簡單的解決方法,逼迫所有人在登入的時候一定要回答三個問題。
  5. 這是user index裡面的設定,應該是已經把如果非登入用戶沒回答過問題的問題排除掉了(<% if user.respond.any? %>),但是如果登入用戶也沒回答過問題就還沒有排除掉,所以應該要加<% if current_user.respond.any? %>
    Users/index.html.erb
    <% if current_user %>
    <ul class='items clearfix'>
      <% @user.each do |user| %>
        <li>
          <span class='index_content'>
            <span class='index_thumb'>
              <%= image_tag(user.avatar.url(:thumb)) %>
            </span>
            <span class='index_user'>
              <span class='index_lastrespond'>
                <% if user.respond.any? %>
                  <%= user.respond.last.option.issue.content %>
                <% else %>
                  <p><%= "還沒回答過問題" %></p>
                <% end %>
              </span>
    </span> </span> <span class=
    'footer_match'> <span> <strong><%= link_to user.name, user_path(user), class:'index_name'%></strong> </span> <span class='match_percentage'> <% unless user.respond.any? %> <span class='heart'><span class='glyphicon glyphicon-heart'></span> <strong><%= match_percentage(current_user, user) %></strong></span> <% else %> <span class='heart'><span class='glyphicon glyphicon-heart'></span> <strong>0%</strong></span> <% end %> </span> </span> </li>
    <% end %> </u
    l> <% else %> <ul class='items'> <% @user.each do |user| %> <li> <%= image_tag(user.avatar.url(:icon)) %> <span class='name'> <%= user.name %></span> </li>
    <% end %> </ul> <% end %> </div> <%=
    will_paginate @paginate, :page_links => false if @paginate %> </div>


    1. 除了最簡單的方法外,我能夠想到的方法是:
      Controller/Users_Controller:
      if user_signed_in? 
            if current_user.option.any?
              render :index
            else
              redirect_to unresponded_issue_responds_path
            end
            else
              redirect_to new_user_registration_path
          end
      
      如果登入,如果有回答過問題,就到首頁去看配對。如果沒有回答過問題,就到新問題去。 如果沒有登入,就到註冊頁。
 
over 2 years ago

參考:Rails 3: Multiple Select with has_many through associations

參考:Rails form with multiple nested models causes issues with radio groups

參考:after create and after save
--after_create:第一次create時才會啟動。 after_save:每次存檔的時候都會啟動

參考:Ruby on Rails - Creating a profile when user is created

  1. 登入以後,要先回答三個問題。
  2. 現在因為沒有發出註冊信,所以可以設在註冊之後。不過,以後應該會增加註冊信,所以還是在登入後再回答三個準備題比較好。
  3. 註冊之後的畫面:How To: Redirect to a specific page on successful sign up (registration)
  4. 新增profile => 讓新註冊跟登入的人來填寫基本資料跟準備題。
    目的:避免用戶在還沒回答過問題,就跑去配對 (兩個方法來避免:用基本資料跟準備題來配對;如果沒有回答過問題,跑到首頁要去看配對會被導引到問題集那邊去)

    • User has one Profile <=> Profile belongs_to Uer
    • 想問看看 devise 擴展
    • JC:User 和 Profile 切離成 1 : 1 relation 是很正常的,因為你不會希望每次 SELECT users 的時候都把地址之類的資訊都 select 出來,因為這頻率很高,而這樣做很蠢(累死你家 mysql),而在我們家的打法,Profile 沒有 id,只有 user_id,且 add index unique => true,這樣就很漂亮,create User 的時候用 callback 順便把空的 Profile 一起生出來即可(我猜,應該是在user model裡面用after_create去生出來空的profile。)

    參考:after create and after save

    參考:Ruby on Rails - Creating a profile when user is created
    after save:每次存檔都會啟動;after create:只有第一次create時才會啟動。

#User Model的兩種寫法:
class User < ActiveRecord::Base
第一種:
after_create :build_profile
private
def build_profile
 @profile = @user.build_profile
end

第二種: 
  after_create do
    @profile = @user.build_profile
  end
end

正確的寫法:
    after_create :build_profile

  def build_profile
    Profile.create(user: self)
  end

再來, User 儲存一個 column 叫做 is_profile 來存 Profile 是否有填過完整資料,在必要時才阻擋或提醒需要去完成即可

這邊在 Profile 增加一個 after_savecallback 來檢查 profile 的完整性,ok 時修改 user 即可,而以使用者便利的因素,是不會在註冊時就要他填入過多資料就是了(應該是在profile model裡面寫after_save,驗證完整性應該是用valid?就可以了)

Updating a Rails Model with a Non-default Primary Key

準備題有兩種做法:
- 第一個做法:在profile.rb裡面多弄出來5個attributes,然後準備5個題目,這樣以後擴充可能會有困難。要增加第6個attribute。
- 第二個做法:是profile has_many :option, through: :profile_option,然後可以在new.html.erb一次送出去5個題目。不過會發生錯誤:每個selection都要變成是複選題(:multiple => true),option_ids=>[]才能夠生效。
- item

  1. application_controller,參考:ActiveRecord - 資料驗證及回呼
    application_controller
    def after_sign_in_path_for(resource) 
    case resource 
      when User 
        #dashboard_user_path(resource)
    
        #如果沒有profile,去新增一個。
    
        if current_user.profile.blank?
         new_dashboard_user_profile_path(resource)
        else
          dashboard_user_path(resource)
        end
        #如果profile驗證有錯誤,去edit profile
    
        #if current_user.profile.valid?
    
          #edit_dashboard_user_profile_path(resource)
    
        #else
    
          #root_path
    
        #end
    
      when Manager
        dashboard_admin_managers_path
      else
        root_path 
    end 
    
    應該還是要加上validates_length_ofvalidates_presence_of這種東西,valid?才可以用?

JC新增profile的方法:http://railsfun.tw/t/devise/497
我的方法:

  1. 用戶註冊
  2. 拿到確認信
  3. 回來確認
  4. 登入
  5. 檢查有沒有profile (profile.blank?) 5-1. 沒有的話就新增:要注意用 - 戶可以按別的鈕,然後跑到別的地方去。如果跑到有配對數據、問答題的地方,就會發生錯誤。 5-2. 有的話就登入
 
over 2 years ago
  1. dashboard要修改,不然A用戶可以去改到B用戶的答案。
    解答:

    dashboard_controller.rb
    class Dashboard::DashboardController < ApplicationController
    before_action :authenticate_user!
    layout 'dashboard'
    before_filter :require_permission
    
    def require_permission
        if current_user != User.find(params[:id])
            redirect_to root_path
        end
    end
    end
    

    不過這樣寫之後又出現新的問題,自己回答過的題目會進不去,會找不到id。
    解決:後來JC有講解法。

  2. 檢查為甚麼registration的時候會出現提示訊息,login不會。

  3. 建立與編輯類別/題目的時候

    • 有刪除類別的按鈕,沒有刪除問題的按鈕
    • 用戶回答過的,如果類別/題目被刪除了,會發生什麼事情?
    • 新建問題的時候,有四個選項。如果只填兩個選項,在編輯的時候,只會有兩個選項可以編輯,不能新增第三個選項。
  4. 照片上傳後看不到預覽。好像是devise的問題吧 Users Avatars - Uploading Images Using Paperclip

  5. 看誰回答過一樣的題目,應該要顯示答題人數。然後進去之後,應該是看到每個回答過的用戶對於當題的答案
    可以寫成這樣,不過會包含current_user,所以還要扣掉1。

    view/responds/responded_issue.html.erb
    <td>
    <%= link_to "#{ri.option.issue.user.count}人回答過", issue_path(ri.option.issue.id), class:"btn btn-purple" %>
    </td>
    

    扣掉1。有點怪怪的,不過可以用。

    view/responds/responded_issue.html.erb
    <td>
    <%= link_to "#{ri.option.issue.user.count-1}人回答過", issue_path(ri.option.issue.id), class:"btn btn-purple" %>
    </td>
    
  6. 這個式子應該是錯的,所以才會前後台的respond id 不一樣。

    users_helper.rb
    def current_user_respond(user_respond,current_user)
    @current_user_respond = current_user.issue.find(user_respond.option.issue.id).respond.find_by(:user_id == current_user).option.content
    @current_user_respond
    end
    
    a.respond.find_by(:option_id => Issue.find(7)
    

    如果問題只有兩個選項,之後不能新增新選項,只能刪除舊選項。

  7. current_user沒回答過的問題,別的user回答過的問題應該顯示:已答,而不是直接顯示答案。

 
over 2 years ago
  1. Sessions,半永久性:登入必須要用session。瀏覽器關掉後,session就會被刪除。
    Sessions: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).

  2. Rails 是用cookies達到sessions的效果,並將cookies放到瀏覽器內。譬如,存入user id,就不用一直重復存取資料庫。
    The most common techniques for implementing sessions in Rails involve using cookies, which are small pieces of text placed on the user’s browser. Because cookies persist from one page to the next, they can store information (such as a user id) that can be used by the application to retrieve the logged-in user from the database.

  3. session method:暫時的sessions,Session Controller不會用到database(所以不需要用到Model)
    In this section and Section 8.2, we’ll use the Rails method called session to make temporary sessions that expire automatically on browser close。
    Unlike the Users resource, which uses a database back-end (via the User model) to persist data, the Sessions resource will use cookies, and much of the work involved in login comes from building this cookie-based authentication machinery.

  4. cookies method:生存期比較長的是cookies
    Section 8.4 we’ll add longer-lived sessions using another Rails method called cookies.

  5. find: raises an exception if the user id doesn’t exist.
    find_by:returns nil (indicating no such user) if the id is invalid.

  6. Sessions_Controller其實也是符合RESTful,new / create / destroy
    The elements of logging in and out correspond to particular REST actions of the Sessions controller: the login form is handled by the new action (covered in this section), actually logging in is handled by sending a POST request to the create action (Section 8.2), and logging out is handled by sending a DELETE request to the destroy action (Section 8.3).

  7. Sign in page用到的就是Sessions_Controller (註冊是用到User#new)

    routes.rb
    get    'login'   => 'sessions#new'
    post   'login'   => 'sessions#create'
    delete 'logout'  => 'sessions#destroy'
    
  8. 註冊錯誤會自動出現錯誤訊息 =>Active Record 。不過登入錯誤不會有,因為Session不是Active Record object。
    In Section 7.3.3, we used an error-messages partial to display error messages, but we saw in that section that those messages are provided automatically by Active Record. This won’t work for session creation errors because the session isn’t an Active Record object, so we’ll render the error as a flash message instead.

  9. User有Model,在使用form_for所以可以使用@user。不過,因為Session沒有Model,所以沒有@session這種東西,要把resource name跟相關的URL放上去:form_for(:session, url: login_path)
    Recall from Listing 7.13 that the signup form uses the form_for helper, taking as an argument the user instance variable @user:

    <%= form_for(@user) do |f| %>
    .
    .
    .
    <% end %>
    

    The main difference between the session form and the signup form is that we have no Session model, and hence no analogue for the @user variable. This means that, in constructing the new session form, we have to give form_for slightly more information; in particular, whereasform_for(@user)allows Rails to infer that the action of the form should be to POST to the URL /users, in the case of sessions we need to indicate the name of the resource and the corresponding URL:form_for(:session, url: login_path)With the proper form_for in hand, it’s easy to make a login form to match the mockup in Figure 8.1 using the signup form (Listing 7.13) as a model, as shown in Listing 8.2.

  10. params[:session][:email] and params[:session][:password]
    login form will result in a params hash where params[:session][:email] and params[:session][:password] correspond to the email and password fields.

  11. &&只有兩邊的值都是true時,結果才會是true。如果有一邊是false或nil,就會回傳false。
    This uses && (logical and) to determine if the resulting user is valid. Taking into account that any object other than nil and false itself is true in a boolean context (Section 4.2.3), the possibilities appear as in Table 8.2. We see from Table 8.2 that the if statement is true only if a user with the given email both exists in the database and has the given password, exactly as required.

  12. 有Model的,才會有Rails內建的error message(user.errors.full_messages)(<% @user.errors.full_messages.each do |msg| %>)。session因為只有controller,沒有model,所以只好自己寫(flash[:danger] = 'Invalid email/password combination')。
    (7.3.3 Signup error messages:As a final step in handling failed user creation, we’ll add helpful error messages to indicate the problems that prevented successful signup. Conveniently, Rails automatically provides such messages based on the User model validations. For example, consider trying to save a user with an invalid email address and with a password that’s too short:Here the errors.full_messages object (which we saw briefly before in Section 6.2.2) contains an array of error messages.)

  13. flash.now會在下一頁消失。
    flash.now disappear as soon as there is an additional request

    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
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
    end
    
  14. 產生Sessions_controller的時候,自動會產生Sessions_helper,Veiw就可以馬上用這些helper。不過,controller要include module之後,才能夠使用helper。所以要記得Application controllerli中,把她include進來。
    Conveniently, a Sessions helper module was generated automatically when generating the Sessions controller (Section 8.1.1). Moreover, such helpers are automatically included in Rails views; by including the module into the base class of all controllers (the Application controller), we arrange to make them available in our controllers as well (Listing 8.11).

    Listing 8.11: Including the Sessions helper module into the Application controller.
    app/controllers/application_controller.rb
    class ApplicationController < ActionController::Base
    protect_from_forgery with: :exception
    include SessionsHelper
    end
    

    With this configuration complete, we’re now ready to write the code to log users in.

  15. Rails有內建的session method,可以用來產生session[:user_id] = user.id,會把user.id自動加密之後處存在瀏覽器內,然後可以攜帶到其他頁面去。
    Logging a user in is simple with the help of the session method defined by Rails. (This method is separate and distinct from the Sessions controller generated in Section 8.1.1.) We can treat session as if it were a hash, and assign to it as follows:session[:user_id] = user.idThis places a temporary cookie on the user’s browser containing an encrypted version of the user’s id, which allows us to retrieve the id on subsequent pages using session[:user_id]. In contrast to the persistent cookie created by the cookies method (Section 8.4), the temporary cookie created by the session method expires immediately when the browser is closed.

  16. 登入,log_in,session[:user_id] = user.id會在sessions_controller.rb跟users_controller.rb用到,可以在sessions_helper.rb定義一個log_in method。
    登出,log_out,用destroy,只會在一個地方用到,在sessions_helper.rb定義一個log_out method
    Because we’ll want to use the same login technique in a couple of different places, we’ll define a method called log_in in the Sessions helper, as shown in Listing 8.12.

    Listing 8.12: The log_in function. app/helpers/sessions_helper.rb
    module SessionsHelper
    # Logs in the given user.
    
    def log_in(user)
    session[:user_id] = user.id
    end
    end
    
  17. 我的理解是,如果用find,會直接出現錯誤訊息。但是用find_by,因為是出現nil,所以程式還是可以繼續運作下去,接著作nil該作的事情。find: raises an exception if the user id doesn’t exist.find_by:returns nil (indicating no such user) if the id is invalid.
    But recall from Section 6.1.4 that find raises an exception if the user id doesn’t exist. This behavior is appropriate on the user profile page because it will only happen if the id is invalid, but in the present case session[:user_id] will often be nil (i.e., for non-logged-in users). To handle this possibility, we’ll use the same find_by method used to find by email address in the create method, with id in place of emailUser.find_by(id: session[:user_id])Rather than raising an exception, this method returns nil (indicating no such user) if the id is invalid.We could now define the current_user method as follows:

    def current_user
    User.find_by(id: session[:user_id])
    end
    
  18. 寫成這樣,會一直讀取資料庫,所以把寫成@會比較好。

    def current_user
    User.find_by(id: session[:user_id])
    end
    

    可以先簡化成:

    if @current_user.nil?
    @current_user = User.find_by(id: session[:user_id])
    else
    @current_user
    end
    

    然後變這樣:
    @current_user = @current_user || User.find_by(id: session[:user_id])
    最後變這樣:
    @current_user ||= User.find_by(id: session[:user_id])

  19. ||= (“or equals”)
    This uses the potentially confusing but frequently used ||= (“or equals”) operator (Box 8.1).

  20. 登入跟目前使用者的設定都在sessions_helper.rb

    app/helpers/sessions_helper.rb
     module SessionsHelper
    # Logs in the given user.
    
    def log_in(user)
    session[:user_id] = user.id
    end
    # Returns the current logged-in user (if any).
    
    def current_user
    @current_user ||= User.find_by(id: session[:user_id])
    end
    end
    
  21. 根據user有沒有登入,決定要去哪一個頁面。 xdite的建議

    <% if logged_in? %>
    # Links for logged-in users
    <% else %>
    # Links for non-logged-in-users
    
    <% end %>
    
  22. logged_in?會回傳true or false。!current_user.nil?如果current_user不是nil。
    logged_in? boolean method:
    A user is logged in if there is a current user in the session, i.e., if current_user is not nil. Checking for this requires the use of the “not” operator (Section 4.2.3), written using an exclamation point ! and usually read as “bang”. The resulting logged_in? method appears in Listing 8.15.

    app/helpers/sessions_helper.rb
    module SessionsHelper
    # Logs in the given user.
    
    def log_in(user)
    session[:user_id] = user.id
    end
    # Returns the current logged-in user (if any).
    
    def current_user
    @current_user ||= User.find_by(id: session[:user_id])
    end
    # Returns true if the user is logged in, false otherwise.
    
    def logged_in?
    !current_user.nil?
    end
    end
    
  23. dropdowndropdown-menu是 Bootstrap 的功能,要在application.js加上//= require bootstrap才會動。
    As part of including the new links into the layout, Listing 8.16 takes advantage of Bootstrap’s ability to make dropdown menus.8 Note in particular the inclusion of the special Bootstrap CSS classes such as dropdown, dropdown-menu, etc. To activate the dropdown menu, we need to include Bootstrap’s custom JavaScript library in the Rails asset pipeline’s application.js file, as shown in Listing 8.17.

    app/assets/javascripts/application.js
     //= require jquery
    //= require jquery_ujs
    //= require bootstrap
    //= require turbolinks
    //= require_tree .
    
  24. has_secure_password是rails 內建的method,加到User.rb之後,就可以有hashed password_digest attribute,password and password_confirmationauthenticate method。
    The ability to save a securely hashed password_digest attribute to the database
    A pair of virtual attributes18 (password and password_confirmation), including presence validations upon object creation and a validation requiring that they match
    An authenticate method that returns the user when the password is correct (and false otherwise)

  25. 登出,log_out,用destroy,只會在一個地方用到,在sessions_helper.rb定義一個log_out method

    app/helpers/sessions_helper.rb
    module SessionsHelper
    # Logs in the given user.
    
    def log_in(user)
    session[:user_id] = user.id
    end
    # Logs out the current user.
    
    def log_out
    session.delete(:user_id)
    @current_user = nil
    end
    end
    
    app/controllers/sessions_controller.rb
    class SessionsController < ApplicationController
    def new
    end
    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
    def destroy
    log_out
    redirect_to root_url
    end
    end
    
  26. remember token: 用cookies method產生永久性cookiesremember digest:授權給remember token
    remember token appropriate for creating permanent cookies using the cookies method, together with a secure remember digest for authenticating those tokens.

  27. remember token是隨機產生的string,會被保留在瀏覽器裡面,瀏覽器裡面還會有被加密過的使用者id
    hash過的remember token則會存在資料庫裡面,就變成remember digist
    當接收到瀏覽器裡面的cookie時,cookie內含的使用者id,將被用來在資料庫中找到相同的使用者
    然後cookie裡面的remember token跟資料庫裡面的remember digist(也就是加密過的remember token)做比較。
    Create a random string of digits for use as a remember token.
    Place the token in the browser cookies with an expiration date far in the future.
    Save the hash digest of the token to the database.
    Place an encrypted version of the user’s id in the browser cookies.
    When presented with a cookie containing a persistent user id, find the user in the database using the given id, and verify that the remember token cookie matches the associated hash digest from the database.

  28. 使用Rails內建的SecureRandom moduleurlsafe_base64 method,去產生long random string去當作remember token
    The urlsafe_base64 method from the SecureRandom module in the Ruby standard library fits the bill

  29. 如果沒有用self的話,會產生local variable
    self還可以定義成userattribute->remember_token (attr_accessor :remember_token)。
    User的attribute只有remember_digest(資料庫內),並沒有remember_token(因為只需要存在瀏覽器內,不需要存在資料庫),但是我們又需要用到remember_token,所以我們要用self去產生remember_token這個"attribute"。
    (意思好像是,self可以無中生有出一個暫時的attribute)
    update_attributes可以一次更新很多個attributes,
    update_attribute一次只能更新一個attribute,但是可以避免產生錯誤訊息。
    (Listing 8.18:new_token這個method不需要object,所以直接弄成Class Method)
    (Remembering users involves creating a remember token and saving the digest of the token to the database. We’ve already defined a digest method for use in the test fixtures (Listing 8.18), and we can use the results of the discussion above to create a new_token method to create a new token. As with digest, the new token method doesn’t need a user object, so we’ll make it a class method.19 The result is the User model shown in Listing 8.31.)

    def User.new_token
    SecureRandom.urlsafe_base64
    end
    

    Our plan for the implementation is to make a user.remember method that associates a remember token with the user and saves the corresponding remember digest to the database. Because of the migration in Listing 8.30, the User model already has a remember_digest attribute, but it doesn’t yet have a remember_token attribute. We need a way to make a token available via user.remember_token (for storage in the cookies) without storing it in the database. We solved a similar issue with secure passwords in Section 6.3, which paired a virtual password attribute with a secure password_digest attribute in the database. In that case, the virtual password attribute was created automatically by has_secure_password, but we’ll have to write the code for a remember_token ourselves. The way to do this is to use attr_accessor to create an accessible attribute, which we saw before in Section 4.4.5:
    Note the form of the assignment in the first line of the remember method. Because of the way Ruby handles assignments inside objects, without self the assignment would create a local variable called remember_token, which isn’t what we want. Using self ensures that assignment sets the user’s remember_token attribute. (Now you know why the before_save callback from Listing 6.31 uses self.email instead of just email.) Meanwhile, the second line of remember uses the update_attribute method to update the remember digest. (As noted in Section 6.1.5, this method bypasses the validations, which is necessary in this case because we don’t have access to the user’s password or confirmation.)

  30. (1)用 SecureRandom.urlsafe_base64先產生一個夠長的亂數,放到remember_token裡面。
    (2)再用digestremember_token加密。
    (3)然後把加密過的內容存到資料庫的:remember_digest裡面。

    app/models/user.rb
    class User < ActiveRecord::Base
    attr_accessor :remember_token
    has_secure_password
    validates :password, presence: true, length: { minimum: 6 }
    # 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
    (1)# Returns a random token.
    
    def User.new_token
    SecureRandom.urlsafe_base64
    end
    # Remembers a user in the database for use in persistent sessions.
    
    def remember
    (2)self.remember_token = User.new_token
    (3)update_attribute(:remember_digest, User.digest(remember_token))
    end
    end
    
  31. cookie method:是一個hash,分兩個部分:一個是值,一個是到期日。
    A cookie consists of two pieces of information, a value and an optional expires date. For example, we could make a persistent session by creating a cookie with value equal to the remember token that expires 20 years from now:

    cookies[:remember_token] = { value:   remember_token,
                             expires: 20.years.from_now.utc }
    
  32. 20年可以寫成permanent
    permanent method to implement it, so that we can simply writecookies.permanent[:remember_token] = remember_token

  33. cookies[:user_id] = user.id需要加密成:cookies.signed[:user_id] = user.id。(cookie本身可能沒有加密的功能,所以還要加上signed才能加密)再加上時間會變成:cookies.permanent.signed[:user_id] = user.id
    Because it places the id as plain text, this method exposes the form of the application’s cookies and makes it easier for an attacker to compromise user accounts. To avoid this problem, we’ll use a signed cookie, which securely encrypts the cookie before placing it on the browser:

  34. User.find_by(id: cookies.signed[:user_id])之後的頁面就可以用這個來叫出user。
    After the cookies are set, on subsequent page views we can retrieve the user with code like User.find_by(id: cookies.signed[:user_id])where cookies.signed[:user_id] automatically decrypts the user id cookie. We can then use bcrypt to verify that cookies[:remember_token] matches the remember_digest generated in Listing 8.32. (In case you’re wondering why we don’t just use the signed user id, without the remember token, this would allow an attacker with possession of the encrypted id to log in as the user in perpetuity. In the present design, an attacker with both cookies can log in as the user only until the user logs out.)

  35. secure password source code裡面:, BCrypt::Password.new(password_digest) == unencrypted_password,我們可以轉換成BCrypt::Password.new(remember_digest) == remember_token
    If you think about it, this code is really strange: it appears to be comparing a bcrypt password digest directly with a token, which would imply decrypting the digest in order to compare using ==. But the whole point of using bcrypt is for hashing to be irreversible, so this can’t be right.

  36. bcrypt gem 裡面的 ==已經被重新定義成BCrypt::Password.new(remember_digest).is_password?(remeing redef
    Indeed, digging into the source code of the bcrypt gem verifies that the comparison operator == is being redefined, and under the hood the comparison above is equivalent to the following: BCrypt::Password.new(remember_digest).is_password?(remember_token)Instead of ==, this uses the boolean method is_password? to perform the comparison. Because its meaning is a little clearer, we’ll prefer this second comparison form in the application code.

  37. user.rb加入authenticated?(remember_token)之後,就可以remember user放到sessions_controller.rb裡面去了。

    app/models/user.rb
    def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
    end
    end
    
    app/controllers/sessions_controller.rb
    class SessionsController < ApplicationController
    def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      remember user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
    end
    end
    
  38. 整個過程好像是:先到sessions_controller.rb,create時會遇到remember user,然後會跑到sessions_helper.rbremember(user),第一個步驟是回到user.rbdef remember去產生self.remember_token = User.new_tokenupdate_attribute(:remember_digest, User.digest(remember_token)),把資料庫內的:remember_digest產生出來後,就會繼續第二個步驟去產生cookies,也就是cookies.permanent.signed[:user_id] = user.idcookies.permanent[:remember_token] = user.remember_token

  39. 如果有session[:user_id]就用session[:user_id]登入。不然,就去找cookies[:user_id],改用cookies[:user_id]登入。
    In the case of persistent sessions, we want to retrieve the user from the temporary session if session[:user_id] exists, but otherwise we should look for cookies[:user_id] to retrieve (and log in) the user corresponding to the persistent session. We can accomplish this as follows:

    app/helpers/sessions_helper.rb
    if session[:user_id]
    @current_user ||= User.find_by(id: session[:user_id])
    elsif cookies.signed[:user_id]
    user = User.find_by(id: cookies.signed[:user_id])
    if user && user.authenticated?(cookies[:remember_token])
    log_in user
    @current_user = user
    end
    end
    
  40. 登入之後,cookies要等20年才會失效的解決方法是:先在user.rb設定,將:remember_digest更新為nil,然後在sessions_helper.rb裡面就可以用forget去登出。

    app/models/user.rb
    def forget
    update_attribute(:remember_digest, nil)
    end
    
    module SessionsHelper
    # Logs in the given user.
    def log_in(user)
    session[:user_id] = user.id
    end
    # Forgets a persistent session.
    def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
    end
    # Logs out the current user.
    def log_out
    forget(current_user)
    session.delete(:user_id)
    @current_user = nil
    end
    end
    
  41. 兩個問題待解:
    開多個視窗的時候,在其中一個登出,然後到另一個視窗要登出的時候,就會出現錯誤訊息 => 必須去檢查有沒有current_user。
    登出第一個視窗(Firefox),可是還沒登出第二個視窗(Chrome),就把第二個視窗關掉又重開:第一個視窗"登出"後,會把資料庫裡面的remember digest刪除。當把第二個視窗關掉的時候,因為只是關掉,不是登出,所以只會刪除session[:user_id],而cookies.signed[:user_id]還會存在,可是因為remember digest已經被刪掉了,所以會拋出錯誤訊息。 => To fix this, we want authenticated? to return false instead.

    app/controllers/sessions_controller.rb
    class SessionsController < ApplicationController
    def destroy
    log_out if logged_in?
    redirect_to root_url
    end
    end
    
    app/models/user.rb
    class User < ActiveRecord::Base
    # Returns true if the given token matches the digest.
    
    def authenticated?(remember_token)
    return false if remember_digest.nil?
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
    end
    def forget
    update_attribute(:remember_digest, nil)
    end
    end
    
  42. return false可以直接跳過後面要執行的code。
    This uses the return keyword to return immediately if the remember digest is nil, which is a common way to emphasize that the rest of the method gets ignored in that case. The equivalent code

    if remember_digest.nil?
    false
    else
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
    end
    
  43. view作好之後,當送出form的時候,會送出去:params[:session][:remember_me],因此可以寫成:

    if params[:session][:remember_me] == '1'
    remember(user)
    else
    forget(user)
    end
    

    或:params[:session][:remember_me] == '1' ? remember(user) : forget(user)
    更新到這邊:

    app/controllers/sessions_controller.rb
    class SessionsController < ApplicationController
    def new
    end
    def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
    log_in user
    params[:session][:remember_me] == '1' ? remember(user) : forget(user)
    redirect_to user
    else
    flash.now[:danger] = 'Invalid email/password combination'
    render 'new'
    end
    end
    def destroy
    log_out if logged_in?
    redirect_to root_url
    end
    end
    
  44. 這邊的運作流程是:使用者填寫登入表單後,會將params送到app/controllers/sessions_controller.rb,驗證user && user.authenticate(params[:session][:password])後之,就會把使用者登入,如果有勾選remember的checkbox的時候,瀏覽器會送出:paparams[:session][:remember_me] == '1',然後就會有remember(user),然後會由app/helpers/sessions_helper.rb去產生user.remembercookies.permanent.signed[:user_id] = user.idcookies.permanent[:remember_token] = user.remember_token,其中的user.remember會去執行app/models/user.rbremember method,去產生update_attribute(:remember_digest, User.digest(remember_token))

  45. 我猜的:如果沒有勾remember me,會執行forget(user),就會變成update_attribute(:remember_digest, nil),所以使用者就不能夠使用資料庫裡面的:remember_digest去作永久性的登入。

 
over 2 years ago

九個適應性設計小撇步,把你的網站打造成變形金剛!(上篇)

  1. ul 設定clearfix,然後設定text-align:center;
  2. li 設定成inline-block,然後不要有float:left;

置中排列的小秘技-神奇的 margin: auto 屬性

  1. 使用 display: block 時-固定寬度加上 margin: auto 屬性
  2. 使用 display: inline-block 時-在外層設定 text-align: center
  3. 使用絕對定位時-margin: auto 加上左右定位

寫給不是設計師的——漸層配色(上)

pixelmator
[筆記] 基本RWD版型設計

不重要的圖片放在Background:網友不會點選到的圖片,在CSS中用background-image來呈現,而不要在網頁中用img屬性。

aside和article這兩個區塊是並排的,要做到這個功能,我們可以用float來達到,我們讓aside和article這兩個區塊都是float:left。

最後有一個很重要的動作,是因為aside和article這兩個區塊都浮起來,所以下面的footer會趁虛而入,這時候在footer的區塊,我們要使用clear這個指令(clear:both)把float造成的效果清除掉

關於水平置中
如果是區塊要水平置中,一般使用的是margin: auto這樣的指令
但有些時候雖然是區塊,卻還是沒辦法置中,這時候要注意是不是因為我們沒有給它寬度,當沒有給它寬度的時候,預設就是和螢幕一樣寬,所以也沒辦法再將區塊進行置中的動作
如果是一個區塊內的內容要水平置中,這時候就要用到text-align:center這樣的指令。
在aside img這個css中,為了要讓它顯示出這個區塊,我們使用了display:block,但是這會使得在aside這個區塊中,無法使用text-align:center來置中圖片,因為img已經被當成一個區塊來處理的,這時候如果我們想要將aside img這張圖片置中的話,我們要直接在aside img這個區塊的css中加入margin:auto來達到水平置中的效果。

 
over 2 years ago
  1. gem environment
  2. 注意:INSTALLATION DIRECTORY 或 GEM PATHS
    • INSTALLATION DIRECTORY: /usr/local/lib/ruby/gems/2.2.0
    • GEM PATHS:
      • /usr/local/lib/ruby/gems/2.2.0
      • /Users/tienshunlo/.gem/ruby/2.2.0
      • /usr/local/Cellar/ruby/2.2.0/lib/ruby/gems/2.2.0
  3. 2個進到資料夾的方法

    • Finder,GO,貼上路徑
    • 在terminal 貼上 cd /usr/local/lib/ruby/gems/2.2.0
  4. /usr/local/rvm/gems/ruby-2.3.0/gems/materialize-sass-0.97.8/app/assets/stylesheets/materialize/components $

 
over 2 years ago
  1. pinterest
    <div class="item  ui-draggable ui-draggable-disabled ui-state-disabled" aria-disabled="true" style="top: 367px; left: 500px; visibility: visible;">
    <div class="Module Pin pinActionBarStickyContainer summary" data-component-type="0"> 
    <div class="pinWrapper ">
        <div class="bulkEditPinWrapper"></div>
      <div class="pinImageActionButtonWrapper">
                <div class="leftSideButtonsWrapper">
            <button class="Button Module ShowModalButton btn hasText isBrioFlat pinitLocalization primary primaryOnHover repinSmall rounded" data-element-type="0" type="button">
            <em></em>
            <span class="buttonText">Save</span>
            </button>
        </div>
        <div class="rightSideButtonsWrapper ">
            <button class="Button DropdownButton Module btn isBrioFlat rounded sendSmall sendPinGrid" data-element-type="98" type="button">
            <em></em>
            <span class="accessibilityText">Send</span>
          </button>
          <button class="Button LikeButton Module PinLikeButton btn isBrioFlat likeSmall rounded" data-element-type="1" data-source-interest-id="" type="button">
            <em></em>
            <span class="accessibilityText">Like</span></button>
         </div>
         <a class="Button Module NavigateButton borderless hasText pinNavLink navLinkOverlay" data-element-type="162" href="http://motto.time.com/4300164/karlie-kloss-time-100-video" rel="nofollow" type="button"> 
            <em></em>
          <span class="buttonText">Learn more at motto.time.com</span>
        </a>
        <div class="pinHolder">
            <a href="/pin/228487381074237671/" class="pinImageWrapper draggable" data-element-type="35" style="background: #010217;">
            <div class="pinDomain">motto.time.com</div>
            <div class="fadeContainer">
                <div class="pinImageDim">
                <div class="dimOverlay"></div>
                <div class="dimGradient"></div>
              </div>
                <div class="Image Module pinUiImage" style="width: 236px">
                    <div class="heightContainer" style="padding-bottom: 55.9322033%">
                    <img src="https://s-media-cache-ak0.pinimg.com/236x/4e/72/69/4e726941f3242f706ce13a2f6017bca8.jpg" class="pinImg fullBleed loaded fade" onload="P.lazy.onImageLoad(this)" alt="'I hope that never goes away'">
                  </div>
                            </div>
           </div>
         </a>
       </div>
      </div>
      <div class="pinMetaWrapper">
                <div class="richPinMeta">
            <div class="richPinMetaLink">
            <div class="richIconAndAttribution">
                <div class="Image Module richPinIcon unknownImage">
    <div class="heightContainer"> <img src="https://s-media-cache-ak0.pinimg.com/favicons/5cf0511058529f7b442f0c81952864d9b3c85c72ce49f89559805db7.png?51cbb4c5d6e916a05ae9db014f84c68a" data-load-state="pending" alt="Motto" data-src="https://s-media-cache-ak0.pinimg.com/favicons/5cf0511058529f7b442f0c81952864d9b3c85c72ce49f89559805db7.png?51cbb4c5d6e916a05ae9db014f84c68a"> </div> </div> <span class="richPinGridAttributionTitle">from Motto</span> </div> <h3 class="richPinGridTitle">Karlie Kloss Still Gets Nervous Having Her Picture Taken</h3> </div> </div> <div class="pinMeta "> <p class="pinDescription">'I hope that never goes away'</p> <div class="Module SocialIconsCounts"> <div class="pinSocialMeta"> <a class="socialItem" href="/pin/228487381074237671/activity/" data-element-type="174"> <em class="repinIconSmall"></em> <em class="socialMetaCount repinCountSmall">10 <span class="visuallyHidden">Repins</span>
    </em> </a> <a class="socialItem likes" href="/pin/228487381074237671/activity/" data-element-type="175"> <em class="likeIconSmall"></em> <em class="socialMetaCount likeCountSmall"> 1 <span class="visuallyHidden">Like</span>
    </em> </a> </div> </div> </div> </div> <div class="pinCredits"> <div class="pinCreditWrapper "> <button class="Button DropdownButton Module borderless hidePinInfo" type="button"> <em></em> <span class="accessibilityText">More information</span> </button> <div class="creditItem "> <a href="/timemagazine/picture-our-world/"> <div class="Image Module creditImg user unknownImage"> <div class="heightContainer"> <img src="https://s-media-cache-ak0.pinimg.com/avatars/time_magazine_1418314700_30.jpg" data-load-state="pending" alt="Picture Our World" data-src="https://s-media-cache-ak0.pinimg.com/avatars/time_magazine_1418314700_30.jpg"> </div> </div> <div class="creditName">TIME Magazine</div> <div class="creditTitle"> Picture Our World </div> </a> </div> </div> </div> </div> </div> </div>
    .GridItems.padItems>.item {
    margin: 14px 7px 0 7px;
    width: 236px;
    

2.instagram
@media (min-width: 640px)

._oyz6j {
padding-top: 60px;
}

._oyz6j {
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1;
margin: 0 auto;
max-width: 600px;
width: 100%;
}

width:185px;
height:185px;

  1. bridestory
    .wrapper {
    width: 1000px;
    margin-left: auto;
    margin-right: auto;
    }
    .box-item {
    width: 233px;
    margin-bottom: 20px;
    border: 1px solid #d9d8d8;
    -moz-border-radius: 2px;
    -webkit-border-radius: 2px;
    border-radius: 2px;
    background-color: #ffffff;
    }

  2. twitter
    .content-main {
    float: right;
    width: 590px;
    }

 
over 2 years ago

備註1:render跟redirect_to的差別:參考:Action Controller - 控制 HTTP 流程
備註2:_error_messages.html.erb
備註3:count,any?,empty?
備註4:pluralizeinclude ActionView::Helpers::TextHelper 才能用
備註5:flash[:success] = "Welcome to the Sample App!"
備註6:

<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>

備註7:alert-<%= message_type %>可以變成alert-success,Bootstrap CSS:success, info, warning, and danger。
備註8:只要在production.rb加上一行 config.force_ssl = true,就可以開啟SSL的功能了
備註9:puma gem

  1. 內建的method:debug <%= debug(params) if Rails.env.development? %>
  2. Rails environments: test, development(default的環境), production
    $ rails console
    Loading development environment
    >> Rails.env
    => "development"
    >> Rails.env.development?
    => true
    >> Rails.env.test?
    => false
    
    $ rails console test
    Loading test environment
    >> Rails.env
    => "test"
    >> Rails.env.test?
    => true
    
    server也可以改成production模式,不過會沒有production database
    $ rails server --environment production
    
    這樣才會有production database
      $ bundle exec rake db:migrate RAILS_ENV=production
    
    heroku的預設環境是production
      $ heroku run console
    >> Rails.env
    => "production"
    >> Rails.env.production?
    => true
    
  3. Listing 7.2,@import ,@mixin,@include
  4. 把用console做出來的東西清乾淨:$ bundle exec rake db:migrate:reset,記得要重新啟動server
  5. byebug gem: bundle install之後,在要檢查的controller的action加上debugger
    app/controllers/users_controller.rb
    class UsersController < ApplicationController
    def show
        @user = User.find(params[:id])
        debugger
    end
    def new
    end
    end
    
    byebug prompt: 可以輸入指令看有什麼東西,press Ctrl-D可以離開, 然後記得把debugger刪掉。
    (byebug) @user.name
    "Example User"
    (byebug) @user.email
    "example@railstutorial.org"
    (byebug) params[:id]
    "1"
    
  6. helper裡面定義的method是可以讓所有的view使用的,但是為了方便起見,還是把gravatar_for method放在跟Users Controller相關的helper檔案裡面 By default, methods defined in any helper file are automatically available in any view, but for convenience we’ll put the gravatar_for method in the file for helpers associated with the Users controller.
    app/helpers/users_helper.rb
     module UsersHelper
    # 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
    end
    
  7. form_for 是個helper method,主要是用在model(Active Record)裡的object,所以要先在controller裡面定義好@user。 We can accomplish this in Rails with the form_for helper method, which takes in an Active Record object and constructs a form using the object’s attributes. our first step is to create the User object required as an argument to form_for. The resulting @user variable definition appears in Listing 7.12.
  8. f 是被用來建構HTML表格的,主要是object(@user)的attributes(name, email, password, confirmation) we do need to know is what the f object does: when called with a method corresponding to an HTML form element—such as a text field, radio button, or password field—f returns code for that element specifically designed to set an attribute of the @user object. In other words,creates the HTML needed to make a labeled text field element appropriate for setting the name attribute of a User model.
  9. <%= f.email_field :email %> <%= f.password_field :password %> 會有特殊的格式出現,email會有@,密碼會出現星號。 password fields (type="password") obscure the input for security purposes, as seen in Figure 7.13. (The benefit of using an email field is that some systems treat it differently from a text field; for example, the code type="email" will cause some mobile devices to display a special keyboard optimized for entering email addresses.)
  10. Rails為每個input都弄了一個特別的name attribute (name="user[password]"), []裡面的東西(如password,其實是key,還有對應的value(用戶輸入的值))都會一起被丟到{}裡面去。 As we’ll see in Section 7.4, the key to creating a user is the special name attribute in each input:These name values allow Rails to construct an initialization hash (via the params variable) for creating users using the values entered by the user, as we’ll see in Section 7.3.
    <input id="user_name" name="user[name]" - - - />
    <input id="user_password" name="user[password]" - - - />
    
  11. form_for會為@user弄一個form tag出來,每個object都知道自己的class,然後也會知道這是一個新的object(因為在controller裡面會寫好@user = User.new),所以會自動在form內加上一個post method,post method是專門用來創建新物件的。 The second important element is the form tag itself. Rails creates the form tag using the @user object: because every Ruby object knows its own class (Section 4.4.1), Rails figures out that @user is of class User; moreover, since @user is a new user, Rails knows to construct a form with the post method, which is the proper verb for creating a new object (Box 3.2):<form action="/users" class="new_user" id="new_user" method="post">Here the class and id attributes are largely irrelevant; what’s important is action="/users" and method="post". Together, these constitute instructions to issue an HTTP POST request to the /users URL. We’ll see in the next two sections what effects this has.
  12. resources :users就可以確保路徑符合RESTful URLs規格。 Recall from Section 7.1.2 that adding resources :users to the routes.rb file (Listing 7.3) automatically ensures that our Rails application responds to the RESTful URLs from Table 7.1.In particular, it ensures that a POST request to /users is handled by the create action. Our strategy for the create action is to use the form submission to make a new user object using User.new, try (and fail) to save that user, and then render the signup page for possible resubmission.
  13. render 跟 redirect_to 的不同,參考:Action Controller - 控制 HTTP 流程 This listing includes a second use of the render method, which we first saw in the context of partials (Section 5.1.3); as you can see, render works in controller actions as well.
  14. render 'new' 只會展現new action的template,不會執行裡面的程式。參考:Action Controller - 控制 HTTP 流程
  15. 不同Controller的Template再加上Controller名稱,例如render "events/index"。參考:Action Controller - 控制 HTTP 流程
  16. "user[email]" [email]是user的一個attribute。
    <input id="user_email" name="user[email]" type="email" />
    
    with name "user[email]" is precisely the email attribute of the user hash.
  17. 直接完全接收用戶傳過來的值(params[:user])是非常危險的,尤其是用戶可以把自己設成 admin(admin=’1’)。 The reason is that initializing the entire params hash is extremely dangerous—it arranges to pass to User.new all data submitted by a user. In particular, suppose that, in addition to the current attributes, the User model included an admin attribute used to identify administrative users of the site. (We will implement just such an attribute in Section 9.4.1.) The way to set such an attribute to true is to pass the value admin=’1’ as part of params[:user], a task that is easy to accomplish using a command-line HTTP client such as curl. The result would be that, by passing in the entire params hash to User.new, we would allow any user of the site to gain administrative access by including admin=’1’ in the web request.
    @user = User.new(params[:user])    # Not the final implementation!
    文章裡面直接傳,就出現錯誤訊息了
    
  18. 避免的方法:舊Rails 是用attr_accessible Previous versions of Rails: used a method called attr_accessible in the model layer to solve this problem
  19. Rails 4.0:strong parameters指定哪些變數可以傳進來。 Rails 4.0: the preferred technique is to use so-called strong parameters in the controller layer
  20. params必須要有:user這個屬性,而我們允許:user的屬性包括name, email, password, 和 password confirmation。 In the present instance, we want to require the params hash to have a :user attribute, and we want to permit the name, email, password, and password confirmation attributes (but no others). We can accomplish this as follows:
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
    
  21. 因為 user_params只會在Users controller 內部存取,不能備外部用戶透過網路存取,所以要設成private。 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, as shown in Listing 7.17. (We’ll discuss private in more detail in Section 8.4.) @user = User.new(params[:user]) # Not the final implementation!會變成@user = User.new(user_params)
    app/controllers/users_controller.rb
    class UsersController < ApplicationController
    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
    end
    
  22. form-control:Bootstrap的格式 。
  23. errors.full_messages可以看到錯誤訊息。
  24. render an error-messages partial
    app/views/users/new.html.erb
    <%= render 'shared/error_messages' %>
    
    render跟redirect_to的差別:參考:Action Controller - 控制 HTTP 流程 render 其他controllerc/ action資料夾 /檔案
  25. shared/error_messages:convention:share資料夾(要自己mkdir)內放置的partial,可以被用在橫跨多個controller的view裡面。 This reflects the common Rails convention of using a dedicated shared/ directory for partials expected to be used in views across multiple controllers.
    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>
    
  26. 作一個error_messages.html.erb partial We then need to create the error_messages.html.erb partial file using our text editor as usual. The contents of the partial appear in Listing 7.19.
    $ mkdir app/views/shared
    
    app/views/shared/_error_messages.html.erb
     <% if @user.errors.any? %>
    <div id="errorexplanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.fullmessages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
    </div>
    <% end %>
    
  27. empty?,any?,兩者剛好相反 empty? returning true for an empty object and false otherwise. The any? method returning true if there are any elements present and false otherwise. any? is just the opposite of empty?.
    >> user.errors.empty?
    => false
    >> user.errors.any?
    => true
    
  28. error_message的CSS:#error_explanation。出現錯誤訊息時,會自動產生:.field_with_errors Note that Listing 7.19 includes the CSS id error_explanation for use in styling the error messages. (Recall from Section 5.1.2 that CSS uses the pound sign # to style ids.) In addition, after an invalid submission Rails automatically wraps the fields with errors in divs with the CSS class field_with_errors. These labels then allow us to style the error messages with the SCSS shown in Listing 7.20, which makes use of Sass’s @extend function to include the functionality of the Bootstrap class has-error.
    app/assets/stylesheets/custom.css.scss
    #error_explanation {
    color: red;
    ul {
    color: red;
    margin: 0 0 30px 0;
    }
    }
    .field_with_errors {
    @extend .has-error;
    .form-control {
    color: $state-danger-text;
    }
    }
    
  29. Note: Because both the presence validation and the has_secure_password validation catch the case of empty (nil) passwords, the signup form currently produces duplicate error messages when the user submits empty passwords. We could manipulate the error messages directly to eliminate duplicates, but luckily this issue will be fixed automatically by the addition of allow_nil: true in Section 9.1.4.
  30. redirect_to @userredirect_to user_url(@user) ,Rails知道這兩個是一樣的。
  31. The flash:出現後,在重新讀取頁面的時候就會消失了的功能。convention,用:success當作key。
    :success是key,將 "Welcome to the Sample App!"設定作value
    flash[:success] = "Welcome to the Sample App!"
    
    the flash, which we can treat like a hash. Rails adopts the convention of a :success key for a message indicating a successful result. 29.Flash會出現登入後的第一個頁面。 By assigning a message to the flash, we are now in a position to display the message on the first page after the redirect.a message that appears on the subsequent page and then disappears upon visiting a second page or on page reload.
  32. 把HTML和ERb結合看起來不是很漂亮的寫法。
    <% flash.each do |message_type, message| %>
    <div class="alert alert-<%= message_type %>"><%= message %></div>
    <% end %>
    
    alert-<%= message_type %>可以變成alert-success
    app/views/layouts/application.html.erb
     <!DOCTYPE html>
    <html>
      <% flash.each do |message_type, message| %>
        <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
      <% end %>
    </html>
    
  33. :success雖然是symbol,但是Rails 可以把它變成string "success" (The key :success is a symbol, but embedded Ruby automatically converts it to the string "success" before inserting it into the template.) 32.Bootstrap CSS:success, info, warning, and danger Using a different class for each key allows us to apply different styles to different kinds of messages. For example, in Section 8.1.4 we’ll use flash[:danger] to indicate a failed login attempt.10 (In fact, we’ve already used alert-danger once, to style the error message div in Listing 7.19.) Bootstrap CSS supports styling for four such flash classes (success, info, warning, and danger), and we’ll find occasion to use all of them in the course of developing the sample application.
  34. flash[:success] = "Welcome to the Sample App!" 會變成 <div class="alert alert-success">Welcome to the Sample App!</div>
  35. 全部就是這樣:
    app/views/layouts/application.html.erb
     <!DOCTYPE html>
    <html>
    <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <% flash.each do |message_type, message| %>
        <div class="alert alert-<%= message_type %>"><%= message %></div>
      <% end %>
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div> 
    </body>
    </html>
    
  36. data離開local端後,容易受到攻擊(hijacking),所以使用Secure Sockets Layer (SSL)來克服。 When submitting the signup form developed in this chapter, the name, email address, and password get sent over the network, and hence are vulnerable to intercept. This is a potentially serious security flaw in our application, and the way to fix it is to use Secure Sockets Layer (SSL)11 to encrypt all relevant information before it leaves the local browser.
  37. 只要在production.rb加上一行 config.force_ssl = true,就可以開啟SSL的功能了 Enabling SSL is as easy as uncommenting a single line in production.rb, the configuration file for production applications. As shown in Listing 7.27, all we need to do is set the config variable to force the use of SSL in production.
    config/environments/production.rb
     Rails.application.configure do
    # Force all access to the app over SSL, use Strict-Transport-Security,
    
    # and use secure cookies.
    
    config.force_ssl = true
    end
    
  38. Setting up a production site to use SSL involves purchasing and configuring an SSL certificate for your domain. That’s a lot of work, though, and luckily we won’t need it here: for an application running on a Heroku domain (such as the sample application), we can piggyback on Heroku’s SSL certificate. As a result, when we deploy the application in Section 7.5.2, SSL will automatically be enabled. (If you want to run SSL on a custom domain, such as www.example.com, refer to Heroku’s page on SSL.) 36.WEBrick是純Ruby webserver,流量大時跑不動。production的時候應該要改用Puma,可以處理比較大的流量。 By default, Heroku uses a pure-Ruby webserver called WEBrick, which is easy to set up and run but isn’t good at handling significant traffic. As a result, WEBrick isn’t suitable for production use, so we’ll replace WEBrick with Puma, an HTTP server that is capable of handling a large number of incoming requests.
  39. puma gem,in the :production,要記得執行bundle install To add the new webserver, we simply follow the Heroku Puma documentation. The first step is to include the puma gem in our Gemfile, as shown in Listing 7.28. Because we don’t need the Puma gem locally, Listing 7.28 puts it in the :production group.
    Gemfile
    source 'https://rubygems.org'
    group :production do
    gem 'pg',             '0.17.1'
    gem 'rails_12factor', '0.0.2'
    gem 'puma',           '3.1.0'
    end
    
    Because we configured Bundler not to install production gems (Section 3.1), Listing 7.28 won’t add any gems to the development environment, but we still need to run Bundler to update Gemfile.lock:$ bundle install
  40. 新增一個config/puma.rb The next step is to create a file called config/puma.rb and fill it with the contents of Listing 7.29. The code in Listing 7.29 comes straight from the Heroku documentation,12 and there is no need to understand it.
    config/puma.rb
     workers Integer(ENV['WEB_CONCURRENCY'] || 2)
    threads_count = Integer(ENV['MAX_THREADS'] || 5)
    threads threads_count, threads_count
    preload_app!
    rackup      DefaultRackup
    port        ENV['PORT']     || 3000
    environment ENV['RACK_ENV'] || 'development'
    on_worker_boot do
    # Worker specific setup for Rails 4.1+
    
    # See: https://devcenter.heroku.com/articles/
    
    # deploying-rails-applications-with-the-puma-web-server#on-worker-boot
    
    ActiveRecord::Base.establish_connection
    end
    
  41. 在root新增一個Procfile,告訴Heroku要在production的時候使用PUMA。 Finally, we need to make a so-called Procfile to tell Heroku to run a Puma process in production, as shown in Listing 7.30. The Procfile should be created in your application’s root directory (i.e., in the same location as the Gemfile).
    ./Procfile Defining a Procfile for Puma.
    web: bundle exec puma -C config/puma.rb
    
  42. With the production webserver configuration completed, we’re ready to commit and deploy:13
    $ bundle exec rake test
    $ git add -A
    $ git commit -m "Use SSL and the Puma webserver in production"
    $ git push
    $ git push heroku
    $ heroku run rake db:migrate
    
  43. The signup form is now live, and the result of a successful signup is shown in Figure 7.24. Note the presence of https:// and a lock icon in the address bar of Figure 7.24, which indicate that SSL is working.
  44. When deploying to Heroku, you may get a warning message like this one:
    ###### WARNING:
       You have not declared a Ruby version in your Gemfile.
       To set your Ruby version add this line to your Gemfile:
       ruby '2.1.5'
    
    Experience shows that, at the level of this tutorial, the costs associated with including such an explicit Ruby version number outweigh the (negligible) benefits, so you should ignore this warning for now. The main issue is that keeping your sample app and system in sync with the latest Ruby version can be a huge inconvenience,14 and yet it almost never makes a difference which exact Ruby version number you use. Nevertheless, you should bear in mind that, should you ever end up running a mission-critical app on Heroku, specifying an exact Ruby version in the Gemfile is recommended to ensure maximum compatibility between development and production environments.
 
over 2 years ago

備註1:每次開始前,記得先弄一個新的branch。$ git checkout -b modeling-users
重點1Controller的名字要加sModel的名字不加s
不過,migration的檔案裡面,create_table的時候,Table名稱都會加s。(create_table :users do |t|)
所以,在做relation的時候,就不用加s。(has_many :item)
(參考RailsFun.tw 新手教學_day2 HD 1:35:00的地方)
重點2migrate的兩種寫法:$ bundle exec rake db:migrate$ bundle exec rake db:rollback,會去修改這個檔案:development.sqlite3
重點3$ rails console --sandbox sandbox不會儲存資料到資料庫。heroku 也有sandbox $ heroku run console --sandbox
重點4:新增資料的2個方法:
- 2步驟:new + save :user = User.new + user.save
- 1步驟:user = User.create
重點5:搜尋的方法: findfind_byUser.find(3)User.find_by(email: "mhartl@example.com")
重點6:更新的2個方法:
- 方法1:只更新1個attribute,記得最後要有.save,不然資料庫不會更新。save完之後,update_at也會更新。
- 方法2:更新多個attribute,user.update_attributes,1個步驟更新,這個就不用.save了。
重點7:測試技巧:TDD適合用在測試Model。先以一個有效(valid)的Model Object開始,然後再把其中一個屬性(attribute)設成無效,然後讓測試顯示他是無效的,之後再寫CODE讓他變有效。 -> 一次測試一個屬性。
- 步驟1: 先以一個有效的Object開始: setup method每次都會先執行。
- 步驟2: 不同的test方法:$ bundle exec rake test:models$ bundle exec rake test:integration$ bundle exec rake test
- 步驟3: assert_not:測試object是不是無效的
- 步驟4: 用errors.full_messages可以知道哪個特定項目出現錯誤。有錯誤就不能存檔 >> user.save => false
- 步驟5: %w[]:變成字串的array。
- 步驟6: assert可以自訂錯誤訊息assert @user.valid?, "#{valid_address.inspect} should be valid"
- 步驟7: 要測試唯一性,首先要先把物件存到資料庫裡。@user.save
- 步驟8: 幫email加上indexuniqueness:we just need to enforce uniqueness at the database level as well as at the model level. Our method is to create a database index on the email column (Box 6.2), and then require that the index be unique.
-步驟9: callback:在某個特定時點先做某件事情。在存入資料庫之前,先把他們都弄成一樣的:before_save { self.email = email.downcase }這個方法叫做callback

重點8:validations(認可、確認),Rubular
- presence -> presence: true -> validates :name, presence: truevalidates(:name, presence: true)
- length -> length: { maximum: 50 } -> validates :name, presence: true, length: { maximum: 50 }
- format -> format: { with: VALID_EMAIL_REGEX }, VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
- uniqueness -> uniqueness: trueuniqueness: { case_sensitive: false }
- confirmation -> has_secure_password method

重點9has_secure_password method:有三個功能:
- a. 將password_digest hash後存到資料庫。password_digest會顯示hash過的亂數
- b. 一套屬性:password and password_confirmation。
- c. 認證功能:authenticate method:用戶輸入密碼後,回傳 true or false。如果密碼錯誤,authenticate會顯示false
- 步驟1:password_digest:Model要新增這個attribute後,has_secure_password才能運作 '$ rails generate migration add_password_digest_to_users password_digest:string'
- 步驟2:要記得db:migrate
- 步驟3:新增一個gem: gem 'bcrypt', '3.1.7'
- 步驟4:在User model裡面寫下: has_secure_password
- 步驟5:限制密碼的最小長度:validates :password, presence: true, length: { minimum: 6 }

重點10:Active Record validations allow us to place constraints on the data in our models.
重點11:Common validations include presence, length, and format.
重點12:Regular expressions are cryptic but powerful.
重點13:Defining a database index improves lookup efficiency while allowing enforcement of uniqueness at the database level.
重點14:We can add a secure password to a model using the built-in has_secure_password method.

  1. 資料庫儲存長期資料,並以Active Record這個library來存取資料庫
    The default Rails solution to the problem of persistence is to use a database for long-term data storage, and the default library for interacting with the database is called Active Record.

  2. Active Record: 提供可以直接創造、儲存與搜尋資料物件的一些method,而不必用到關聯資料庫的SQL語法。
    Active Record comes with a host of methods for creating, saving, and finding data objects, all without having to use the structured query language (SQL)2 used by relational databases.

  3. SQLite:Development。PostgreSQL (via Heroku) :Deployment

  4. Controller用複數,Model用單數。 (參考RailsFun.tw 新手教學_day2 HD 1:35:00的地方)
    $ rails generate controller Users new
    $ rails generate model User name:string email:string
    (Note that, in contrast to the plural convention for controller names, model names are singular: a Users controller, but a User model.)
    (JC建議在做relation的時候,就不用加s。(has_many :item))

  5. Here the table name is plural (users) even though the model name is singular (User), which reflects a linguistic convention followed by Rails: a model represents a single user, whereas a database table consists of many users.

  6. $ bundle exec rake db:migrate
    $ bundle exec rake db:rollback

  7. migration: it updated a file calleddevelopment.sqlite3 by creating a table users with columns id, name, email, created_at, and updated_at.

  8. $ rails console --sandbox

    Loading development environment in sandbox
    Any modifications you make will be rolled back on exit
    
  9. 新增的方法:2步驟 - new + save :user = User.new之後,雖然是valid的,但還沒有存到資料庫裡面去,所以id、created_at跟updated_at都是 nil,要user.save之後,created_at跟updated_at才會有日期。

    user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
    => #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com",
    created_at: nil, updated_at: nil>
    
     user.save
    (0.2ms)  begin transaction
    User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE LOWER("users".
    "email") = LOWER('mhartl@example.com') LIMIT 1
    SQL (0.5ms)  INSERT INTO "users" ("created_at", "email", "name", "updated_at)
    VALUES (?, ?, ?, ?)  [["created_at", "2014-09-11 14:32:14.199519"],
    ["email", "mhartl@example.com"], ["name", "Michael Hartl"], ["updated_at",
    "2014-09-11 14:32:14.199519"]]
    (0.9ms)  commit transaction
    
  10. 新增的方法:1步驟 - user = User.create = > user = User.new + user.save 。
    create之後,會回傳物件的值。

  11. The inverse of create is destroy。
    destroy之後,雖然還會存在記憶體內,但是已經從database裡面刪除了。

  12. 如何找到用戶: find 跟 find_by
    User.find(3)
    User.find_by(email: "mhartl@example.com")

  13. 更新的方法:單一attribute,記得最後要有.save,不然資料庫不會更新。save完之後,update_at也會更新。

    >> user           # Just a reminder about our user's attributes
    => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
    created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
    >> user.email = "mhartl@example.net"
    => "mhartl@example.net"
    >> user.save
    => true
    
  14. 更新的方法:multiple attributes = > user.update_attributes,1個步驟更新,這個就不用.save了。

    >> user.update_attributes(name: "The Dude", email: "dude@abides.org")
    => true
    >> user.name
    => "The Dude"
    >> user.email
    => "dude@abides.org"
    

    The update_attributes method accepts a hash of attributes, and on success performs both the update and the save in one step (returning true to indicate that the save went through). Note that if any of the validations fail, such as when a password is required to save a record (as implemented in Section 6.3), the call to update_attributes will fail. If we need to update only a single attribute, using the singular update_attribute bypasses this restriction:

  15. User validations:

    • name: should be non-blank,名字不能空白
    • email: should match the specific format characteristic of email addresses,e-mail有特定格式
    • email addresses: as unique usernames when users log in, we shouldn’t allow email duplicates in the database,email用作用戶名,必須要獨一無二
    • Active Record allows us to impose such constraints using validations
      • validating presence.
      • validating length.
      • validating format.
      • validating uniqueness.
      • validating confirmation.
  16. 測試技巧:TDD適合用在測試Model。先以一個有效(valid)的Model Object開始,然後再把其中一個屬性(attribute)設成無效,然後讓測試顯示他是無效的,之後再寫CODE讓他變有效。 -> 一次測試一個屬性。
    As noted in Box 3.3, test-driven development isn’t always the right tool for the job, but model validations are exactly the kind of features for which TDD is a perfect fit. It’s difficult to be confident that a given validation is doing exactly what we expect it to without writing a failing test and then getting it to pass.
    Our method will be to start with a valid model object, set one of its attributes to something we want to be invalid, and then test that it in fact is invalid. As a safety net, we’ll first write a test to make sure the initial model object is valid. This way, when the validation tests fail we’ll know it’s for the right reason (and not because the initial object was invalid in the first place).

  17. 先以一個有效的Object開始: setup method每次都會先執行。
    To write a test for a valid object, we’ll create an initially valid User model object @user using the special setup method (discussed briefly in the Chapter 3 exercises), which automatically gets run before each test.

    def setup
        @user = User.new(name: "Example User", email: "user@example.com")
    end
    
  18. 不同的test方法:

    $ bundle exec rake test:models
    

    Section 5.3.4

    $ bundle exec rake test:integration
    
    $ bundle exec rake test
    
  19. presence: true 不能空白的測試:
    assert_not:測試object是不是無效的
    assert_not method:check that the resulting User object is not valid.
    將@user.name設定成空白字串,但是因為還沒有規定name一定要有東西(presence),所以就算是空白字串,他也會是有效的。
    (@user.valid? 的結果會是true,可是assert_not是希望後面的變數是false,所以這個測試會fail掉)

    會fail
    @user.name = "     "
    assert_not @user.valid?
    

    加上

    app/models/user.rb
    class User < ActiveRecord::Base
      validates :name, presence: true
    end
    
    app/models/user.rb
    class User < ActiveRecord::Base
      validates(:name, presence: true)
    end
    

    在sandbox裡面測試,user.valid?就會變成falseassert_not @user.valid?就會變成true了。

    在sandbox裡面測試,`user.valid?`就會變成`false`,`assert_not @user.valid?`就會變成`true`了。
    $ rails console --sandbox
    >> user = User.new(name: "", email: "mhartl@example.com")
    >> user.valid?
    => false
    

    errors.full_messages可以知道哪個特定項目出現錯誤。

    用`errors.full_messages`可以知道哪個特定項目出現錯誤。
    user.errors.full_messages
    => ["Name can't be blank"]
    
    會不能存檔
    >> user.save
    => false
    

    不能空白的測試:

    app/models/user.rb
     class User < ActiveRecord::Base
    validates :name,  presence: true
    validates :email, presence: true
    end
    
  20. length: { maximum: 50 } 長度的測試。

    app/models/user.rb
     class User < ActiveRecord::Base
    validates :name,  presence: true, length: { maximum: 50 }
    validates :email, presence: true, length: { maximum: 255 }
    end
    
  21. %w[]:變成字串的array。
    %w[] technique for making arrays of strings, as seen in this console session:

    >> %w[foo bar baz]
    => ["foo", "bar", "baz"]
    >> addresses = %w[USER@foo.COM THE_US-ER@foo.bar.org first.last@foo.jp]
    => ["USER@foo.COM", "THE_US-ER@foo.bar.org", "first.last@foo.jp"]
    >> addresses.each do |address|
    ?>   puts address
    >> end
    USER@foo.COM
    THE_US-ER@foo.bar.org
    first.last@foo.jp
    
  22. assert可以自訂錯誤訊息

    assert @user.valid?, "#{valid_address.inspect} should be valid"
    
  23. VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
    參考最後的表格

  24. Rubular

  25. app/models/user.rb
    class User < ActiveRecord::Base
        VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
        validates :email, presence: true, length: { maximum: 255 },
                            format: { with: VALID_EMAIL_REGEX }
    end
    
  26. Uniqueness validation:這邊好難寫筆記
    要測試唯一性,首先要先把物件存到資料庫裡。@user.save

    test/models/user_test.rb
    require 'test_helper'
    class UserTest < ActiveSupport::TestCase
    def setup
                            @user = User.new(name: "Example User", email: "user@example.com")
    end
    .
    .
    .
    test "email addresses should be unique" do
                            duplicate_user = @user.dup
                            @user.save
                            assert_not duplicate_user.valid?
    end
    end
    
    app/models/user.rb
     class User < ActiveRecord::Base
        validates :name,  presence: true, length: { maximum: 50 }
        VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
        validates :email, presence: true, length: { maximum: 255 },
                            format: { with: VALID_EMAIL_REGEX },
                            uniqueness: true
    end
    
    app/models/user.rb
     class User < ActiveRecord::Base
    validates :name,  presence: true, length: { maximum: 50 }
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
    end
    
  27. 幫email加上index,uniqueness
    we just need to enforce uniqueness at the database level as well as at the model level. Our method is to create a database index on the email column (Box 6.2), and then require that the index be unique.

    $ rails generate migration add_index_to_users_email
    

    需要手動加上unique: true
    Unlike the migration for users, the email uniqueness migration is not pre-defined, so we need to fill in its contents with Listing 6.28.16

    db/migrate/[timestamp]_add_index_to_users_email.rb
    class AddIndexToUsersEmail < ActiveRecord::Migration
        def change
            add_index :users, :email, unique: true
        end
    end
    
  28. 要記得去調整fixtures檔案 test/fixtures/users.yml
    fixtures, which contain sample data for the test database. User fixtures were generated automatically in Listing 6.1, and as shown in Listing 6.29 the email addresses are not unique. (They’re not valid either, but fixture data doesn’t get run through the validations.)

  29. 我們的網站對大小寫不敏感,不過有些網站的資料庫會敏感。
    所以要在存入資料庫之前,先把他們都弄成一樣的:before_save { self.email = email.downcase }這個方法叫做callback
    Some database adapters use case-sensitive indices, considering the strings “Foo@ExAMPle.CoM” and “foo@example.com” to be distinct, but our application treats those addresses as the same.
    callback在某個特定時點先做某件事情。
    which is a method that gets invoked at a particular point in the lifecycle of an Active Record object.
    (where self refers to the current user), but inside the User model the self keyword is optional on the right-hand side:
    1跟2是可以的,3不行。
    We encountered this idea briefly in the context of reverse in the palindrome method (Section 4.4.2), which also noted that self is not optional in an assignment, so 3 wouldn’t work. (We’ll discuss this subject in more depth in Section 8.4.)
    第1. self.email = self.email.downcase
    第2. self.email = email.downcase
    第3. email = email.downcase

  30. has_secure_password method:

    class User < ActiveRecord::Base
    .
    .
    .
    has_secure_password
    end
    

    has_secure_password有三個功能:
    a. 將password_digesthash後存到資料庫。
    The ability to save a securely hashed password_digest attribute to the database
    b. 一套屬性:password and password_confirmation
    A pair of virtual attributes (password and password_confirmation), including presence valid ations upon object creation and a validation requiring that they match
    c. 認證功能:An authenticate method that returns the user when the password is correct (and false otherwise)

  31. password_digest:Model要新增這個attribute後,has_secure_password才能運作:

    $ rails generate migration add_password_digest_to_users password_digest:string
    
    db/migrate/[timestamp]_add_password_digest_to_users.rb
     class AddPasswordDigestToUsers < ActiveRecord::Migration
    def change
    add_column :users, :password_digest, :string
    end
    end
    

    要記得db:migrate

    $ bundle exec rake db:migrate
    
  32. 還要新增一個gem: gem 'bcrypt', '3.1.7'

  33. 然後在model裡面寫下:

    app/models/user.rb
    class User < ActiveRecord::Base
      before_save { self.email = email.downcase }
      validates :name, presence: true, length: { maximum: 50 }
      VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
      validates :email, presence: true, length: { maximum: 255 },
                        format: { with: VALID_EMAIL_REGEX },
                        uniqueness: { case_sensitive: false }
      has_secure_password
    end
    

    34.限制密碼的最小長度:validates :password, presence: true, length: { minimum: 6 }

    app/models/user.rb
    class User < ActiveRecord::Base
      before_save { self.email = email.downcase }
      validates :name, presence: true, length: { maximum: 50 }
      VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
      validates :email, presence: true, length: { maximum: 255 },
                        format: { with: VALID_EMAIL_REGEX },
                        uniqueness: { case_sensitive: false }
      has_secure_password
      validates :password, presence: true, length: { minimum: 6 }
    end
    
  34. password_digest會顯示hash過的亂數

    >> user = User.find_by(email: "mhartl@example.com")
    >> user.password_digest
    => "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQWITUYlG3XVy"
    
  35. 如果密碼錯誤,authenticate會顯示false

    >> user.authenticate("not_the_right_password")
    false
    >> user.authenticate("foobaz")
    >> user.authenticate("foobar")
    => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
    created_at: "2014-07-25 02:58:28", updated_at: "2014-07-25 02:58:28",
    password_digest: "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQW...">
    
  36. heroku 也有sandbox

    $ heroku run console --sandbox
    >> User.create(name: "Michael Hartl", email: "michael@example.com",
    ?>             password: "foobar", password_confirmation: "foobar")
    => #<User id: 1, name: "Michael Hartl", email: "michael@example.com",
    created_at: "2014-08-29 03:27:50", updated_at: "2014-08-29 03:27:50",
    password_digest: "$2a$10$IViF0Q5j3hsEVgHgrrKH3uDou86Ka2lEPz8zkwQopwj...">
    
Expression Meaning
/\A[\w+-.]+@[a-z\d-.]+.[a-z]+\z/i full regex
/ start of regex
\A match start of a string
[\w+-.]+ at least one word character, plus, hyphen, or dot
@ “at sign”
[a-z\d-.]+ at least one letter, digit, hyphen, or dot
. literal dot
[a-z]+ at least one letter
\z match end of a string
/ end of regex
i case-insensitive
 
over 2 years ago

備註1. 記得弄個新branch

$ git checkout master
$ git checkout -b filling-in-layout

備註2: Listing 5.1有兩個<div class="container">container可以用超過一次。
備註3: container 用於自適應且固定寬度的容器。
備註4: container-fluid 用於 100% 寬度的容器,橫跨可視區域的全部寬度。

app/views/layouts/application.html.erb
 <!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag 'application', media: 'all',
                                           'data-turbolinks-track' => true %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
    <%= csrf_meta_tags %>
    <!--[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>

備註5: 一次搞懂 Assets Pipeline
重點1: Bootstrap 是用Less CSS,可是Rails是用Sass,所以要靠bootstrap-sass把 Less 轉換成Sass
重點2: rails generate會為每個Controller產生對應的CSS檔
重點3: 自製的CSS檔裡面,要記得@import bootstrap-sprocketsbootstrap
重點4: Partial 檔案的命名,開頭要有底線(_),然後要放到app/views/layouts/(我好像都在放app/views/common/),在view裡面要用render去把它運算出來。
重點5: 我的專案裡面:
common裡面是放partial,譬如底線navbar.html,使用方式是<%= render 'layouts/navbar' %>
layout裡面是放controller要的,譬如dashboard.html,使用方式是 layout 'dashboard'
重點6: Sass = nesting + variables + mixins
重點7: Less: @Sass: $,不過bootstrap-sass gem會把Less轉換成Sass

  1. <div>就是 division:把文件區分成不同的區域。
    HTML5 新增<header>, <nav>, <section>,<footer>,其實也都是<div>
    The div tag is a generic division; it doesn’t do anything apart from divide the document into distinct parts. In older-style HTML, div tags are used for nearly all site divisions, but HTML5 adds the header, nav, and section elements for divisions common to many applications.

  2. <header> : 裡面的東西要在網頁的最上方
    header tag indicates elements that should go at the top of the page.

  3. <nav>其實沒啥用,只是會讓結構看起來比較清楚。
    The <nav> tag, though formally unnecessary here, is used to more clearly communicate the purpose of the navigation links.

  4. <%= yield %>: 會把每一頁的內容放到這裡來

    <div class="container">
        <%= yield %>
    </div>
    

    As before, the container class has special meaning to Bootstrap. As we learned in Section 3.4.3, the yield method inserts the contents of each page into the site layout.

  5. 因為是用image_tag helper,圖片要放在app/assets/images/,然後就可以直接寫檔名 rails.png,不需要寫路徑

    <%= link_to image_tag("rails.png", alt: "Rails logo"),
            'http://rubyonrails.org/' %>
    

    The second link_to shows off the image_tag helper, which takes as arguments the path to an image and an optional options hash, in this case setting the alt attribute of the image tag using symbols. For this to work, there needs to be an image called rails.png, which you should download from the Rails Tutorial website at http://railstutorial.org/rails.png and place in the app/assets/images/ directory.
    Because we used the image_tag helper in Listing 5.2, Rails will automatically find any images in the app/assets/images/ directory using the asset pipeline (Section 5.2).

  6. image_tag的功能:

    <img alt="Rails logo" src="/assets/rails-9308b8f92fea4c19a3a0d8385b494526.png" />
    

    圖片名稱會變數字。
    src的地方也不會顯示image這個資料夾,只有asset,這樣可以讓server讀得比較快。
    Here the string 9308b8f92fea4c19a3a0d8385b494526 (which will differ on your system) is added by Rails to ensure that the filename is unique, which causes browsers to load images properly when they have been updated (instead of retrieving them from the browser cache).
    Note that the src attribute doesn’t include images, instead using an assets directory common to all assets (images, JavaScript, CSS, etc.).
    On the server, Rails associates images in the assets directory with the proper app/assets/images directory, but as far as the browser is concerned all the assets look like they are in the same directory, which allows them to be served faster. Meanwhile, the alt attribute is what will be displayed if the page is accessed by a program that can’t display images (such as screen readers for the visually impaired).

  7. Bootstrap: bootstrap-sass gem
    Bootstrap 是用Less CSS,
    可是Rails是用Sass,
    所以要靠bootstrap-sass把 Less 轉換成Sass
    Our first step is to add Bootstrap, which in Rails applications can be accomplished with the bootstrap-sass gem, as shown in Listing 5.4. The Bootstrap framework natively uses the Less CSS language for making dynamic stylesheets, but the Rails asset pipeline supports the (very similar) Sass language by default (Section 5.2), so bootstrap-sass converts Less to Sass and makes all the necessary Bootstrap files available to the current application.8

  8. rails generate會為每個Controller產生對應的CSS檔,不過這樣很麻煩,所以可以自己弄一個CSS檔,放在app/assets/stylesheets/裡面。
    Although rails generate automatically creates a separate CSS file for each controller, it’s surprisingly hard to include them all properly and in the right order, so for simplicity we’ll put all of the CSS needed for this tutorial in a single file. The first step toward getting custom CSS to work is to create such a custom CSS file:

    $ touch app/assets/stylesheets/custom.css.scss
    
  9. app/assets/stylesheets/ 這裡面的CSS檔,都會被加到application.css,然後整個網站都會用到。
    custom.css.scss.css是指 CSS file, and the .scss是指Sassy CSS,用來處理Sass。
    is part of the asset pipeline (Section 5.2), and any stylesheets in this directory will automatically be included as part of the application.css file included in the site layout.
    Furthermore, the filename custom.css.scss includes the .css extension, which indicates a CSS file, and the .scss extension, which indicates a “Sassy CSS” file and arranges for the asset pipeline to process the file using Sass.

  10. 自製的CSS檔裡面,要記得@import bootstrap-sprocketsbootstrap
    Inside the file for the custom CSS, we can use the @import function to include Bootstrap (together with the associated Sprockets utility), as shown in Listing 5.5.9

    Listing 5.5: Adding Bootstrap CSS.
    app/assets/stylesheets/custom.css.scss
    @import "bootstrap-sprockets";
    @import "bootstrap";
    
  11. Partial 檔案的命名,開頭要有底線(_),然後要放到app/views/layouts/(我好像都在放app/views/common/),在view裡面要用render去把它運算出來。
    ps. 我的專案裡面:
    common裡面是放partial,譬如底線navbar.html,使用方式是<%= render 'layouts/navbar' %>
    layout裡面是放controller要的,譬如dashboard.html,使用方式是 layout 'dashboard'

    app/views/layouts/application.html.erb
     <!DOCTYPE html>
    <html>
    <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
    <%= render 'layouts/shim' %>
    </head>
    <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
    </div>
    </body>
    </html>
    

    a single call to a Rails helper called render
    this underscore is the universal convention for naming partials, and among other things makes it possible to identify all the partials in a directory at a glance.

  12. 5.2 Sass and the asset pipeline
    一次搞懂 Assets Pipeline
    Sprockets: 用來從你的 assets 路徑中打包壓縮你所有的 assets 後包裝成一個檔案

    • Assets 的結構
      1. app/assets(通常放置我們自己為了自己的程式所寫的 js、css 或是 images)
      2. lib/assets(通常是我們所使用的套件中去用到的 assets)
      3. vendor/assets(通常是放一些我們從別的地方借用的 assets,例如說一些 jQuery 的套件)
      4. 這三個目錄,在預設情況下這三個資料夾的東西是共通的(因為都會被打包成一個檔案),你可以把你的 rails app 跑起來後在 http://localhost:3000/assets/application.js 中看到你所有的 js 都在這支檔案中,css 同理亦然
      5. 在 terminal 中輸入 Rails.application.config.assets.paths 來查看所有的 assets 路徑。你可以發現,除了原本我們剛剛說的三個 assets 目錄之外,還出現了包含在我們 GemFile 中的 jquery,這代表你的 assets 現在也可以包成 gem 來用,
      6. 如果你有很多個 projects 常重複使用一些共通的 assets,不妨考慮包成 gem 來使用,方便又愉快。
    • Assets 的載入
      1. app/asset/javascripts/application.js 這支檔案為例,這是一支 manifest 檔案
      2. 主要用來告訴 Sprockets 說哪些檔案是要被載入最後要被包起來壓縮的,最後這支檔案裡面所有的東西就會被包成 application.js 這支檔案,也是我們 layout/application.html.erb 中的 javascript_include_tag 'application' 中的檔案
      3. require_tree . 表示是把三個 assets/javascript 目錄下的檔案或是子目錄內的檔案全部都包進來
  13. asset pipeline:convenience in development and efficiency in production.

  14. Sass = nesting + variables + mixins
    SCSS only adds features to CSS, rather than defining an entirely new syntax.

  15. css層級的用法: nesting

    <div class="center jumbotron">
    <h1></h1>
    <h2></h2>
    </div>
    
    .center {
    text-align: center;
    }
    .center h1 {
    margin-bottom: 10px;
    }
    
    .center {
    text-align: center;
    h1 {
    margin-bottom: 10px;
    }
    }
    
    <footer class="footer">
    <small>
    The <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
    by <a href="http://www.michaelhartl.com/">Michael Hartl</a>
    </small>
    <nav>
    <ul>
      <li><%= link_to "About",   about_path %></li>
      <li><%= link_to "Contact", contact_path %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
    </ul>
    </nav>
    </footer>
    
    footer {
    margin-top: 45px;
    padding-top: 5px;
    border-top: 1px solid #eaeaea;
    color: #777;
    a {
    color: #555;
    &:hover {
      color: #222;
    }
    }
    small {
    float: left;
    }
    ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
    }
    }
    
  16. Variables:bootstrap-sass gem會把Less轉換成Sass
    color: #777;
    $light-gray: #777;
    Less: @
    Sass: $
    Bootstrap page of Less variables. That page defines variables using Less, not Sass, but the bootstrap-sass gem provides the Sass equivalents.

17.

root_path -> '/'
root_url  -> 'http://www.example.com/'
get 'help' => 'static_pages#help'
help_path -> '/help'
help_url  -> 'http://www.example.com/help'