49
Rails Under The Knife Jacob Harris The New York Times http://open.nytimes.com / http://www.nimblecode.com / [email protected] [email protected] harrisj on Flickr / Twitter / Del.icio.us / 43whatever / NYC.rb / Last.fm / etc.

Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Rails Under The KnifeJacob HarrisThe New York Timeshttp://open.nytimes.com/http://www.nimblecode.com/[email protected]@schizopolis.netharrisj on Flickr / Twitter / Del.icio.us /43whatever / NYC.rb / Last.fm / etc.

Page 2: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •
Page 3: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Things You Might Know• basic Ruby syntax

• object-oriented programming

• has_many:talks

• <%=fortin@talks%>

• and that it’s really @talks.eachdo|t|

• validates_presence_of:name

• defbefore_save(talk)

Page 4: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •
Page 5: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

• Three things you might kinda know:

• Blocks

• Reflection

• Metaprogramming

• Commonly called magic, but...

The Stuff of Magic

Page 6: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Code can and should be manipulated like data

Page 7: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

<%@talks.eachdo|t|%> <%=render_partial'talk',t%><%end%>

Blocks

Page 8: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Blocks

@talks.any?{|t|t.title=~/rails/i}

@talks.select{|t|sounds_cool?t}

@talks.inject{|mins,t|mins+=t.minutes}

Page 9: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Reflection

irb>(3.public_methods‐Object.public_methods).sort#=>["%","&","*","**","+","+@","‐","‐@","/","<<",">>","[]","^","abs","between?","ceil","chr","coerce","denominator","div","divmod","downto","floor","gcd","gcdlcm","id2name","integer?","lcm","modulo","next","nonzero?","numerator","power!","prec","prec_f","prec_i","quo","rdiv","remainder","round","rpower","singleton_method_added","size","step","succ","times","to_bn","to_f","to_i","to_int","to_r","to_sym","truncate","upto","zero?","|","~"]

Page 10: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Metaprogrammingconsole>>(3.public_methods‐Object.public_methods).sort#=>["%","&","*","**","+","+@","‐","‐@","/","<<",">>","[]","^","abs","ago","between?","byte","bytes","ceil","chr","coerce","day","days","denominator","div","divmod","downto","even?","exabyte","exabytes","floor","fortnight","fortnights","from_now","gcd","gcdlcm","gigabyte","gigabytes","hour","hours","id2name","integer?","kilobyte","kilobytes","lcm","megabyte","megabytes","minute","minutes","modulo","month","months","multiple_of?","next","nonzero?","numerator","odd?","ordinalize","petabyte","petabytes","power!","prec","prec_f","prec_i","quo","rdiv","remainder","round","rpower","second","seconds","since","singleton_method_added","size","step","succ","terabyte","terabytes","times","to_bn","to_f","to_i","to_int","to_r","to_sym","truncate","until","upto","week","weeks","xchr","year","years","zero?","|","~"]

Even base classes are modifiable

Page 11: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

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.

Page 12: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

photo from Flickr user procsilas

Page 13: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

classConference<ActiveRecord::Base has_many:talksend

Active Record

Page 14: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

AR AssociationsclassConference<ActiveRecord::Base has_many:talksend

adds to the class these methods (among others): c.talks c.talks<< c.talks.find c.talks.empty? c.talks.create ...

Page 15: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

@[email protected]

SELECT*FROMtalksWHERE(talks.conference_id=1)

ActiveRecord

Page 16: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

defhas_many(association_id,options={},&extension) reflection=create_has_many_reflection(association_id, options,&extension) ... collection_accessor_methods(reflection, HasManyAssociation)end

has_many

Page 17: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

defcollection_reader_method(reflection,association_proxy_class) define_method(reflection.name)do|*params| association=instance_variable_get("@#{reflection.name}")

unlessassociation.respond_to?(:loaded?) association=association_proxy_class.new(self,reflection) instance_variable_set("@#{reflection.name}",association) end

association endend

define_method

Page 18: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

defcollection_reader_method(reflection,association_proxy_class) define_method(reflection.name)do|*params| association=instance_variable_get("@#{reflection.name}")

unlessassociation.respond_to?(:loaded?) association=association_proxy_class.new(self,reflection) instance_variable_set("@#{reflection.name}",association) end

association endend

All Together Now

MetaprogrammingBlocksReflection

Page 19: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

defines methodsclassConference deftalks(*params) association=instance_variable_get("@#{reflection.name}")

unlessassociation.respond_to?(:loaded?) association=HasManyAssociation.new(self,reflection) instance_variable_set("@#{reflection.name}",association) end

association endend

closure

Page 20: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

About That SQLclassHasManyAssociation<AssociationCollectiondefinitializeconstruct_sqlend

defconstruct_sql...@finder_sql="#{@reflection.klass.table_name}.#{@reflection.primary_key_name}=#{@owner.quoted_id}"@finder_sql<<"AND(#{conditions})"ifconditionsendend

Page 21: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Conference.find_allConference.find_by_idTalk.find_all_by_nameTalk.find_all_by_trackTalk.find_by_day_and_trackSpeaker.find_by_name_and_hobbySpeaker.find_all_by_zipcode

method_missing

Page 22: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

• classTalk<ActiveRecord::Basebelongs_to:conferenceend

• 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)

Where Are Those From?

Page 23: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

method_missingirb>3.fooNoMethodError:undefinedmethod`foo'forFixnum:Classfrom(irb):2

classObjectdefmethod_missing(method_id,*arguments)throwNoMethodError...endend

Page 24: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

classActiveRecord::Basedefmethod_missing(method_id,*arguments) ifmatch=/^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) superunlessall_attributes_exists?(attribute_names)

attributes=construct_attributes_from_arguments(attribute_names,arguments)

send(finder,finder_options) else super endend

method_missing

Page 25: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

defmethod_missing(method_id,*arguments) ifmatch=/^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) superunlessall_attributes_exists?(attribute_names)

attributes=construct_attributes_from_arguments(attribute_names,arguments)

send(finder,finder_options) else super endend

method_missing

if method name is find_*

see if we should find one or find all

extract columns to find by from name or extra arguments

call the finder with options, return results

else super ⇌ call Object's MM ⇌ NoMethodError

Page 26: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Reflection

Page 27: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

classLocationObserver<ActiveRecord::Observer defbefore_save(location) res=MultiGeocoder.geocode(location.address) lat=res.lat lng=res.lng true endend

Observers

Page 28: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Calling My Observers

#whenyourappcallsLocation.savedefcreate_or_update_with_callbacks returnfalseifcallback(:before_save)==false result=create_or_update_without_callbacks callback(:after_save) resultend saves to the DB

Page 29: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

defcallback(method) callbacks_for(method).eachdo|callback| ... ifcallback.respond_to?(method) callback.send(method,self) end...end

Doing The Callback

:before_save

Callback is anobject of some type

Page 30: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

defcallback(:before_save) callbacks_for(:before_save).eachdo|callback| ... ifcallback.respond_to?(:before_save) callback.send(:before_save,self) end...end

Doing The Callback

Callback is your Observer

Page 31: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Type Is Irrelevant

Notice it's

ifcallback.respond_to?(:before_save)

NOT

ifcallback.kind_of?(ActiveRecord::Observer)

Page 32: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

photo from Flickr user selva

Page 33: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

photo from Flickr user phrenologist

GarbageTruck acts_as_plow

Page 34: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Where Classic OOP FailsclassGarbageTruck<SnowPlowend

classGarbageTruck includePlowingend

classPlow<AbstractFrontAttachmentclassTruck defadd_attachment(attach_object)endclassGarbageTruck<Truck

No Way!

No Better!

WTF?

Page 35: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

The Ruby WayclassGarbageTruckacts_as_plow_maybeend

defacts_as_plow_maybeifsnowing? define_method('plow!')do|*params| ... endendend

Page 36: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Blocks

Page 37: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

λaka

Page 38: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

transactiondotalk.add_attendee('Jake')conference.recalc_ranking!end

Managing Resources

Page 39: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

deftransaction(start_db_transaction=true) transaction_open=false begin ifblock_given? ifstart_db_transaction begin_db_transaction transaction_open=true end yield end rescueException=>database_transaction_rollback iftransaction_open transaction_open=false rollback_db_transaction end raise end ensure commit_db_transactioniftransaction_openend

executes your block

Page 40: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

respond_todo|format|format.html#index.rhtmlformat.xml{render:xml =>@users.to_xml}end

RESTful Responding

Page 41: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

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

Page 42: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

RESTful Responding

/talks

=> return the rendered index.rhtml

/talks.xml

=> return XML format

/talks.jpg

=> return HTTP Error 406 - “Not Acceptable”

Page 43: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Content NegotationAccept: 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.

Page 44: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Why Not A Case?

caseformat when:html render:html when:xml render:xml=>@users.to_xmlend

Page 45: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

respond_todo|format|format.html#index.rhtmlformat.xml{render:xml =>@users.to_xml}end

Outer Block - yields registry

Inner Block - mime/type handler

Page 46: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

format.xml{render:xml=>@users.to_xml}

classActionController::MimeResponds::Responder defmethod_missing(symbol,&block) mime_constant=symbol.to_s.upcase ifMime::SET.include?(Mime.const_get(mime_constant)) custom(Mime.const_get(mime_constant),&block) else super end endend

Registering a Handler:xml { render :xml ... }

stores your block to execute for MIME match

Page 47: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

defrespond forpriorityin@mime_type_priority ifpriority===@order @responses[priority].call return #mimetypematchfound,behappyandreturn end end

eval'render(:nothing=>true,:status=>"406NotAcceptable")',@block_bindingend

Respondingpriority list of acceptableresponse MIME types

find in your list ofblocks to respond_to

error if no handlers

Page 48: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

Thank You

Page 49: Rails Under The Knifes3.amazonaws.com/harrisj-share/oscon2007.pdf · Things You Might Know • basic Ruby syntax • object-oriented programming • has_many :talks •

www.nimblecode.com