Automating Google Workspace (GWS) & more with Apps Script
Â
Os Harris
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.
3. 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)
4.
5. The Stuff of Magic
⢠Three things you
might kinda know:
⢠Blocks
⢠ReďŹection
⢠Metaprogramming
⢠Commonly called
magic, but...
11. 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 inďŹnite methods
⢠eval - evaluate any Ruby code (be careful)
⢠send - dynamically invoke methods by name.
18. 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
ReďŹection
end
19. deďŹnes 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
20. 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
22. Where Are Those From?
⢠classâŠTalkâŠ<âŠActiveRecord::Base
âŠâŠbelongs_toâŠ:conference
end
⢠No ďŹnd methods added by script/generate
⢠Nothing being added by define_to.
⢠It even ďŹnds new columns right when I add
them to the DB (cue spooky theremin music here)
25. 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 ďŹnd_*
⊠⊠finderâŠ=âŠdetermine_finder(match) ďŹnd all
see if we should ďŹnd one or
⊠⊠attribute_namesâŠ=âŠextract_attribute_names_from_match(match)
⊠⊠superâŠunlessâŠall_attributes_exists?(attribute_names)
extract columns to ďŹnd by from
name or extra arguments
⊠⊠attributesâŠ=âŠconstruct_attributes_from_argumentsâŠ
(attribute_names,âŠarguments)
call the ďŹnder with options,
⊠⊠send(finder,âŠfinder_options)
return results
⊠else
⊠⊠super
⊠end
end else super â call Object's MM â NoMethodError
29. 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
30. 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
31. Type Is Irrelevant
Notice it's
ifâŠcallback.respond_to?(:before_save)
NOT
ifâŠcallback.kind_of?(ActiveRecord::Observer)
34. 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
35. 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
41. 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
speciďŹed (eg, Atom before XML)
⢠Rails may also have to decide on one
based on client HTTP request headers
42. RESTful Responding
/talks
=> return the rendered index.rhtml
/talks.xml
=> return XML format
/talks.jpg
=> return HTTP Error 406 - âNot Acceptableâ
43. 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 ďŹelds 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 ďŹelds or within extension header ďŹelds not deďŹned by this
speciďŹcation.
44. Why Not A Case?
caseâŠformat
⊠whenâŠ:html
⊠⊠renderâŠ:html
⊠whenâŠ:xml
⊠⊠renderâŠ:xmlâŠ=>âŠ@users.to_xml
end
46. 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
47. Responding
priority list of acceptable
response MIME types
defâŠrespond
⊠forâŠpriorityâŠinâŠ@mime_type_priority
⊠⊠ifâŠpriorityâŠ===âŠ@order ďŹnd 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