• Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
626
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
0
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Rails Under The Knife Jacob Harris The New York Times http://open.nytimes.com/ http://www.nimblecode.com/ harrisj@nytimes.com harrisj@schizopolis.net harrisj on Flickr / Twitter / Del.icio.us / 43whatever / NYC.rb / Last.fm / etc.
  • 2. Things You Might Know • basic Ruby syntax • object-oriented programming • has_many
:talks • <%=
for
t
in
@talks
%> • and that it’s really @talks.each
do
|t| • validates_presence_of
:name • def
before_save(talk)
  • 3. The Stuff of Magic • Three things you might kinda know: • Blocks • Reflection • Metaprogramming • Commonly called magic, but...
  • 4. Code can and should be manipulated like data
  • 5. Blocks <%
@talks.each
do
|t|
%> 
 <%=
render_partial
'talk',
t
%> <%
end
%>
  • 6. Blocks @talks.any?
{
|t|
t.title
=~
/rails/i
} @talks.select
{|t|
sounds_cool?
t
} @talks.inject
{|mins,
t|
mins
+=
t.minutes
}
  • 7. Reflection irb>
(3.public_methods
‐
Object.public_methods).sort #
=>
[quot;%quot;,
quot;&quot;,
quot;*quot;,
quot;**quot;,
quot;+quot;,
quot;+@quot;,
quot;‐quot;,
quot;‐@quot;,
quot;/quot;,
quot;<<quot;,
quot;>>quot;,
quot;[]quot;,
 quot;^quot;,
quot;absquot;,
quot;between?quot;,
quot;ceilquot;,
quot;chrquot;,
quot;coercequot;,
quot;denominatorquot;,
quot;divquot;,
 quot;divmodquot;,
quot;downtoquot;,
quot;floorquot;,
quot;gcdquot;,
quot;gcdlcmquot;,
quot;id2namequot;,
quot;integer?quot;,
quot;lcmquot;,
 quot;moduloquot;,
quot;nextquot;,
quot;nonzero?quot;,
quot;numeratorquot;,
quot;power!quot;,
quot;precquot;,
quot;prec_fquot;,
 quot;prec_iquot;,
quot;quoquot;,
quot;rdivquot;,
quot;remainderquot;,
quot;roundquot;,
quot;rpowerquot;,
 quot;singleton_method_addedquot;,
quot;sizequot;,
quot;stepquot;,
quot;succquot;,
quot;timesquot;,
quot;to_bnquot;,
quot;to_fquot;,
 quot;to_iquot;,
quot;to_intquot;,
quot;to_rquot;,
quot;to_symquot;,
quot;truncatequot;,
quot;uptoquot;,
quot;zero?quot;,
quot;|quot;,
quot;~quot;]
  • 8. Metaprogramming console>>
(3.public_methods
‐
Object.public_methods).sort #
=>
[quot;%quot;,
quot;&quot;,
quot;*quot;,
quot;**quot;,
quot;+quot;,
quot;+@quot;,
quot;‐quot;,
quot;‐@quot;,
quot;/quot;,
quot;<<quot;,
quot;>>quot;,
quot;[]quot;,
 quot;^quot;,
quot;absquot;,
quot;agoquot;,
quot;between?quot;,
quot;bytequot;,
quot;bytesquot;,
quot;ceilquot;,
quot;chrquot;,
quot;coercequot;,
 quot;dayquot;,
quot;daysquot;,
quot;denominatorquot;,
quot;divquot;,
quot;divmodquot;,
quot;downtoquot;,
quot;even?quot;,
 quot;exabytequot;,
quot;exabytesquot;,
quot;floorquot;,
quot;fortnightquot;,
quot;fortnightsquot;,
quot;from_nowquot;,
 quot;gcdquot;,
quot;gcdlcmquot;,
quot;gigabytequot;,
quot;gigabytesquot;,
quot;hourquot;,
quot;hoursquot;,
quot;id2namequot;,
 quot;integer?quot;,
quot;kilobytequot;,
quot;kilobytesquot;,
quot;lcmquot;,
quot;megabytequot;,
quot;megabytesquot;,
 quot;minutequot;,
quot;minutesquot;,
quot;moduloquot;,
quot;monthquot;,
quot;monthsquot;,
quot;multiple_of?quot;,
quot;nextquot;,
 quot;nonzero?quot;,
quot;numeratorquot;,
quot;odd?quot;,
quot;ordinalizequot;,
quot;petabytequot;,
quot;petabytesquot;,
 quot;power!quot;,
quot;precquot;,
quot;prec_fquot;,
quot;prec_iquot;,
quot;quoquot;,
quot;rdivquot;,
quot;remainderquot;,
quot;roundquot;,
 quot;rpowerquot;,
quot;secondquot;,
quot;secondsquot;,
quot;sincequot;,
quot;singleton_method_addedquot;,
quot;sizequot;,
 quot;stepquot;,
quot;succquot;,
quot;terabytequot;,
quot;terabytesquot;,
quot;timesquot;,
quot;to_bnquot;,
quot;to_fquot;,
quot;to_iquot;,
 quot;to_intquot;,
quot;to_rquot;,
quot;to_symquot;,
quot;truncatequot;,
quot;untilquot;,
quot;uptoquot;,
quot;weekquot;,
quot;weeksquot;,
 quot;xchrquot;,
quot;yearquot;,
quot;yearsquot;,
quot;zero?quot;,
quot;|quot;,
quot;~quot;] Even base classes are modifiable
  • 9. Metaprogramming • Or add new code as your program runs • define_method - specify new methods for your classes as needed • method_missing - catch-all method that can support infinite methods • eval - evaluate any Ruby code (be careful) • send - dynamically invoke methods by name.
  • 10. photo from Flickr user procsilas
  • 11. Active Record class
Conference
<
ActiveRecord::Base 
 has_many
:talks end
  • 12. AR Associations class
Conference
<
ActiveRecord::Base 
 has_many
:talks end adds to the class these methods (among others): c.talks 
 c.talks
<< 
 c.talks.find 
 c.talks.empty? 
 c.talks.create 
 ...
  • 13. ActiveRecord @talks
=
@conference.talks SELECT
*
FROM
talks
WHERE
 (talks.conference_id
=
1)
  • 14. has_many def
has_many(association_id,
options
=
{},
&extension) 
 reflection
=
create_has_many_reflection(association_id,
 
 
 
 
 
 
 
 
 
 
 
 
 

options,
&extension) 
 ... 
 collection_accessor_methods(reflection,
 
 
 
 
 
 
 
 
 
 
 
 
 

HasManyAssociation) end
  • 15. define_method def
collection_reader_method(reflection,
association_proxy_class) 
 define_method(reflection.name)
do
|*params| 
 
 association
=
instance_variable_get(quot;@#{reflection.name}quot;) 
 
 unless
association.respond_to?(:loaded?) 
 
 
 association
=
association_proxy_class.new(self,
reflection) 
 
 
 instance_variable_set(quot;@#{reflection.name}quot;,
association) 
 
 end 


 association 
 end end
  • 16. All Together Now def
collection_reader_method(reflection,
association_proxy_class) 
 define_method(reflection.name)
do
|*params| 
 
 association
=
instance_variable_get(quot;@#{reflection.name}quot;) 
 
 unless
association.respond_to?(:loaded?) 
 
 
 association
=
association_proxy_class.new(self,
reflection) 
 
 
 instance_variable_set(quot;@#{reflection.name}quot;,
association) 
 
 end Metaprogramming 


 association Blocks 
 end Reflection end
  • 17. defines methods class
Conference 
 def
talks(*params) 
 
 association
=
instance_variable_get(quot;@#{reflection.name}quot;) 
 
 unless
association.respond_to?(:loaded?) 
 
 
 association
=
HasManyAssociation.new(self,
reflection) 
 
 
 instance_variable_set(quot;@#{reflection.name}quot;,
 association) 
 
 end closure 
 
 association 
 end end
  • 18. About That SQL class
HasManyAssociation
<
AssociationCollection 
 def
initialize 
 
 construct_sql 
 end 
 def
construct_sql 
 
 ... 
 
 @finder_sql
=
quot;#{@reflection.klass.table_name}.# {@reflection.primary_key_name}
=
#{@owner.quoted_id}quot; 
 
 @finder_sql
<<
quot;
AND
(#{conditions})quot;
if
 conditions 
 end end
  • 19. method_missing Conference.find_all Conference.find_by_id Talk.find_all_by_name Talk.find_all_by_track Talk.find_by_day_and_track Speaker.find_by_name_and_hobby Speaker.find_all_by_zipcode
  • 20. Where Are Those From? • class
Talk
<
ActiveRecord::Base 

belongs_to
:conference end • No find methods added by script/generate • Nothing being added by define_to. • It even finds new columns right when I add them to the DB (cue spooky theremin music here)
  • 21. method_missing irb>
3.foo NoMethodError:
undefined
method
 `foo'
for
Fixnum:Class 







from
(irb):2 class
Object 
 def
method_missing(method_id,
*arguments) 
 
 throw
NoMethodError
... 
 end end
  • 22. method_missing class
ActiveRecord::Base def
method_missing(method_id,
*arguments) 
 if
match
=
/^find_(all_by|by)_([_a‐zA‐Z]w*)$/.match 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 (method_id.to_s) 
 
 finder
=
determine_finder(match) 
 
 attribute_names
=
extract_attribute_names_from_match(match) 
 
 super
unless
all_attributes_exists?(attribute_names) 
 
 attributes
=
construct_attributes_from_arguments
 (attribute_names,
arguments) 
 
 send(finder,
finder_options) 
 else 
 
 super 
 end end
  • 23. method_missing def
method_missing(method_id,
*arguments) 
 if
match
=
/^find_(all_by|by)_([_a‐zA‐Z]w*)$/.match 
 
 
 
 
 
 
 
 
is 
 
 
 
 
 
 (method_id.to_s) if method name find_* 
 
 finder
=
determine_finder(match) find all see if we should find one or 
 
 attribute_names
=
extract_attribute_names_from_match(match) 
 
 super
unless
all_attributes_exists?(attribute_names) extract columns to find by from name or extra arguments 
 
 attributes
=
construct_attributes_from_arguments
 (attribute_names,
arguments) call the finder with options, 
 
 send(finder,
finder_options) return results 
 else 
 
 super 
 end end else super ⇌ call Object's MM ⇌ NoMethodError
  • 24. Reflection
  • 25. Observers class
LocationObserver
<
ActiveRecord::Observer 
 def
before_save(location) 

 res
=
MultiGeocoder.geocode(location.address) 

 lat
=
res.lat 

 lng
=
res.lng 

 true 
 end end
  • 26. Calling My Observers #
when
your
app
calls
Location.save def
create_or_update_with_callbacks 
 return
false
if
callback(:before_save)
==
false 
 result
=
create_or_update_without_callbacks 
 callback(:after_save) 
 result end saves to the DB
  • 27. Doing The Callback :before_save def
callback(method) 
 callbacks_for(method).each
do
|callback| 
 
 ... 
 
 if
callback.respond_to?(method) 
 
 
 callback.send(method,
self) 
 
 end 

... end Callback is an object of some type
  • 28. Doing The Callback def
callback(:before_save) 
 callbacks_for(:before_save).each
do
|callback| 
 
 ... 
 
 if
callback.respond_to?(:before_save) 
 
 
 callback.send(:before_save,
self) 
 
 end 

... end Callback is your Observer
  • 29. Type Is Irrelevant Notice it's if
callback.respond_to?(:before_save) NOT if
callback.kind_of?(ActiveRecord::Observer)
  • 30. photo from Flickr user selva
  • 31. GarbageTruck acts_as_plow photo from Flickr user phrenologist
  • 32. Where Classic OOP Fails class
GarbageTruck
<
SnowPlow No Way! end class
GarbageTruck 
 include
Plowing No Better! end class
Plow
<
AbstractFrontAttachment class
Truck WTF? 
 def
add_attachment(attach_object) end class
GarbageTruck
<
Truck
  • 33. The Ruby Way class
GarbageTruck 
 acts_as_plow_maybe end def
acts_as_plow_maybe 
 if
snowing? 
 
 define_method('plow!')
do
|*params| 
 
 
... 
 
 end 
 end end
  • 34. Blocks
  • 35. λ aka
  • 36. Managing Resources transaction
do 
talk.add_attendee('Jake') 
conference.recalc_ranking! end
  • 37. def
transaction(start_db_transaction
=
true) 
 transaction_open
=
false 
 begin 
 
 if
block_given? 
 
 
 if
start_db_transaction 
 
 
 
 begin_db_transaction
 
 
 
 
 transaction_open
=
true 
 
 
 end executes your block 
 
 
 yield 
 
 end 
 rescue
Exception
=>
database_transaction_rollback 
 
 if
transaction_open 
 
 
 transaction_open
=
false 
 
 
 rollback_db_transaction 
 
 end 
 
 raise 
 end 
 ensure 
 
 commit_db_transaction
if
transaction_open end
  • 38. RESTful Responding respond_to
do
|format| 
format.html
#
index.rhtml 
format.xml
{
render
:xml 

 
 
 
 
 
 =>
@users.to_xml
} end
  • 39. RESTful Responding • Rails 1.2 allows you specify different actions for different formats requested by the caller (eg, page for HTML, feed for XML, etc.) • Response behavior based on complex logic: • Caller may explicitly specify in URL • Your app may have implicit priorities specified (eg, Atom before XML) • Rails may also have to decide on one based on client HTTP request headers
  • 40. RESTful Responding /talks => return the rendered index.rhtml /talks.xml => return XML format /talks.jpg => return HTTP Error 406 - “Not Acceptable”
  • 41. Content Negotation Accept:
 text/xml,application/xml,application/ xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/ png,*/*;q=0.5 HTTP/1.1 includes the following request-header fields for enabling server-driven negotiation through description of user agent capabilities and user preferences: Accept (section 14.1), Accept-Charset (section 14.2), Accept-Encoding (section 14.3), Accept- Language (section 14.4), and User-Agent (section 14.43). However, an origin server is not limited to these dimensions and MAY vary the response based on any aspect of the request, including information outside the request- header fields or within extension header fields not defined by this specification.
  • 42. Why Not A Case? case
format 
 when
:html 
 
 render
:html 
 when
:xml 
 
 render
:xml
=>
@users.to_xml end
  • 43. respond_to
do
|format| 
format.html
#
index.rhtml 
format.xml
{
render
:xml 

 
 
 
 
 
 =>
@users.to_xml
} end Outer Block - yields registry Inner Block - mime/type handler
  • 44. Registering a Handler format.xml
{
render
:xml
=>
@users.to_xml
} :xml { render :xml ... } class
ActionController::MimeResponds::Responder 
 def
method_missing(symbol,
&block) 
 
 mime_constant
=
symbol.to_s.upcase 







 
 
 if
Mime::SET.include?(Mime.const_get (mime_constant)) 
 
 
 custom(Mime.const_get(mime_constant),
&block) 
 
 else 


 
 super stores your block to 
 
 end execute for MIME match 
 end end
  • 45. Responding priority list of acceptable response MIME types def
respond 
 for
priority
in
@mime_type_priority 
 
 if
priority
===
@order find in your list of 
 
 
 @responses[priority].call blocks to respond_to 
 
 
 return
 
 
 
 #
mime
type
match
found,
be
happy
and
return 
 
 end 
 end 
 eval
'render(:nothing
=>
true,
:status
=>
quot;406
Not
 Acceptablequot;)',
@block_binding end error if no handlers
  • 46. Thank You
  • 47. www.nimblecode.com