Advanced Rails Studio Day 2 (Live-blogging)

Notes follow after the jump.

Metaprogramming Rails

  • How to write “magic” (ha!) such as belongs_to
  • “Is there a way to track down the file where a modification was made to a class?”
    • DT: Cause it to raise an error. Otherwise, no.
    • OUCH
  • DT whips up a little example showing how Rails implements Numeric.kilobytes
class Numeric
  def kilobytes
    self * 1024
  end
end
  • Of course, ActiveSupport just includes a definition that does something similar.
  • Question: does it speed up start up time to cherry pick what you want to load?
    • DT: It’s pretty trivial to load active_support in the grand scheme of loading Rails
  • DT describing how a class definition is executable code (recap of NoVaRUG presentation from April)
    • Just reopening ActiveRecord::Base and adding class methods. Waiting for the Adv Ruby class. :D
    • More simple metaprogramming examples. Goofing off while waiting for the harder core stuff.
  • They should’ve called this section ‘Ruby eye for the PHP guy’. Boooooring……
    • But, in the meantime, I have Markdown working in WordPress now. Yay.
  • Ok, ok, the “lib” directory in Rails is in the LOAD_PATH. Not a huge surprise but there ya go.
  • Adding methods dynamically at the class level
    • DT is describing the use of Object#extend. Honestly, I haven’t used that in anger before; I’ve always been a class << self; do_stuff; send guy. But, yeah, definitely simpler if you just want to add methods to the singleton object on a Class.
  • Decent question: What if you have a module with both class and instance methods?
    • Don’t use Object#extend (makes sense)
    • Have to use Module#include
    • In the implementing Module, Rails will look for a method called included that contains the methods that should be put in the class.
Module Foo
  def self.include(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def method_that_will_be_a_class_method_shortly
    end
  end
end
  • Where the hell is Module#included called from?
    • Awesome question: Why is foo.__send__ :include, module used in some places instead of include module?
  • Because the former doesn’t cause a class to load if it isn’t already loaded. Fucking cool! If only it was a little more obvious…

Break

  • DT: ”Kernel#eval is the work of the devil.”
  • Didn’t realize: Kernel#eval accepts an optional Binding (Kernel#binding)…
  • DT is giving some examples of Bindings. Surprised that this came up in a Rails class.
    • And just as I was starting to ask “What’s the difference between Bindings and Continuations?” (other than having a stack…), DT mentions that a “Continuation is a Binding on steroids” and will be discussed in the Adv Ruby Studio later this week. Bring on the pain!
  • And now Object#instance_eval
  • DT is giving a nice simple example of closures:
def twice
  yield
  yield
end

name = "Evan"
twice do
  puts "Hello #{name}"
end
  • name is defined outside the block before twice is invoked. The yield call is evaluating name based on it’s definition at the time of the twice call.

Another example:

def hello(name)
  lambda {
    puts "hello #{name}"
  }
end

c = hello "Evan"
c.call

name is out of scope at the time of the call but name is stil referenced by the block’s Binding.

And….

  def hello(name)
    count = 0
    lambda {
      puts "Hello, #{name} with count #{count}"
      count += 1
    }
  end

  c = hello "Evan"

  c.call
  c.call
  c.call
  c.call
  c.call

…returns:

  Hello, Evan with count 0
  Hello, Evan with count 1
  Hello, Evan with count 2
  Hello, Evan with count 3
  Hello, Evan with count 4

… because count is referenced by the block’s Binding

DT mentions later: blocks are evaluated only when they are called

  • Module#define_method

Take 1:

  def definer
    def hello
      puts "I've defined a method inside of the execution of another method"
    end
  end

  definer
  hello

Or the better way of doing it…

  class Definer
    def self.define
      define_method(:hello) do
        puts "I've defined a method inside of the execution of another method"
      end
    end
  end

  Definer.define
  Definer.new.hello

The above example invokes Module#define_method inside of a class because it’s a Module method; you can’t access it unless you’re inside of a Module (or Class because Class extends Module).

And here we use a closure within the example:

  class Definer
    def self.define(name)
      define_method(name) do |p|
        puts "I've defined a method called #{name} that was passed an arg #{p}"
      end
    end
  end

  Definer.define("hello")
  d = Definer.new
  d.hello "Evan"
  d.hello "Spock"

Nifty. The string that we’re writing gets evaluated when hello is called.

DT says “I use define_method all over the place”. Ok, ok, no more Object#eval for me. “This is a closer to God way of doing Ruby duplication”. Nice.

Note to self: Slide 129 in the slide package has the Module#define_method example.

DT: Ruby is slower than most scripting languages because (1) it’s not written for speed and (2) it’s completely OO and classes are open. DT: The overhead of define_method versus def is “microscopic”. Question: When would you use define_method? It’s a little different than code generation.

  • Will be discussed more in the Adv Ruby Studio (GOOD!)

Lunch

ActiveRecord

  • Joining Models
    • Discussing notion of creating relational objects to handle a many-to-many
    • has_many :through on both sides (simple-ish stuff so far)
    • Simple example of Magazine with Readers joined via Subscriptions.
    • Note to self: Slide 137
    • Question: “Is there anything forcing you to “has many :through” on both sides of a relationship?
    • Honest question but nope. Although the DB may expose a bi-directional relationship, the code doesn’t have to represent it.
    • Worth noting: adding a Magazine to the Reader will automatically create a Subscription because of the has_many :through
    • Most people in the room aren’t using HABTM often
    • Join Model or HABTM?
    • Users and Groups
    • Attendees and Events
    • None of the above sell me on HABTM
    • Products and Tags
      • Tagging?
    • Products and Related Products
      • HABTM? Maybe….
    • HABTM if there isn’t a good name? I don’t know… I shun HABTM. Besides, there’s almost always qualitative and/or quantitive information that’s associated with the relationship. That warrants a join model.
    • CF mentions using :include => [:registrations => :event] to eager load all of the events assocaited with each registration
    • MC believes that the use of :include generally causes more problems than it solves
    • Self-referential HABTM
    • Example with Person and having friends
    • FUGLY (sixs hash entries to supply to HABTM to add and remove bidirectional relationships automatically – see slide 141)
    • I suggested a Join Model approach with Friendship
    • DT: enforced bi-directional relationships should be maintained by triggers.
      • In the context where the data may persist indefinitely (i.e., enterprise), enforcing the data constraints exclusively within the application is nonsense.
      • No one in the class pointed this one out. I know that I, for one, lose sight of this.
    • DT, MC, and CF: Don’t put raw finds in your controller. Tightly couples controller to model implementation. BAD.
      • Good sense if you want to possibly swap out ORM implementations – which is quite possile if you’re using ActiveRecord.
    • CF: Shows the Friendship example which is better but still doesn’t let the DB be responsible for the relationship.
      • MC example:
class Person
  has_many :friendships, :dependent => :destroy

will call destroy on the the Friendship when the Person object is destroyed.

  * Again, triggers would handle it better....
  • Polymorphic Associations
class Person < ActiveRecord::Base
  has_many :addresses, :as => :addressable
end

class Company < ActiveRecord::Base
  has_many :addresses, :as => :addressable
en

class Address < ActiveRecord::Base
  belongs_to :addressable, :polymorphic => true
end
  • The addresses table then needs “addressableid” and “addressabletype” columns to indicate which row in which table (persons or companies) is related.
  • I’ve used these before and, really, polymorphic associations, in Rails, hurt!
    • I hate them.
  • Someone else mentions single table inheritance as an option. I’m not keen on that either but it is far less ugly than PA.
  • CF: If you use PA, you have to index the type field because you’re going to be querying it often.

    • Single-Table Inheritance
  • When you want to have an AR object inherit from another
  • Type field in the DB which contains the constant name of the type
  • Has all shared and distinct fields must be in the table
  • MC: Your STI table can get very wide if the classes in the family have little commonality…
  • CF: if you change the class name, you have to update the DB
  • CF: Ideally, prefer composition over inheritance
    • STI makes a strong case for composition – because STI can get very ugly…
  • CF: Don’t use a column called “type” in AR – ever; bizarre-ass error messages.

break

Refactorings

  • Starting with an example controller action with a couple of raw finds
  • Interesting use of a code block on the AssociationProxy.
    • Below example seems reasonable if this query (i.e., recent) is only valuable on this relationship
  has_many :visits do
    def recent(limit = 5)
      find(:all, ...)
    end
  end
  • Someone just pointed out that this doesn’t return an AssociationProxy (see here and find “proxy”)
    • For example, it’s easy to believe that event.registrations is an Array (if both represent AR::Base objects)
    • It even says it’s an Array when you call event.registrations.class
    • Calling event.registrations.ancestors reveals more
    • It’s actually an AssociationProxy
    • See AR association_proxy.rb
      • Delegates everything to method_missing and from there to a wrapped target
    • To extend an AssociationProxy with a module (in the example, called RecentFinder):
class User < ActiveRecord::Base
  has_many :vists, :extend => RecentFinder
end

… which makes the former code sample, where we define a method to the AssociationProxy, much cleaner.

Active Record Scoping

  • ActiveRecord::Base#with_scope creates a block where all contained finder calls on the class use the specified scope
    • Slides 154 and 155 – want to review this more later
  • Named Associations
  has_many :registrations

  has_many :preregistrations,
           :class_name => "Registration"
           :conditions => "pre_register is true"
  • These are cached (slightly evil that this is implicit – but a handy feature)
  • So a cool use is to nest scopes to and conditions together (but this isn’t cached – could suck performance wise for complex access control)
    • How many queries would we be performing? One per scope?
    • Nested calls to with_scope accumulate a single SQL query so could be super awesome for complex access control (depending upon the SQL)
      • To be useful, this would have to be FAST!
  • Slides 159-160 lend themselves awesomely to access control (except need to know how to mark the cache dirty to force a refresh)

break

Custom Form Builders

  • Delegating the markup generation cleans up form code
  • FormHelper#form_for takes a :builder argument.
  • Subclass of ActionView::Helpers::FormBuilder
  • When we call formfor :builder => MyBuilder, formfor yields with an instance of our builder as the argument.
  • Slide 177 contains the sample code for a simple LabelFormBuilder that dynamically generates methods that overrides much of the behavior o the DefaultFormBuilder but with our custom implementation.
    • That’s it. Unless I need a form with a lot of unique formatting (which I can’t see happening often), I’m writing a customer FormBuilder.
  • MC/CF: I asked how often these guys write custom FormBuilders.
    • Both use a custom FormBuilder in every project; they just may get someone who generates better UIs than they do to write it.
    • Unless I need a form with bizarre/unusual formatting, I’m going to be using one of these in the future.
  • They also suggest writing a helper to wrap form_for to factor out the :builder option.
  • ActionView:Base.defaultformbuilder=: Go assign your own default. :D

  • content_for

    • Page-specific content
    • JS
    • CSS includes
    • Contributing content (HTML) to the layout or any other template.
    • Smacks a little of goto…. so mighty but take care!

Posted by evan on Tuesday, May 06, 2008

blog comments powered by Disqus