Share
Embed
Rails4: a Christmas tale
Presentation given during the genevarb meetup, in Geneva (Switzerland)
Yannis Jaquet
December 12, 2012
Transcript
87 slides
-
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 -
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 -
# 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 -
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 -
# 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 -
3 Try it now! #gemfile gem 'strong_parameters', github: 'rails/strong_parameters' #
application.rb config.active_record.whitelist_attributes = false 12 -
Many choices… • gem 'delayed_job_active_record' • gem 'resque' • gem
'sidekiq' • … 3 15 -
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 -
# 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 -
# application.rb config.action_mailer.async = true # welcome_mailer.rb class WelcomeMailer <
ActionMailer::Base self.async = true end 4 20 -
# 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 -
# 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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
and… 4 ActionController::Caching::Pages ActionController::Caching::Actions # gemfile gem 'actionpack-page_caching' # gemfile
gem 'actionpack-action_caching' 36 -
• 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 -
• 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 -
Gotchas 3 $(document).ready(function() { ! $("a.special_link").on("click", function(event) { ! !
alert('Christmas!!!'); ! }); }); 40 -
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 -
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 -
$(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 -
• 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 -
• not idempotent • allows full and partial updates •
allows side-effects on other resources PATCH 48 -
PATCH • PATCH is going to be the primary method
for updates in Rails 4.0 • both PUT and PATCH are routed to update 49 -
• match respond to all HTTP verbs (POST, PUT, DELETE
and GET) • potential cross site request forgery (CSRF) #match 3 51 -
match '/toys/:id/purchase' => 'toys#purchase' CSRF protection already exists in Rails
but not for the GET method Problem 3 52 -
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 -
# 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 -
# 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 -
# 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 -
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 -
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 -
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 -
ActiveRecord::Relation#order Toy.order("orders_count DESC").order(:created_at) # SELECT * FROM toys ORDER BY
orders_count DESC, created_at 3 64 -
Toy.order("orders_count DESC").order(:created_at) # SELECT * FROM toys ORDER BY created_at,
orders_count DESC 4 ActiveRecord::Relation#order 65 -
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 -
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 -
class Order < ActiveRecord::Base ! scope :past, where("orders.sent_on < ?",
Time.current) end scope 3 67 -
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 -
class Order < ActiveRecord::Base ! scope :past, ->{ where("orders.sent_on <
?", Time.current) } end scope 4 69 -
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 -
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 -
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 -
Install it today! • git clone git://github.com/rails/rails.git path_to/ rails •
path_to/rails/railties/bin/rails new AppName --edge 74 -
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