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.
- More simple metaprogramming examples. Goofing off while waiting for the harder core stuff.
- Just reopening ActiveRecord::Base and adding class methods. Waiting for the Adv Ruby class.
- 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 aclass << self; do_stuff; sendguy. But, yeah, definitely simpler if you just want to add methods to the singleton object on a Class.
- DT is describing the use of
- 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
includedthat contains the methods that should be put in the class.
- Don’t use
Module Foo
def self.include(base)
base.extend(ClassMethods)
end
module ClassMethods
def methodthatwillbeaclassmethod_shortly
end
end
end
- Where the hell is
Module#includedcalled from?- Awesome question: Why is
foo.__send__ :include, moduleused in some places instead ofinclude module?
- Awesome question: Why is
- 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#evalis the work of the devil.” - Didn’t realize:
Kernel#evalaccepts 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
twicecall.
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 definemethod versus def is “microscopic”. Question: When would you use definemethod? 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 endclass 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.ancestorsreveals 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_scopecreates 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 withscope 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_fortakes 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.
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!


2 comments ↓
Regarding your comments on using with_scope for access control, and needing to figure out how to mark as dirty …
You might want to take a look at the “namedscope” functionality that is going to be in Rails 2.1. It’s essentially a wrapper for withscope, that returns an association proxy.
You can define a named_scope using a lambda, which presents as a method, and won’t be cached indefinitely
namedscope :belongingto, lambda {|user| { :conditions => { :user_id => user.id }}}
named_scope :public, :conditions => { :public => true }
Called like Tags.belongingto(currentuser) Tags.belongingto(currentuser).find(:all, :limit => 10) Tags.public.belonging_to(bob)
It’s backported inside the willpaginate gem, and was originally called hasfinder.
http://ryandaigle.com/articles/2008/3/24/what-s-new-in-edge-rails-has-finder-functionality http://mislav.caboo.se/rails/will_paginate-love/
[...] week, but still worth mentioning is this series of Notes from the Advanced Rails Studio (part 1, 2 and [...]
Leave a Comment