Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rails4: a Christmas tale

Rails4: a Christmas tale

Presentation given during the genevarb meetup, in Geneva (Switzerland)

A8c53bacf85eb821777918e11610a5a9?s=128

Yannis Jaquet

December 12, 2012
Tweet

Transcript

  1. 4 1

  2. 2

  3. Mass extraction • Hash-based & Dynamic finder methods rails /

    activerecord-deprecated_finders • Mass assignment protection in Active Record models rails/protected_attributes • ActiveRecord::SessionStore rails/activerecord-session_store • Active Record Observers rails/rails-observers • Active Resource rails/activeresource • Action Caching rails/actionpack-action_caching • Page Caching rails/actionpack-page_caching • Sprockets rails/sprockets-rails 3
  4. 4

  5. Ruby 1.9.3 5

  6. Deprecation policy • Rails 3.2.x • new deprecation warnings •

    Rails 4.0.0 • new features • new deprecation warnings • nothing is removed • easy upgrade • Rails 4.0.x • no new features • no new deprecation warnings • Rails 4.1: • new features • removes deprecated stuff 6
  7. strong parameters queue API russian dolls caching 7

  8. strong parameters queue API russian dolls caching 7

  9. # app/models/kid.rb class Kid < ActiveRecord::Base attr_accessible :name, :age, :address,

    :girl #, :good end # app/controllers/kids_controller.rb class KidsController < ApplicationController def create @kid = Kid.new(params[:kid]) if @kid.save redirect_to root_url else render action: "new" end end end 3 Mass-asignment protection 8
  10. ActionController::StrongParameters 4 9

  11. ActionController::StrongParameters # app/models/kid.rb class Kid < ActiveRecord::Base end # app/controllers/kids_controller.rb

    class KidsController < ActionController::Base def create Kid.create(params.require(:kid).permit(:name, :age)) end # Kid.create(params[:kid]) => # ActiveModel::ForbiddenAttributes end 4 10
  12. # app/controllers/kidss_controller.rb class KidsController < ActionController::Base def create Kid.create(kid_params) end

    def update redirect_to kids.find(params[:id]).tap { |kid| kid.update_attributes!(kid_params) } end private def kid_params params.require(:kid).permit(:name, :age) end # params.require(:kid).permit(:name, :age, pets_attributes: # [ :name, :category ]) end 4 ActionController::StrongParameters 11
  13. 3 Try it now! #gemfile gem 'strong_parameters', github: 'rails/strong_parameters' #

    application.rb config.active_record.whitelist_attributes = false 12
  14. #gemfile gem 'protected_attributes', github: 'rails/protected_attributes' # application.rb config.active_record.whitelist_attributes = true

    Easy transition… 4 13
  15. Rails.queue 14

  16. Many choices… • gem 'delayed_job_active_record' • gem 'resque' • gem

    'sidekiq' • … 3 15
  17. Rails.queue (ActiveSupport::Queue) 4 16

  18. class ToySender attr_reader :kid_id, :toy_id def initialize(attr) @kid_id = attr[:kid_id]

    @toy_id = attr[:toy_id] end def run kid = Kid.find(@kid_id) toy = Toy.find(@toy_id) # toy.send_to(kid) end end # Then, add an instance of WishSender to Rails.queue. # A likely place for this code is in a controller: class KidsController < ApplicationController def send_toy Rails.queue << ToySender.new(params) flash[:notice] = "Toy queued to be sent!" redirect_to kids_url end end 4 4 17
  19. # config/application.rb # Default Synchronous config.queue = ActiveSupport::SynchronousQueue.new # Default

    Threaded config.queue = ActiveSupport::Queue.new # Resque Queue config.queue = Resque::Rails::Queue.new # Sidekiq Queue config.queue = Sidekiq::Client::Queue.new # Test Queue config.queue = ActiveSupport::TestQueue.new 4 18
  20. asynchronous ActionMailer 19

  21. # application.rb config.action_mailer.async = true # welcome_mailer.rb class WelcomeMailer <

    ActionMailer::Base self.async = true end 4 20
  22. # application.rb config.action_mailer.queue = :sidekiq # welcome_mailer.rb class WelcomeMailer <

    ActionMailer::Base def queue MyQueue.new # Sidekiq::Client::Queue.new end end 4 21
  23. # kids_controller.rb # don’t do this! can't be marshalled (serialized)

    in the # queue system WelcomeMailer.welcome(@kid).deliver # You should do WelcomeMailer.welcome(@kid.id).deliver # welcome_mailer.rb class WelcomeMailer < ActionMailer::Base def welcome(id) @kid = User.find(id) # prepare your mail end end 4 22
  24. russian dolls caching 23

  25. 3 - cache [ "v1", kid ] do %h1 All

    toys = render kid.toys ! - cache [ "v1", toy ] do %h2= toy.name ! = render toy.elves ! ! - cache [ "v1", elf ] do ! ! %p ! ! ! = elf.first_name kids/show.html.erb toys/_toy.html.erb elves/_elf.html.erb 24
  26. 3 - cache [ "v2", kid ] do %h1= "All

    toys for #{kid.name}" = render kid.toys ! - cache [ "v1", toy ] do %h2= toy.name ! = render toy.elves ! ! - cache [ "v1", elf ] do ! ! %p ! ! ! = elf.first_name kids/show.html.erb toys/_toy.html.erb elves/_elf.html.erb 25
  27. 3 - cache [ "v2", kid ] do %h1= "All

    toys for #{kid.name}" = render kid.toys ! - cache [ "v2", toy ] do %em= toy.build_on %h2= toy.name ! = render toy.elves ! ! - cache [ "v1", elf ] do ! ! %p ! ! ! = elf.first_name kids/show.html.erb toys/_toy.html.erb elves/_elf.html.erb 26
  28. 3 - cache [ "v3", kid ] do %h1= "All

    toys for #{kid.name}" = render kid.toys ! - cache [ "v2", toy ] do %em= toy.build_on %h2= toy.name ! = render toy.elves ! ! - cache [ "v1", elf ] do ! ! %p ! ! ! = elf.first_name kids/show.html.erb toys/_toy.html.erb elves/_elf.html.erb 27
  29. 3 - cache [ "v3", kid ] do %h1= "All

    toys for #{kid.name}" = render kid.toys ! - cache [ "v2", toy ] do %em= toy.build_on %h2= toy.name ! = render toy.elves ! ! - cache [ "v2", elf ] do ! ! %p ! ! ! = elf.first_name ! ! ! = elf.last_name kids/show.html.erb toys/_toy.html.erb elves/_elf.html.erb 28
  30. 3 - cache [ "v3", kid ] do %h1= "All

    toys for #{kid.name}" = render kid.toys ! - cache [ "v3", toy ] do %em= toy.build_on %h2= toy.name ! = render toy.elves ! ! - cache [ "v2", elf ] do ! ! %p ! ! ! = elf.first_name ! ! ! = elf.last_name kids/show.html.erb toys/_toy.html.erb elves/_elf.html.erb 29
  31. 3 - cache [ "v4", kid ] do %h1= "All

    toys for #{kid.name}" = render kid.toys ! - cache [ "v3", toy ] do %em= toy.build_on %h2= toy.name ! = render toy.elves ! ! - cache [ "v2", elf ] do ! ! %p ! ! ! = elf.first_name ! ! ! = elf.last_name kids/show.html.erb toys/_toy.html.erb elves/_elf.html.erb 30
  32. 4 https://github.com/rails/cache_digests 31

  33. views/kids/45-20121211152602/d9fb66b120b61f46707c67ab41d93cb2 MD5 hash of the template content and of all

    its dependencies 4 32
  34. 4 - cache [ kid ] do %h1 All toys

    = render kid.toys ! - cache [ toy ] do %h2= toy.name ! = render toy.elves ! ! - cache [ elf ] do ! ! %p ! ! ! = elf.first_name kids/show.html.erb toys/_toy.html.erb elves/_elf.html.erb 33
  35. 4 - cache [ kid ] do %h1= "All toys

    for #{kid.name}" = render kid.toys ! - cache [ toy ] do %em= toy.build_on %h2= toy.name ! = render toy.elves ! ! - cache [ elf ] do ! ! %p ! ! ! = elf.first_name ! ! ! = elf.last_name kids/show.html.erb toys/_toy.html.erb elves/_elf.html.erb 34
  36. Use it now! # gemfile gem 'cache_digests' 3 35

  37. and… 4 ActionController::Caching::Pages ActionController::Caching::Actions 36

  38. and… 4 ActionController::Caching::Pages ActionController::Caching::Actions 36

  39. and… 4 ActionController::Caching::Pages ActionController::Caching::Actions # gemfile gem 'actionpack-page_caching' # gemfile

    gem 'actionpack-action_caching' 36
  40. turbolinks 37

  41. • doesn’t re-load JS and CSS • doesn’t re-compile •

    page instance is kept alive • only the body and title of the page are replaced 4 38
  42. • doesn’t re-load JS and CSS • doesn’t re-compile •

    page instance is kept alive • only the body and title of the page are replaced 4 • response contains the full rendered page, so no bandwidth is saved 38
  43. Gotchas $(document).ready( function() { ! … }); 39

  44. Gotchas 3 $(document).ready(function() { ! $("a.special_link").on("click", function(event) { ! !

    alert('Christmas!!!'); ! }); }); 40
  45. Gotchas 3 $(document).ready(function() { ! $("a.special_link").on("click", function(event) { ! !

    alert('Christmas!!!'); ! }); }); 4 $(document).ready(function() { ! $(document).on("click", "a.special_link", function(event) { ! ! alert('Christmas!!!'); ! }); }); 40
  46. Turbolinks events page:fetch starting to fetch the target page (only

    called if loading fresh, not from cache). page:load fetched page is being retrieved fresh from the server page:restore fetched page is being retrieved from the 10-slot client-side cache page:change page has changed to the newly fetched version 41
  47. $(document).ready(function() { ! $(document).on("click", "a.special_link", function(event) { ! ! alert('Christmas!!!');

    ! }); }); $(document).on("page:load" function() { ! $("a.special_link").on("click", function(event) { ! ! alert('Christmas!!!'); ! }); }); 4 42
  48. Track asset changes 4 <link href="/assets/ application-9bd64a86adb3cd9ab3b16e9dca67a33a.css" rel="stylesheet" type="text/css" data-turbolinks-track>

    43
  49. Don’t want turbolinks? 4 <a href="/articles" data-no-turbolink>Articles</a> 44

  50. Use it now! https://github.com/rails/turbolinks 45

  51. PATCH verb 46

  52. • resource creation or replacement at some given URL •

    must send a complete representation of the resource • idempotent method (same request has always same result, think :updated_at) PUT 47
  53. • not idempotent • allows full and partial updates •

    allows side-effects on other resources PATCH 48
  54. PATCH • PATCH is going to be the primary method

    for updates in Rails 4.0 • both PUT and PATCH are routed to update 49
  55. match 50

  56. • match respond to all HTTP verbs (POST, PUT, DELETE

    and GET) • potential cross site request forgery (CSRF) #match 3 51
  57. match '/toys/:id/purchase' => 'toys#purchase' CSRF protection already exists in Rails

    but not for the GET method Problem 3 52
  58. get '/toys/:id/purchase' => 'Toy#purchase' match '/toys/:id/purchase' => 'Toy#purchase', :via =>

    :get match '/toys/:id/purchase' => 'Toy#purchase', :via => :any Rails4 forces a verb to be present on match routes Solution 53
  59. concerns 54

  60. # config/routes.rb Christmas::Application.routes.draw do ! resources :toys do ! !

    resources :pictures ! end ! resources :kids do ! ! resources :pictures ! end ! resources :elves do ! ! resources :pictures ! end end Your routes are not DRY 3 55
  61. # config/routes.rb Christmas::Application.routes.draw do ! concern :picturable do |options| !

    ! resources :pictures, options ! end resources :toys, :concerns :picturable resources :kids, :concerns :picturable resources :elves, :concerns :picturable end Your routes are DRY 4 56
  62. # config/routes.rb Christmas::Application.routes.draw do ! concern :picturable do ! !

    resources :pictures ! end resources [:toys, :kids, :elves], :concerns :picturable end Your routes are DRY 4 57
  63. various 58

  64. ActiveRecord::Relation 59

  65. 3 # toys_controller.rb def index ! if current_kid.good_kid? ! !

    if current_kid.girl? ! ! ! @toys = Toy.for_girl ! ! else ! ! ! @toys = Toy.for_boy ! ! end ! else ! ! @toys = [] ! end ! … ! @toys = @toys.in_stock unless @toys.is_a?(Array) end 60
  66. ActiveRecord::Relation#none # toys_controller.rb def index ! if current_kid.good_kid? ! !

    if current_kid.girl? ! ! ! @toys = Toy.for_girl ! ! else ! ! ! @toys = Toy.for_boy ! ! end ! else ! ! @toys = Toy.none ! end ! … ! @toys = @toys.in_stock end 4 61
  67. ActiveRecord::Relation#all ActiveRecord::Relation#where! ActiveRecord::Relation#none! # toys_controller.rb def index ! @toys =

    Toy.all ! if current_kid.good_kid? ! ! if current_kid.girl? ! ! ! @toys.where!(girl: true) ! ! else ! ! ! @toys.where!(boy: true) ! ! end ! else ! ! @toys.none! ! end ! … ! @toys = @toys.in_stock end 4 62
  68. ActiveRecord::Relation#not Item.where "kid_id != ?", current_kid.kid_id Item.where.not kid_id: current_kid.id 4

    3 63
  69. ActiveRecord::Relation#order Toy.order("orders_count DESC").order(:created_at) # SELECT * FROM toys ORDER BY

    orders_count DESC, created_at 3 64
  70. Toy.order("orders_count DESC").order(:created_at) # SELECT * FROM toys ORDER BY created_at,

    orders_count DESC 4 ActiveRecord::Relation#order 65
  71. Toy.order("orders_count DESC").order(:created_at) # SELECT * FROM toys ORDER BY created_at,

    orders_count DESC 4 Comment.order("replies_count DESC", :created_at) ActiveRecord::Relation#order 65
  72. deprecated finders 3 Item.find(:first, :conditions => ...) Item.where(...).first find_all_by_... scoped_by_...

    where(...) find_last_by_... where(...).last first_or_initialize_by_... first_or_initialize_by(...) find_or_create_by_... find_or_create_by(...) first_or_create_by_...! find_or_create_by!(...) 4 # Gemfile gem "activerecord-deprecated_finders" 66
  73. class Order < ActiveRecord::Base ! scope :past, where("orders.sent_on < ?",

    Time.current) end scope 3 67
  74. class Order < ActiveRecord::Base ! scope :past, where("orders.sent_on < ?",

    Time.current) end scope # DEPRECATION WARNING: Using #scope without passing a callable object is deprecated. 4 68
  75. class Order < ActiveRecord::Base ! scope :past, ->{ where("orders.sent_on <

    ?", Time.current) } end scope 4 69
  76. new form helpers collection_check_boxes(:toy, :category_ids, Category.all, :id, :name) # Returns:

    # <input id="toy_category_ids_1" name="toy[category_ids][]" type="checkbox" # value="1" checked="checked" /> # <label for="toy_category_ids_1">Ruby</label> # <input id="toy_category_ids_2" name="toy[category_ids][]" type="checkbox" # value="2" /> # <label for="toy_category_ids_2">Javascript</label> # ... # <input name="toy[category_ids][]" type="hidden" value="" /> 70
  77. new form helpers collection_radio_buttons(:toy, :category_ids, Category.all, :id, :name) # Returns:

    # <input id="toy_category_ids_1" name="toy[category_ids][]" type="radio" value="1" checked="checked" /> # <label for="toy_category_ids_1">Ruby</label> # <input id="toy_category_ids_2" name="toy[category_ids][]" type="radio" value="2" / > # <label for="toy_category_ids_2">Javascript</label> # ... 71
  78. new form helpers collection_check_boxes(:toy, :category_ids, Category.all, :id, :name) do |b|

    b.label(:"data-value" => b.value) { b.check_box + b.text } end Customizable by a block 72
  79. 73

  80. Install it today! • git clone git://github.com/rails/rails.git path_to/ rails •

    path_to/rails/railties/bin/rails new AppName --edge 74
  81. When? 75

  82. When? 75

  83. A few resources… 76

  84. …for Christmas time! 77

  85. Prepare to upgrade https://github.com/alindeman/rails4_upgrade 78

  86. presentations • rails 4 in 30 minutes (Santiago Pastorino) •

    slides: http://blog.wyeworks.com/2012/10/29/rails-4-in-30-minutes/ • Rails 4.0 Whirlwind Tour (Andy Lindeman) • slides: https://speakerdeck.com/alindeman/rails-4-dot-0-whirlwind-tour • video: http://vimeo.com/51181496 • What to Expect in Rails 4.0 (Prem Sichanugrist) • slides: https://speakerdeck.com/bostonrb/what-to-expect-in-rails-4-dot-0 • video: http://www.youtube.com/watch?v=z6YgD6tVPQs • Why should I care about Rails 4? (Steve Klabnik) • slides: https://speakerdeck.com/steveklabnik/why-should-i-care-about-rails-4 • video: http://vimeo.com/51898266 • Rails 4 and the Future of Web (Aaron Patterson) • slides: https://speakerdeck.com/tenderlove/aloha-ruby-conference-2012 • video: http://www.youtube.com/watch?v=kufXhNkm5WU 79
  87. 80