Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
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
• Reflection
• 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 infinite 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
Reflection
end
19. 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
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 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)
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 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
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
39. 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
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
specified (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 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.
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 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