Ruby 2.0 / Rails 4.0, A selection of new features.
-
Upload
lrdesign -
Category
Technology
-
view
513 -
download
5
description
Transcript of Ruby 2.0 / Rails 4.0, A selection of new features.
Ruby 2.0 / Rails 4.0A selection of new features
Evan DornFounder, Logical Reality Design
http://[email protected]
@idahoev
Friday, August 9, 13
RUBY 2.0:A FEW COOL BITS
Friday, August 9, 13
#to_h
hash representation, like to_a or to_s or
to_sym.
Friday, August 9, 13
#to_h
Implemented on:•ENV•NilClass•Struct•Your classes!•ActiveRecord::Base (hopefully soon...)
Friday, August 9, 13
#bsearch
Fast binary search!
•Array•Range
Friday, August 9, 13
2.0.0p195 :003 > arr = [1, 3, 5, 7, 10, 11, 12, 14, 16, 18, 19] => [1, 3, 5, 7, 10, 11, 12, 14, 16, 18, 19]
#bsearch
Friday, August 9, 13
2.0.0p195 :003 > arr = [1, 3, 5, 7, 10, 11, 12, 14, 16, 18, 19] => [1, 3, 5, 7, 10, 11, 12, 14, 16, 18, 19] 2.0.0p195 :004 > n = 0 => 0
#bsearch
Friday, August 9, 13
2.0.0p195 :003 > arr = [1, 3, 5, 7, 10, 11, 12, 14, 16, 18, 19] => [1, 3, 5, 7, 10, 11, 12, 14, 16, 18, 19] 2.0.0p195 :004 > n = 0 => 0 2.0.0p195 :005 > arr.find{ |elem| p (n = n+1); elem == 11 }
#bsearch
Friday, August 9, 13
2.0.0p195 :003 > arr = [1, 3, 5, 7, 10, 11, 12, 14, 16, 18, 19] => [1, 3, 5, 7, 10, 11, 12, 14, 16, 18, 19] 2.0.0p195 :004 > n = 0 => 0 2.0.0p195 :005 > arr.find{ |elem| p (n = n+1); elem == 11 }1234567891011 => 11
#bsearch
Friday, August 9, 13
2.0.0p195 :003 > arr.bsearch{ |elem| p (n = n+1); elem == 11 }
#bsearch
Friday, August 9, 13
2.0.0p195 :003 > arr.bsearch{ |elem| p (n = n+1); elem == 11 }123 => 11
#bsearch
Friday, August 9, 13
WARNING!
Friday, August 9, 13
Undefined behavior if your Array is not
sorted!
Friday, August 9, 13
REFINEMENTS
•Hopefully improves the monkeypatch madness
•How many Core or StdLib classes does Rails extend?
•String
•Integer•Hash• Range• Array
•Regexp
•Proc•Date
•File
•Marshal
•Logger•NameError
•Numeric
•BigDecimal
•LoadError •Time
Friday, August 9, 13
REFINEMENTS
•Hopefully improves the monkeypatch madness
•How many Core or StdLib classes does Rails extend?
•String
•Integer•Hash• Range• Array
•Regexp
•Proc•Date
•File
•Marshal
•Logger•NameError
•Numeric
•BigDecimal
•LoadError •Time
•Module
•Class•Object
Friday, August 9, 13
REFINEMENTS
•What’s wrong with all this?
•“core” becomes a bit meaningless
•All loaded code sees these mutant core classes
•Harder to write gems that both with and without Rails
Friday, August 9, 13
NON-REFINEMENTSmodule MyExtensions def do_stuff endend
class String include MyExtensionsend
# all loaded code sees String#do_stuff
# String.new.instance_methods changes...
# String.new.respond_to changes...
Friday, August 9, 13
NON-REFINEMENTSmodule MyExtensions def do_stuff endend
class String include MyExtensionsend
# all loaded code sees String#do_stuff
# String.new.instance_methods changes...
# String.new.respond_to changes...
Friday, August 9, 13
NON-REFINEMENTSmodule MyExtensions def do_stuff endend
class String include MyExtensionsend
# all loaded code sees String#do_stuff
# String.new.instance_methods changes...
# String.new.respond_to changes...
Friday, August 9, 13
REFINEMENTSmodule MyExtensions refine String do def do_stuff p “awesome!” end endend
String.new.do_stuff # undefined method “do_stuff”
using MyExtensions
String.new.do_stuff
=> “awesome!”
Friday, August 9, 13
REFINEMENTSmodule MyExtensions refine String do def do_stuff p “awesome!” end endend
String.new.do_stuff # undefined method “do_stuff”
using MyExtensions
String.new.do_stuff
=> “awesome!”
Friday, August 9, 13
REFINEMENTSmodule MyExtensions refine String do def do_stuff p “awesome!” end endend
String.new.do_stuff # undefined method “do_stuff”
using MyExtensions
String.new.do_stuff
=> “awesome!”
Friday, August 9, 13
REFINEMENTSmodule MyExtensions refine String do def do_stuff p “awesome!” end endend
String.new.do_stuff # undefined method “do_stuff”
using MyExtensions
String.new.do_stuff
=> “awesome!”
Friday, August 9, 13
REFINEMENTSmodule MyExtensions refine String do def do_stuff p “awesome!” end endend
String.new.do_stuff # undefined method “do_stuff”
using MyExtensions
String.new.do_stuff
=> “awesome!”
Friday, August 9, 13
REFINEMENTSmodule MyExtensions refine String do def do_stuff p “awesome!” end endend
String.new.do_stuff # undefined method “do_stuff”
using MyExtensions
String.new.do_stuff
=> “awesome!”
Friday, August 9, 13
REFINEMENTSmodule MyExtensions refine String do def do_stuff p “awesome!” end endend
String.new.do_stuff # undefined method “do_stuff”
using MyExtensions
String.new.do_stuff
=> “awesome!”
Friday, August 9, 13
KEYWORD ARGUMENTS
•my_method(options = {}) has problems
•assigning defaults with merge({...}) is fugly
•options[:mispeled_arg] silently returns nil!
•options = {} allocates a new hash on every call
Friday, August 9, 13
KEYWORD ARGUMENTSdef my_method(arg1: ‘foo’, arg2: 123) p arg1 p arg2end
my_method“foo” 123=> 123
Friday, August 9, 13
KEYWORD ARGUMENTSdef my_method(arg1: ‘foo’, arg2: 123) p arg1 p arg2end
my_method“foo” 123=> 123
Friday, August 9, 13
WARNING!
Friday, August 9, 13
THOSE ARGS ARE NOT A HASH!
Friday, August 9, 13
KEYWORD ARGUMENTSdef my_method(:arg1 => 'foo', :arg2 => 123) # doesn’t workend
SyntaxError: (irb):27: syntax error, unexpected tSYMBEG, expecting ')'def my_method(:arg1 => 'foo', :arg2 => 123) ^
Friday, August 9, 13
KEYWORD ARGUMENTSdef my_method(:arg1 => 'foo', :arg2 => 123) # doesn’t workend
SyntaxError: (irb):27: syntax error, unexpected tSYMBEG, expecting ')'def my_method(:arg1 => 'foo', :arg2 => 123) ^
Friday, August 9, 13
MODULE#PREPEND •I didn’t have time to write this slide!
•But the feature is awesome, I promise!
Friday, August 9, 13
Friday, August 9, 13
SAD PUPPY
Friday, August 9, 13
RAILS 4.0!
Friday, August 9, 13
MASS-ASSIGNMENTPROTECTION
•No more “attr_accessible” in the Model
•It’s a controller concern
•... because it’s often different in different controllers
•(admin controllers, etc.)
Friday, August 9, 13
STRONG PARAMSclass UsersController < ApplicationController def update user = User.find(params[:id]) if user.update_attributes(user_params) # see below redirect_to home_path else render :edit end end private # Require that :user be a key in the params Hash, # and only accept :first, :last, and :email attributes def user_params params.require(:user).permit(:first, :last, :email) endend
Friday, August 9, 13
STRONG PARAMSclass UsersController < ApplicationController def update user = User.find(params[:id]) if user.update_attributes(user_params) # see below redirect_to home_path else render :edit end end private # Require that :user be a key in the params Hash, # and only accept :first, :last, and :email attributes def user_params params.require(:user).permit(:first, :last, :email) endend
Friday, August 9, 13
STRONG PARAMSclass UsersController < ApplicationController def update user = User.find(params[:id]) if user.update_attributes(user_params) # see below redirect_to home_path else render :edit end end private # Require that :user be a key in the params Hash, # and only accept :first, :last, and :email attributes def user_params params.require(:user).permit(:first, :last, :email) endend
Friday, August 9, 13
STRONG PARAMSclass UsersController < ApplicationController def update user = User.find(params[:id]) if user.update_attributes(user_params) # see below redirect_to home_path else render :edit end end private # Require that :user be a key in the params Hash, # and only accept :first, :last, and :email attributes def user_params params.require(:user).permit(:first, :last, :email) endend
Friday, August 9, 13
STRONG PARAMS
•Params hashes get some new methods
•e.g. params.permitted?
•params hash will contain only the permitted keys
•No deep permits: if the Hash contains a Hash, you must permit nested keys specifically
Friday, August 9, 13
Friday, August 9, 13
FRAGMENT CACHING
•Caching HTML fragments can be a huge speedup
•Expiring fragments correctly is a bitch
•... especially if they’re nested
•... and slow if you have a lot of cache keys
Friday, August 9, 13
OLD BUSTED CACHING%h2 To-do List- cache “todo_for_#{current_user.id}” do %table - @todo_items.each do |item| - cache “todo_item_#{item.id}” do %tr %td= item.name %td= item.importance
Friday, August 9, 13
OLD BUSTED CACHING%h2 To-do List- cache “todo_for_#{current_user.id}” do %table - @todo_items.each do |item| - cache “todo_item_#{item.id}” do %tr %td= item.name %td= item.importance
Friday, August 9, 13
RUSSIAN DOLL CACHING
•Important changes:
•cache(key) do ...end takes an Array for the key
•key generation concatenates the array elements
•after calling #cache_key on any members that respond to that method
Friday, August 9, 13
NEW HOTNESS CACHING%h2 To-do List- cache [ :todo_list, current_user, @todo_list ] do %table - @todo_list.items.each do |item| - cache [ :todo_item, item ] do %tr %td= item.name %td= item.importance
Friday, August 9, 13
NEW HOTNESS CACHING%h2 To-do List- cache [ :todo_list, current_user, todo_list ] do %table - @todo_list.items.each do |item| - cache [ :todo_item, item ] do %tr %td= item.name %td= item.importance
Friday, August 9, 13
class TodoItem < ActiveRecord::Base def cache_key “#{self.id}-#{self.updated_at.to_s(:number)}” endend
class TodoList # A presenter attr_accessor :items # array of items attr_accessor :user # User object def cache_key “#{user.id}-#{items.newest.updated_at.to_s(:number)“ end end
Friday, August 9, 13
class TodoItem < ActiveRecord::Base def cache_key “#{self.id}-#{self.updated_at.to_s(:number)}” endend
class TodoList # A presenter attr_accessor :items # array of items attr_accessor :user # User object def cache_key “#{user.id}-#{items.newest.updated_at.to_s(:number)“ end end
Friday, August 9, 13
class TodoItem < ActiveRecord:: Base def cache_key “#{self.id}-#{self.updated_at.to_s(:number)}” endend
class TodoList # A Presenter attr_accessor :items # array of items attr_accessor :user # User object def cache_key “#{user.id}-#{items.newest.updated_at.to_s(:number)“ end end
Friday, August 9, 13
class TodoItem def cache_key “#{self.id}-#{self.updated_at.to_s(:number)}” endend
class TodoList attr_accessor :items # array of items attr_accessor :user # User object def cache_key “#{items.newest.updated_at.to_s(:number)“ end end
Friday, August 9, 13
class TodoItem def cache_key “#{self.id}-#{self.updated_at.to_s(:number)}” endend
class TodoList attr_accessor :items # array of items attr_accessor :user # User object def cache_key “#{items.newest.updated_at.to_s(:number)“ end end
class User < ActiveRecord::Base def cache_key “#{self.id}-#{self.updated_at.to_s(:number)}” end end
Friday, August 9, 13
NEW HOTNESS CACHING%h2 To-do List- cache [ :todo_list, current_user, todo_list ] do -# “todo_list/1234-201308081234/201308081146” %table - @todo_list.items.each do |item| - cache [ :todo_item, item ] do %tr %td= item.name %td= item.importance
Friday, August 9, 13
NEW HOTNESS CACHING%h2 To-do List- cache [ :todo_list, current_user, todo_list ] do -# “todo_list/1234-201308081234/201308081146” %table - @todo_list.items.each do |item| - cache [ :todo_item, item ] do -# “todo_item/5316-201306071156” %tr %td= item.name %td= item.importance
Friday, August 9, 13
RUSSIAN DOLL CACHING
•With clever key construction:
•Every model update causes a cache miss
•... Never have to invalidate fragments!
•Any decent memory store will expire unused keys for you
Friday, August 9, 13
PSST... A SECRET:You can do this in Rails 3, too.
Just write your own smart cache key helpers.
Friday, August 9, 13
PATCH•PUT is no longer the default HTTP method for #update
actions.
•(Because PUT is defined to be whole-object, never partial, and should avoid side-effects.)
•Use PATCH instead
•Affects: config/routes.rb, tests, etc.
Friday, August 9, 13
ACTIVEMODEL::MODEL
•Turns any object into a proper model
•For use with form_for(), etc.
•attr_accessors become AM-style attributes
•Adds name introspection, conversion, I18n support, validations, etc...
Friday, August 9, 13
ACTIVEMODEL::MODEL
Helps to break the tendency to equate “resource” with “ActiveRecord Model.”
Friday, August 9, 13
ACTIVEMODEL::MODELclass UserFormModel include ActiveModel::Model
def new(user) @user = user @settings = user.settings end
attr_accessor :name, :admin
def persisted? @user.persisted? end
def persist! @user.name = name @user.save! @settings.admin = admin @settings.save! endend
Friday, August 9, 13
ACTIVEMODEL::MODELclass UserFormModel include ActiveModel::Model
def new(user) @user = user @settings = user.settings end
attr_accessor :name, :admin
def persisted? @user.persisted? end
def persist! @user.name = name @user.save! @settings.admin = admin @settings.save! end
Friday, August 9, 13
ACTIVEMODEL::MODELclass UserFormModel include ActiveModel::Model
def new(user) @user = user @settings = user.settings end
attr_accessor :name, :admin
def persisted? @user.persisted? end
def persist! @user.name = name @user.save! @settings.admin = admin @settings.save! end
Friday, August 9, 13
ACTIVEMODEL::MODELclass UserFormModel include ActiveModel::Model
def new(user) @user = user @settings = user.settings end
attr_accessor :name, :admin
def persisted? # default false if not overridden! @user.persisted? end
def persist! @user.name = name @user.save! @settings.admin = admin @settings.save! endend
Friday, August 9, 13
ACTIVEMODEL::MODELclass UserFormModel include ActiveModel::Model
def new(user) @user = user @settings = user.settings end
attr_accessor :name, :admin
def persisted? @user.persisted? end
def persist! @user.name = name @user.save! @settings.admin = admin @settings.save! endend
Friday, August 9, 13
ACTIVEMODEL::MODEL# /app/views/users/_form
= form_for(@user_form_model) = f.input(:name) = f.check_box(:admin) = f.submit!
Friday, August 9, 13
ACTIVEMODEL::MODEL# /app/views/users/_form
= form_for(@user_form_model) = f.input(:name) = f.check_box(:admin) = f.submit!
Friday, August 9, 13
AVOIDING FULL PAGE LOADS
Friday, August 9, 13
CHECK OUT “TURBOLINKS”
Friday, August 9, 13
OR JUST USE:•AngularJS•EmberJS•Backbone•etc...
Friday, August 9, 13
‘cause that’s probably the
right tool,really.
Friday, August 9, 13
STREAMING SUPPORT!
Friday, August 9, 13
ACTIONCONTROLLER::LIVEclass MyController < ActionController::Base include ActionController::Live
def stream response.headers['Content-Type'] = 'text/event-stream' 100.times { response.stream.write "hello world\n" sleep 1 } ensure response.stream.close end
end
Friday, August 9, 13
ACTIONCONTROLLER::LIVEclass MyController < ActionController::Base include ActionController::Live
def stream response.headers['Content-Type'] = 'text/event-stream' 100.times { response.stream.write "hello world\n" sleep 1 } ensure response.stream.close end
end
Friday, August 9, 13
ACTIONCONTROLLER::LIVEclass MyController < ActionController::Base include ActionController::Live
def stream response.headers['Content-Type'] = 'text/event-stream' 100.times { response.stream.write "hello world\n" sleep 1 } ensure response.stream.close end
end
Friday, August 9, 13
ACTIONCONTROLLER::LIVEclass MyController < ActionController::Base include ActionController::Live
def stream response.headers['Content-Type'] = 'text/event-stream' 100.times { response.stream.write "hello world\n" sleep 1 } ensure response.stream.close end
end
Friday, August 9, 13
ACTIONCONTROLLER::LIVE
•Don’t try to change headers after calling response.stream.write...
•Don’t forget to close...
•Stream actions automatically happen in a separate thread.
•(So make sure it’s thread-safe!)
Friday, August 9, 13
ANY TIME LEFT? (IF SO, I’LL DO
MODULE#PREPEND ON A WHITEBOARD OR SOMETHING.)
Friday, August 9, 13
Thanks!
Evan DornFounder, Logical Reality Design
http://[email protected]
@idahoev
Friday, August 9, 13