almost 2 years ago
  1. 每次都要新增一個branch
  2. 怎麼使用Amazon.com’s Simple Storage Service (S3)
    $ git checkout master
    $ git checkout -b user-microposts
    
  3. user:references這樣寫可以直接產生關聯,然後會有user_id (belongs_to :user會自動產生)

    $ rails generate model Micropost content:text user:references
    

    The generate command produces a migration to create a microposts table in the database (Listing 11.1); compare it to the analogous migration for the users table from Listing 6.2. The biggest difference is the use of references, which automatically adds a user_id column (along with an index and a foreign key reference)3 for use in the user/micropost association.

  4. 把micropost叫出來的時候,要按特定的user,還要有特定的順序,所以:user_id:create_at都要記得上index
    Because we expect to retrieve all the microposts associated with a given user id in reverse order of creation, Listing 11.1 adds an index (Box 6.2) on the user_id and created_at columns:

    add_index :microposts, [:user_id, :created_at]
    

    By including both the user_id and created_at columns as an array, we arrange for Rails to create a multiple key index, which means that Active Record uses both keys at the same time.

  5. 要記得rake db:migrate,資料庫才會被更新。
    With the migration in Listing 11.1, we can update the database as usual:

    $ bundle exec rake db:migrate
    
  6. assert @micropost.valid?答案是valid,所以assert會是true (因為都還沒有設任何的validation,要到micropost.rb去設validates :user_id, presence: true)

    test "should be valid" do
    assert @micropost.valid?
    end
    
  7. 'assert_not @micropost.valid?' 答案是valid,所以assert_not會是false (因為都還沒有設任何的validation,要到micropost.rb去設validates :user_id, presence: true)

    test "user id should be present" do
    @micropost.user_id = nil
    assert_not @micropost.valid?
    end
    
  8. assert_not @micropost.valid?也會是valid,雖然@micropost.content是空白的,但因為沒有validation,所以還是valid。

    test "content should be present" do
    @micropost.content = "   "
    assert_not @micropost.valid?
    end
    
  9. assert_not @micropost.valid?也是valid,因為還沒有設定少於140個字母。

    test "content should be at most 140 characters" do
    @micropost.content = "a" * 141
    assert_not @micropost.valid?
    end
    
  10. 要這樣設才有會讓7跟8是not valid,然後才會變true:validates :content, presence: true, length: { maximum: 140 }

  11. 第二個寫法才是正確的,可以順便產生user_id

    Micropost.create
    Micropost.create!
    Micropost.new
    
    user.microposts.create
    user.microposts.create!
    user.microposts.build
    

    These latter methods constitute the idiomatically correct way to make a micropost, namely, through its association with a user. When a new micropost is made in this way, its user_id is automatically set to the right value.

  12. 這邊又要去想一下newbuild的差別
    (As with new, build returns an object in memory but doesn’t modify the database.)

  13. default_scope可以將資料從資料庫按照我們所需要的順序叫出來
    We’ll get the test to pass using a Rails method called default_scope, which among other things can be used to set the default order in which elements are retrieved from the database

  14. 'order'是default_scope的一個argument
    To enforce a particular order, we’ll include the order argument in default_scope, which lets us order by the created_at column as follows:order(:created_at)

  15. 這樣就可以由新到舊的。
    default_scope -> { order(created_at: :desc) }
    Listing 11.16 introduces the “stabby lambda” syntax for an object called a Proc (procedure) or lambda, which is an anonymous function (a function created without a name). The stabby lambda -> takes in a block (Section 4.3.2) and returns a Proc, which can then be evaluated with the call method. We can see how it works at the console:
    Proc(procedure)跟lambda是匿名函數(不需要取名字),然後可以用call 去呼叫。

    >> -> { puts "foo" }
    => #<Proc:0x007fab938d0108@(irb):1 (lambda)>
    >> -> { puts "foo" }.call
    foo
    => nil
    
  16. dependent: :destroy: user被刪除的時候,user的micropost也要被刪除,所以是要放在user.rb
    has_many :microposts, dependent: :destroy

  17. render @users這樣的寫法,rails會自己去找_user.html.erb的partial。所以我們可以用render @microposts去找 _micropost.html.erb 的partial

    Section 9.3.5
    <ul class="users">
    <%= render @users %>
    </ul>
    

    automatically renders each of the users in the @users variable using the _user.html.erb partial. We’ll define an analogous _micropost.html.erb partial so that we can use the same technique on a collection of microposts as follows:

    <ol class="microposts">
    <%= render @microposts %>
    </ol>
    
    app/views/microposts/_micropost.html.erb (Listing 11.21)
    <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>
    
  18. <%= will_paginate @microposts %>will_paginate如果是放在User Controller,會自動去找@user這個變數,如果是放在User Controller,但是又希望他去找其他的變數,如@microposts,那就要把變數寫在後面。如<%= will_paginate @microposts %>。要記得定義@microposts,@microposts = @user.microposts.paginate(page: params[:page])
    As before, we’ll use the will_paginate method:<%= will_paginate @microposts %>
    If you compare this with the analogous line on the user index page, Listing 9.41, you’ll see that before we had just<%= will_paginate %>
    This worked because, in the context of the Users controller, will_paginate assumes the existence of an instance variable called @users (which, as we saw in Section 9.3.3, should be of class ActiveRecord::Relation). In the present case, since we are still in the Users controller but want to paginate microposts instead, we’ll pass an explicit @microposts variable to will_paginate. Of course, this means that we will have to define such a variable in the user show action (Listing 11.22).

    (Listing 11.22)
     class UsersController < ApplicationController
    .
    .
    .
    def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page])
    end
    .
    .
    .
    end
    
  19. count的好處:不會把所有資料從資料庫叫出來,然後再去計算長度,這樣太沒效率。'count'會直接在資料庫裡面先算好數量之後。
    更進階的可以使用counter cache
    As with paginate, we can use the count method through the association. In particular, count does not pull all the microposts out of the database and then call length on the resulting array, as this would become inefficient as the number of microposts grew. Instead, it performs the calculation directly in the database, asking the database to count the microposts with the given user_id (an operation for which all databases are highly optimized). (In the unlikely event that finding the count is still a bottleneck in your application, you can make it even faster using a counter cache.)

  20. resources :microposts, only: [:create, :destroy] : 不需要new跟dit,只要create跟destroy因為大部份的動作都要在Profile跟Home執行。
    the interface to the Microposts resource will run principally through the Profile and Home pages, so we won’t need actions like new or edit in the Microposts controller; we’ll need only create and destroy.

  21. logged_in_user這個filter本來是放在User Controller裡面,可是因為現在Microposts Controller也要用,所以把他拿到Application controller裡面去,所有的Controller都可以用了!
    Writing the application code needed to get the tests in Listing 11.30 to pass requires a little refactoring first. Recall from Section 9.2.1 that we enforced the login requirement using a before filter that called the logged_in_user method (Listing 9.12). At the time, we needed that method only in the Users controller, but now we find that we need it in the Microposts controller as well, so we’ll move it into the Application controller, which is the base class of all controllers (Section 4.4.4). The result appears in Listing 11.31.

    app/controllers/application_controller.rb
    class ApplicationController < ActionController::Base
    protect_from_forgery with: :exception
    include SessionsHelper
    private
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
    end
    
  22. 如果登入,就顯示app/views/shared/_user_info.html.erbapp/views/shared/_micropost_form.html.erb

    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 %>
    <% end %>
    
    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 %>
    
  23. _micropost_form.html.erb這個partial是在home.html.erb裡面,所以裡面的@micropost要在static_pages_controller.rb裡面定義,不過這個變數@micropost還是會送到Micropost Controller的create method去,所以Create那邊要接好micropoat_params

    app/controllers/static_pages_controller.rb
     class StaticPagesController < ApplicationController
    def home
    @micropost = current_user.microposts.build if logged_in?
    end
    end
    
    app/controllers/microposts_controller.rb (Listing 11.34)
    class MicropostsController < ApplicationController
    before_action :logged_in_user, only: [:create, :destroy]
    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
    end
    
  24. 每個user都要有feed,所以會把feed method放在User Controller裡。Micropost.where("user_id = ?", id)
    我的理解:在User的Profile裡,如果要叫出來一個User所有的microposts的話,因為有關聯的關係,所以可以打成@user.microposts,不過為了增加易讀性,所以要把它包裝在feed method裡面,就可以改寫成@user.feed。
    Since each user should have a feed, we are led naturally to a feed method in the User model, which will initially just select all the microposts belonging to the current user. We’ll accomplish this using the where method on the Micropost model (seen briefly before in Section 10.5), as shown in Listing 11.44.10

    app/models/user.rb (Listing 11.44)
     class User < ActiveRecord::Base
    def feed
    Micropost.where("user_id = ?", id)
    end
    end
    

    Alert readers might note at this point that the code in Listing 11.44 is essentially equivalent to writing

    def feed
    microposts
    end
    

    We’ve used the code in Listing 11.44 instead because it generalizes much more naturally to the full status feed needed in Chapter 12.

  25. ("user_id = ?", id)這樣的寫法是為了防止SQL injection。
    ensures that id is properly escaped before being included in the underlying SQL query, thereby avoiding a serious security hole called SQL injection. The id attribute here is just an integer (i.e., self.id, the unique ID of the user), so there is no danger of SQL injection in this case, but always escaping variables injected into SQL statements is a good habit to cultivate.

  26. 接下來要開始用partial

  27. 在首頁的地方,<%= render 'shared/feed' %> 將顯示_feed.html.erb這個partial。

    app/views/static_pages/home.html.erb (Listing 11.47)
     <% 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 class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
    </div>
    <% else %>
    .
    .
    .
    <% end %>
    
  28. _feed.html.erb這個partial,又會顯示@feed_items這個變數,因為這個變數的class是Micropost,所以又會自動去找_micropost.html.erb這個partial。

    app/views/shared/_feed.html.erb (Listing 11.46)
     <% if @feed_items.any? %>
    <ol class="microposts">
    <%= render @feed_items %>
    </ol>
    <%= will_paginate @feed_items %>
    <% end %>
    

    Here Rails knows to call the micropost partial because each element of @feed_items has class Micropost. This causes Rails to look for a partial with the corresponding name in the views directory of the given resource:app/views/microposts/_micropost.html.erb

    app/views/microposts/_micropost.html.erb(Listing 11.21)
    <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>
    
  29. 如果送出micropost失敗的話,會break。如果輸入一個空的rray好像也沒用。
    At this point, creating a new micropost works as expected, as seen in Figure 11.15. There is one subtlety, though: on failed micropost submission, the Home page expects an @feed_items instance variable, so failed submissions currently break. The easiest solution is to suppress the feed entirely by assigning it an empty array, as shown in Listing 11.48. (Unfortunately, returning a paginated feed doesn’t work in this case. Implement it and click on a pagination link to see why.)

    app/controllers/microposts_controller.rb(Listing 11.48)
    class MicropostsController < ApplicationController
    before_action :logged_in_user, only: [:create, :destroy]
    def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      @feed_items = []
      render 'static_pages/home'
    end
    end
    def destroy
    end
    private
    def micropost_params
      params.require(:micropost).permit(:content)
    end
    end
    
  30. <%= render @feed_items %><%= render @microposts %>,這邊的@變數裡面應該都有複數的東西,可是為什麼不必用到each?
    原來在Ch9的地方有講到:
    9.3.5 Partial refactoring

    app/views/users/index.html.erb(Listing 9.46)
    <% provide(:title, 'All users') %>
    <h1>All users</h1>
    <%= will_paginate %>
    <ul class="users">
    <% @users.each do |user| %>
    <%= render user %>
    <% end %>
    </ul>
    <%= will_paginate %>
    
    app/views/users/index.html.erb(Listing 9.48)
     <% provide(:title, 'All users') %>
    <h1>All users</h1>
    <%= will_paginate %>
    <ul class="users">
    <%= render @users %>
    </ul>
    <%= will_paginate %>
    

    Here Rails infers that @users is a list of User objects; moreover, when called with a collection of users, Rails automatically iterates through them and renders each one with the _user.html.erb partial. The result is the impressively compact code in Listing 9.48.
    雖然Rails會把@user當作User的列表,一個集合,但是在patial的情況下,他會一個一個顯示出來。

  31. 點了delete之後,micropost的id會被送到Micropost Controllerdestroy method去。
    因為有設before_action的關係,他會先去執行correct_user method
    @micropost = current_user.microposts.find_by(id: params[:id])
    這邊的id: params[:id]micropost的id
    we’ll find the micropost through the association, which will automatically fail if a user tries to delete another user’s micropost. We’ll put the resulting find inside a correct_user before filter, which checks that the current user actually has a micropost with the given id. The result appears in Listing 11.50.
    假設current_usermicropost中找不到這個id,會return nil(不會跳出錯誤訊息)
    (find會有錯誤訊息,find_by會return nil)
    find vs find_by vs where
    If no record is found, returns nil.

    app/views/microposts/_micropost.html.erb (Listing 11.49)
    <li id="<%= 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.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
    </span>
    </li>
    
    app/controllers/microposts_controller.rb (Listing 11.50)
    class MicropostsController < ApplicationController
    before_action :logged_in_user, only: [:create, :destroy]
    before_action :correct_user,   only: :destroy
    .
    .
    .
    def destroy
    @micropost.destroy
    flash[:success] = "Micropost deleted"
    redirect_to request.referrer || root_url
    end
    private
    def micropost_params
      params.require(:micropost).permit(:content)
    end
    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
    end
    
  32. request.referrer || root_url
    This uses the request.referrer method,11 which is closely related to the request.url variable used in friendly forwarding (Section 9.2.3), and is just the previous URL (in this case, the Home page).12 This is convenient because microposts appear on both the Home page and on the user’s profile page, so by using request.referrer we arrange to redirect back to the page issuing the delete request in both cases. If the referring URL is nil (as is the case inside some tests), Listing 11.50 sets the root_url as the default using the || operator. (Compare to the default options defined in Listing 8.50.)

  33. 開始做上傳照片

  34. Micropost Model會需要用到下面的gem,把他們都加到Gemfile

    • carrierwave gem: 上傳照片
    • mini_magick gem:image resizing (Section 11.4.3)
    • fog gem: image upload in production (Section 11.4.4)
  35. $ rails generate uploader Picture:產生image uploader 圖片上傳器。
    CarrierWave adds a Rails generator for creating an image uploader, which we’ll use to make an uploader for an image called picture。

  36. 要用CarrierWave上傳照片很簡單:在generate之後,記得把名字(這邊是Picture)放到相對應的Model(這邊是Microposts)去就好了,記得要設成String
    Images uploaded with CarrierWave should be associated with a corresponding attribute in an Active Record model, which simply contains the name of the image file in a string field. The resulting augmented data model for microposts appears in Figure 11.19.
    To add the required picture attribute to the Micropost model, we generate a migration and migrate the development database:

    $ rails generate migration add_picture_to_microposts picture:string
    $ bundle exec rake db:migrate
    
  37. 然後要加上這行:mount_uploader :picture, PictureUploader,才有辦法把image跟model連接起來。
    The way to tell CarrierWave to associate the image with a model is to use the mount_uploader method, which takes as arguments a symbol representing the attribute and the class name of the generated uploader:
    (Here PictureUploader is defined in the file picture_uploader.rb, which we’ll start editing in Section 11.4.2, but for now the generated default is fine.) Adding the uploader to the Micropost model gives the code shown in Listing 11.56.

    app/models/micropost.rb (Listing 11.56)
     class Micropost < ActiveRecord::Base
    belongs_to :user
    default_scope -> { order(created_at: :desc) }
    mount_uploader :picture, PictureUploader
    validates :user_id, presence: true
    validates :content, presence: true, length: { maximum: 140 }
    end
    
  38. html: { multipart: true } :in the arguments to form_for, which is necessary for file uploads.
    <%= f.file_field :picture %>: To include the uploader on the Home page

    app/views/shared/_micropost_form.html.erb (Listing 11.57)
     <%= form_for(@micropost, html: { multipart: true }) 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" %>
    <span class="picture">
    <%= f.file_field :picture %>
    </span>
    <% end %>
    
  39. image_tag helper可以叫出來照片。
    picture? boolean method 是CarrierWave跟據attribute的名字自動產生的method。
    Once the image has been uploaded, we can render it using the image_tag helper in the micropost partial, as shown in Listing 11.59. Notice the use of the picture? boolean method to prevent displaying an image tag when there isn’t an image. This method is created automatically by CarrierWave based on the name of the image attribute. The result of making a successful submission by hand appears in Figure 11.20. Writing an automated test for image upload is left as an exercise (Section 11.6).

    app/views/microposts/_micropost.html.erb (Listing 11.59)
     <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 %>
    <%= image_tag micropost.picture.url if micropost.picture? %>
    </span>
    <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
    </span>
    </li>
    
  40. 開始限制上傳的照片的格式跟尺寸,在伺服器端跟瀏覽器端都需要做設定

  41. 伺服器端:限制上傳檔案的類型:picture_uploader.rb

    app/uploaders/picture_uploader.rb (Listing 11.60)
    class PictureUploader < CarrierWave::Uploader::Base
    storage :file
    # Override the directory where uploaded files will be stored.
    
    # This is a sensible default for uploaders that are meant to be mounted:
    
    def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
    end
    # Add a white list of extensions which are allowed to be uploaded.
    
    def extension_white_list
    %w(jpg jpeg gif png)
    end
    end
    
  42. 伺服器端:尺寸:Micropost model,size validation不是內建的,所以要自己寫

    app/models/micropost.rb (Listing 11.61)
     class Micropost < ActiveRecord::Base
    belongs_to :user
    default_scope -> { order(created_at: :desc) }
    mount_uploader :picture, PictureUploader
    validates :user_id, presence: true
    validates :content, presence: true, length: { maximum: 140 }
    validate  :picture_size
    private
    # Validates the size of an uploaded picture.
    
    def picture_size
      if picture.size > 5.megabytes
        errors.add(:picture, "should be less than 5MB")
      end
    end
    end
    
  43. 用戶端:有兩個
    類型:<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
    尺寸:

    $('#micropost_picture').bind('change', function() {
    var size_in_megabytes = this.files[0].size/1024/1024;
    if (size_in_megabytes > 5) {
    alert('Maximum file size is 5MB. Please choose a smaller file.');
    }
    });
    
  44. 把Query放在最下面

    app/views/shared/_micropost_form.html.erb (Listing 11.62)
     <%= form_for(@micropost, html: { multipart: true }) 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" %>
    <span class="picture">
    <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
    </span>
    <% end %>
    <script type="text/javascript">
    $('#micropost_picture').bind('change', function() {
    var size_in_megabytes = this.files[0].size/1024/1024;
    if (size_in_megabytes > 5) {
      alert('Maximum file size is 5MB. Please choose a smaller file.');
    }
    });
    </script>
    
  45. ImageMagick: 變更上傳圖檔的尺寸

  46. MiniMagickCarrierWavemodule,是用來操控ImageMagick的介面

  47. 做一下比較:
    MiniMagick documentation
    這邊的MyUploader後面是CarrierWave::Uploader::Base
    這邊是不能超過200X200

    class MyUploader < CarrierWave::Uploader::Base
    include CarrierWave::MiniMagick
    process :resize_to_fit => [200, 200]
    end
    

    CarrierWave documentation on MiniMagick
    這邊的AvatarUploader的後面也是CarrierWave::Uploader::Base
    這邊是要放大到200X200

    class AvatarUploader < CarrierWave::Uploader::Base
    include CarrierWave::MiniMagick
    process resize_to_fill: [200, 200]
    end
    

    這邊的存檔的地方是在本地端的資料夾

    app/uploaders/picture_uploader.rb (Listing 11.63)
     class PictureUploader < CarrierWave::Uploader::Base
    include CarrierWave::MiniMagick
    process resize_to_limit: [400, 400]
    storage :file
    # Override the directory where uploaded files will be stored.
    
    # This is a sensible default for uploaders that are meant to be mounted:
    
    def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
    end
    # Add a white list of extensions which are allowed to be uploaded.
    
    def extension_white_list
    %w(jpg jpeg gif png)
    end
    end
    
  48. fog把上傳的存在雲端
    : Configuring the image uploader for production.

    app/uploaders/picture_uploader.rb (Listing 11.64)
     class PictureUploader < CarrierWave::Uploader::Base
    include CarrierWave::MiniMagick
    process resize_to_limit: [400, 400]

    if Rails.env.production?
    storage :fog
    else
    storage :file
    end

    # Override the directory where uploaded files will be stored.

    # This is a sensible default for uploaders that are meant to be mounted:

    def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
    end

    # Add a white list of extensions which are allowed to be uploaded.

    def extension_white_list
    %w(jpg jpeg gif png)
    end
    end


← ROR TUTORIAL (3RD ED.) Ch10 Account activation and password reset ROR TUTORIAL (3RD ED.) Ch12 Following users →