Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Tools
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker
Adam Milligan

#method_missing makes me eat my words

Adam Milligan
Saturday, December 27, 2008

A while back I wrote about private methods in ActiveRecord objects, and how Rails 2.2 makes them behave as they should. ActiveRecord associations will no longer respond to private methods defined on their targets; however, my colleague Joseph pointed out that they also no longer respond to methods defined via #method_missing on their targets. Which sucks horse poop through a straw, to some extent.

In my original post I wrote this, fully aware that I was writing lies, but mostly in a rush to get on to my point at the time:

Sometimes I’m forced to use #send:

method_name = extract_method_name_from_the_aether
some_object.send(method_name)

In order to make this code correct with regard to access control I have to add cruft:

method_name = extract_method_name_from_the_aether
some_object.send(method_name) if some_object.respond_to?(method_name)

The astute reader will note that the second code block doesn’t actually approximate the behavior of calling a private method via function call syntax. It should look more like this:

method_name = extract_method_name_from_the_aether
raise NoMethodError if some_object.private_methods.include?(method_name)
some_object.send(method_name)

Two things to notice here: first, the original code didn’t throw an exception if it failed to call the method; second, and much more importantly, the set of private methods is not the complement of the set of methods an object will respond to. So, using !#respond_to? as the condition for preventing the method call is too restrictive. The fact that this code will prevent calling a non-private method defined via #method_missing clearly shows this.

Now, I know the Rails core team chose to use #respond_to? in this particular case for a good reason (which my original patch did not take into consideration): performance. Some simple investigation quickly shows that #include? is an order of magnitude slower than #respond_to? Method calls happen quite a lot, so slow is bad.

I’ve submitted another patch that should correct the #method_missing behavior without dragging everything to a halt. Building on my previous examples, the code looks something like this:

method_name = extract_method_name_from_the_aether
raise NoMethodError if !some_object.respond_to?(method_name) && some_object.private_methods.include?(method_name)
some_object.send(method_name)

Note that the conditional expression is now redundant; the first condition cannot be false if the second condition is true. However, an object will likely respond to the majority of method calls sent to it. The first conditions will (quickly) evaluate to false in these cases, short-circuiting the conditional. In the minority of cases where the first condition evaluates to true, the second condition will (slowly, but correctly) determine if the method call violates access control.

Unfortunately, sensible as it may seem, this change actually breaks has_one :through associations, because of collection-specific functionality they inherit from has_many :through associations. (Oh yes, has_one :through associations are collections, based on their place in the ActiveRecord inheritance hierarchy. Or, they were; read more about that here).

And, finally, I think it’s important to note that none of these machinations in Rails would be necessary if #send (and #send!) actually worked as it should. Heads up, Matz.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Adam Milligan

ActiveRecord learns to respect your privates

Adam Milligan
Tuesday, December 2, 2008

This is somewhat old news, but I don’t think it has received the attention it deserves. As of Rails 2.2, ActiveRecord associations and attributes will now behave properly with regard to access control. You can view the Rails tickets, with patches, here and here.

Take, for example, this schema:

mysql> desc accounts;
+----------------+--------------+------+-----+---------+----------------+
| Field          | Type         | Null | Key | Default | Extra          |
+----------------+--------------+------+-----+---------+----------------+
| id             | int(11)      | NO   | PRI | NULL    | auto_increment |
| balance        | int(11)      | NO   |     | 0       |                |
+----------------+--------------+------+-----+---------+----------------+

used by this model:

class Account < ActiveRecord::Base
  def deposit(amount)
    do_state_and_federally_mandated_things
    balance += amount
  end

  def withdraw(amount)
    if sufficient_funds?(amount)
      do_state_and_federally_mandated_things
      balance -= amount
    end
  end

private :balance=

private

  def do_state_and_federally_mandated_things
    ...
  end
end

You most likely don’t want someone coming along and modifying the balance attribute directly, either intentionally or inadvertently. However, prior to Rails 2.2, ActiveRecord ignores the privacy declaration for #balance=, so you must execute horrid machinations in order to protect it:

class Account < ActiveRecord::Base
  def balance=(amount)
    raise "I'm private!"
  end

  def deposit(amount)
    do_state_and_federally_mandated_things
    write_attribute(:balance, balance + amount)
  end

  ...

As of Rails 2.2, ActiveRecord will respect private accessors for database column attributes.

Along the same vein, if we add the following:

class User < ActiveRecord::Base
  has_one :account
end

Now we can call methods on the proxy returned by calling User#account, just as if we were calling methods on an account instance; any methods:

johnny_taxpayer = User.first
johnny_taxpayer.account.withdraw(700_000_000_000)
johnny_taxpayer.account.do_state_and_federally_mandated_things

Who knows what scary things #do_state_and_federally_mandated_things does? This will, frighteningly, run just fine prior to Rails 2.2. But, no more.

Now, a number of people have considered this change and asked “why bother?” Ruby allows access to private methods via #send, so they’re not really private, right?

This argument leads down the dark path. Like it or not, cheat around it as you may, access control is an important aspect of object oriented programming. If nothing else, the private keyword is my way of saying “hic sunt dracones,” or “hands off!” It’s also a way of saying “this method may or may not exist in the future.” As a class designer I have every right to refactor that private method entirely away, thus breaking any code that calls it; including, significantly, test code.

More fundamentally, object interactions should be via interfaces. If code makes calls to an object’s private methods, that code has now tied itself to the object’s implementation. Coupling ensues, duck-typing breaks down, anarchy reigns.

So, this begs the question, why does #send ignore access control?. Why should two forms of sending a message to an object differ in their access control semantics? Sometimes I’m forced to use #send:

method_name = extract_method_name_from_the_aether
some_object.send(method_name)

In order to make this code correct with regard to access control I have to add cruft:

method_name = extract_method_name_from_the_aether
some_object.send(method_name) if some_object.respond_to?(method_name)

Now, this is not to say that I don’t think Ruby should provide the ability to call private methods, or generally dig around in an object’s internals. This comes in handy in some instances (although I find many of these instances are short-term solutions that should get refactored away). But, in an ideal world I think the sender should explicitly specify when a message should ignore access control:

potentially_private = extract_method_name_from_the_aether
some_object.send_without_restriction(potentially_private)

This makes the syntax somewhat more ugly, but ugly syntax suits an ugly operation.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Standup 11/18/2008: Unbelievable has_many :through Gotcha

Joe Moore
Tuesday, November 18, 2008
  • One team discovered a jaw-dropping issue with has_many :through. Given the following:

    class User < ActiveRecord::Base
      has_many :user_photos
      has_many :photos, :through => :user_photos
    
    • a_user.photos.create will create and persist both a Photo object and the UserPhoto join object
    • photo = a_user.photos.build followed by photo.save will create and persist the Photo object only, and will not persist an appropriate UserPhoto join object.
  • Rails 2.2: Test::Unit::TestCase extentions have been removed from Rails Core and are now in ActiveSupport::TestCase. As stated in the Groups Thread about this, use ActiveSupport::TestCase instead of Test::Unit::TestCase in test/test_helper.rb.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Standup 11/07/2008: Selenium for Flash

Joe Moore
Friday, November 7, 2008

Interesting Things

  • Teaser: Selenium for Flash! We’ve developed a Selenium-like framework for Flash. It’s pre-alpha, and needs to be extracted from it’s current home inside a project. Are you interested in a Selenium-like framework for Flash, or have you written one yourself? Let us know!
  • STI-weirdness. Rails surprise of the day: given a query of a has_many :photos where Photos has STI subclasses (got that?) Rails will build a SQL query that includes the subclass types of Photo, which you might not want:

    foo.photos.find_by_type("Photo")
    # query will have "... WHERE type IN ('Photo', 'OriginalPhoto', 'ThumbnailPhoto')"
    
  • It appears that the retardase_inhibitor might not work with Rails 2.1.X due to fixes in ActionMailer.

  • JetBrains has been hard at work: they have released both a new Ruby plugin for IntelliJ, and a ruby-specific IDE (based on IntelliJ) named RubyMine.
  • Check out Pivot Jonathan’s wife’s art exhibit at Artist-Xchange Gallery in San Francisco, Friday 11/7 from 7-10pm:

    Artist-Xchange Gallery
    3169 16th Street
    San Francisco
    CA 94103

Ask for Help

“I want to create a custom launcher for Firefox 2 and Firefox 3 with different profiles. Perhaps the real question is how do we create a custom version of a Mac application launcher, passing in the arguments we need?”

… without having to invoke it on the command line every time.

“We’re trying to delete cookies in our Controller, but they keep appearing in the headers anyway.”

Suggestion: make sure you are specifying your URL paths and domains correctly.

“Why won’t our CSS and other assets load the first time when accessing an SSL-protected domain on Engine Yard?”

It’s most likely not Engine Yard or Firefox 3′s fault. More research needed.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Standup 10/15/2008: this_method; dynamically creating tables for testing

Joe Moore
Wednesday, October 15, 2008
  • Where am I? — Ever need to find the name of the method you are currently within? Here’s a this_method method! The magic is in the REGEX, of course.
module Kernel
  private
  def this_method
    caller[0] =~ /`([^']*)'/ and $1
  end
end
  • One project wanted to test a very ActiveRecord-specific Module in an isolated, generic way. After spending time researching techniques of mocking and stubbing the many, many ActiveRecord methods that would be touched, they decided to just dynamically create an ActiveRecord and a DB Table for it on the fly! They even used single table inheritance (STI)
  describe "MyMagicModule Mixin" do
    before(:all) do
      ActiveRecord::Base.connection.create_table "some_base_models",
                                                   :force => true do |t|
        t.string   "name"
        t.string   "type"
        t.integer  "some_model_b_id", :limit => 11
      end
    end

    after(:all) do
      ActiveRecord::Base.connection.drop_table "some_base_models"
    end


    class SomeBaseModel < ActiveRecord::Base;end

    class SomeModelA < SomeBaseModel
      include MyMagicModule

      belongs_to: :some_model_b
    end

    class SomeModelB < SomeBaseModel
      include MyMagicModule
    end

    it 'should use special belongs_to stuff from MyMagicModule' do
       model_a = SomeModelA.create!(
                        :name=> "Model A",
                        :some_model_be => SomeModelB.create!(:name => "Model B"))
       # test the functionality from MyMagicModule
    end
  end
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
David Stevenson

Pivots patch rails: named_scope with the :joins can cause table aliasing issues

David Stevenson
Monday, September 29, 2008

In order to accomplish some advanced search functionality, we’ve added a lot of named_scopes to our User model. This seems like a good idea, and well within the intended use for named_scopes. Unfortunately, we ran into issues with our :joins. We have a separate User and Profile model, but our advanced search scopes often needed both to make decisions. So we had some scopes that look like this:

class User
  named_scope :verified {
    :conditions => {:email_verified => true}
  }

  named_scope :answered_questions {
    :join => "INNER JOIN profiles ON profiles.user_id = users.id " +
                 "INNER JOIN answers ON answers.profile_id = profiles.id"
  }

  named_scope :with_name { lambda { |name|
    :join => "INNER JOIN profiles ON profiles.user_id = users.id",
    :conditions => ["profiles.name LIKE ?", "%#{name}%"]
  } }
end

Using these named_scopes, we wanted to dynamically construct a finder that would return the results the user was interested, such as: User.verified or User.answered_questions or even User.verified.answered_questions.with_name('Joseph'). The last scope caused issues, unfortunately, with table aliasing. The query ended up joining in the profiles table twice, in exactly the same way without renaming the table, so mysql rejects the query.

The easiest solution to this problem was to use only the hash form for :join clauses, such as :join => :profile. Rails correctly merges multiple consecutive join scopes that use hashes. If you need to use string joins (such as a LEFT JOIN rather than an INNER JOIN) or put a condition directly on your join, then merging goes out the window and the hashed form is immediately converted to a string and all consecutive joins are “merged” by appending them together.

We started by manually aliasing our scopes, but in some cases we were concerned about the amount of duplicate data this was causing in our queries.

We thought about creating a dependency framework for named_scopes, such that you could have a single :profile scope that other scopes were dependent on and it would only ever get added once. This seemed really difficult because of the way the with_scopes are constructed by named_scopes, there was no good place to keep track of these dependencies, and it would still cause problems if you had a manual with_scope, or :join in your find.

Finally we decided that rails fundamentally lacked the capability to deal with duplicate joins, and that we should solve this problem. It seemed a good solution was to allow :join options to take an array of strings as follows:

  named_scope :answered_questions {
    :join => ["INNER JOIN profiles ON profiles.user_id = users.id",
                 "INNER JOIN answers ON answers.profile_id = profiles.id"]
  }

Now calling User.answered_questions.with_name('Joseph') will create three values in a :join array, two of which are identical and will be uniq’d out. The downside to this approach is that each value in the :join array has to be string identical, or it will not be properly uniq’d.

So if you are mixing hash style :profile joins with string joins of the same table you need to be careful you match the rails generated syntax. We mostly use string style joins to avoid this issue.

Here’s the ticket the we filed and patched:
1077-chaining-scopes-with-duplicate-joins-causes-alias-problem

It has been commited and will roll out with rails 2.2. Since then we have filed two more issues related to :join and :include:

  • 1078-using-include-assoc-and-join-assoc-leads-to-alias-issue
  • 1104-references_eager_loaded_tables-should-search-tables-in-join-clauses

We hope to patch these two as well!

Joseph & David

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Ruby Pearls vol. 1 – The Splat

Pivotal Labs
Wednesday, April 23, 2008

Over the next week or so I’ll be sharing Ruby idioms and flourishes that I quite like. Today I’d I’ll show a few tiny uses of splat! that make me tremble with delight.

Splat! – For Beginners

Splat! is the star (*) operator, typically used in Ruby for defining methods that take an unlimited number of arguments:

def sprintf(string, *args)
end

It can also be used to convert an array to the multiple-argument form when invoking a function:

some_ints = [1,2,3]
sprintf("%%i %%i %%i", *some_ints)

Splat! – For Wizards

Array to Hash Conversion

The best use of splat! for invoking a infinite-arity functions I’ve ever seen is the recipe for converting an array to a hash. Suppose you have an array of pairs:

array = [[key_1, value_1], [key_2, value_2], ... [key_n, value_n]]

You would like to produce from it the hash: {key1 => value1 ... } You could inject down the array, everybody loves inject, but there is a better way:

Hash[*array.flatten]

Amazing right? This relies on the the fact that the Hash class implements the [] (brackets) operator and behaves thusly:

Hash[key1, value1, ...] = { key1 => value1, ... }

Heads or tails?

Splat! can be used for more than just method definition and invocation. My personal favorite use is destructuring assignment. I read this in Active Record’s source code recently:

  def sanitize_sql_array(ary)
    statement, *values = ary
    ...
  end

This is invoked when you do something like User.find(:all, :conditions => ['first_name = ? and last_name = ?', 'nick', 'kallen']). Splat! is used here is to get the head and tail of the conditions array. Of course, you could use always use shift, but the functional style used here is quite beautiful. Consider another example:

first, second, *rest = ary

One final trivium (#to_splat aka #to_ary)

You can actually customize the behavior of the splat operator. In Ruby 1.8, implement #to_ary and in 1.9 it’s #to_splat. For example

class Foo
  def to_ary
    [1,2,3]
  end
end

a, *b = Foo.new
a # => 1
b # => [2,3]

This also works for method invocation:

some_method(*Foo.new) == some_method(1,2,3)

When I first learned this at RubyConf I thought this was mind-blowing. I have since never used it.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

alias_method_chain :validates_associated, :informative_error_message

Pivotal Labs
Friday, January 4, 2008

I dislike the vague error message produced by validates_associated.

class User
  validates_associated :profile
  delegate ..., :to => :profile
end

I see the following error message: profile is invalid. But WHY was the profile invalid? The validation errors from the profile should bubble up to the user. So,

module ActiveRecord::Validations::ClassMethods
  def validates_associated(association, options = {})
    class_eval do
      validates_each(association) do |record, associate_name, value|
        associate = record.send(associate_name)
        if associate && !associate.valid?
          associate.errors.each do |key, value|
            record.errors.add(key, value)
          end
        end
      end
    end
  end
end

Now we see:

Music tastes can't be blank

Eh, voila!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

dot.rake

Alex Chaffee
Monday, October 15, 2007

[Update: 10/15/07 - incorporated changes by David Vrensk (and a few more from me). Now it merges in associations into the arc, and also deals with inheritance (e.g. STI).]

While googling for articles on Rails associations, I happened upon
this gem of a script by Matt Biddulph. I loved it so much I made it a rake task! Once you install GraphViz like this:

sudo port install graphviz

and put dot.rake in your lib/tasks directory, then running this:

rake dot

produces diagrams like this:

BBC Programme Catalogue codebase

And you can also import the DOT source into OmniGraffle for further editing, like this:

open -a "OmniGraffle" model.dot

Here’s the source for dot.rake:

# dot.rake
# Creates a DOT format file showing the model objects and their associations
# Authors:
#   Matt Biddulph - http://www.hackdiary.com/archives/000093.html
#   Alex Chaffee - http://www.pivotalblabs.com/articles/2007/09/29/dot-rake
#   David Vrensk - david@vrensk.com
# Usage:
#  rake dot
#  To open in OmniGraffle, run
#    open -a 'OmniGraffle' model.dot
#  or
#    open -a 'OmniGraffle Professional' model.dot

desc "Generate a DOT diagram of the ActiveRecord model objects in 'model.dot'"
task :dot => :environment do
  Dir.glob("app/models/*rb") { |f|
    require f
  }
  File.open("model.dot", "w") do |out|
    out.puts "digraph x {"
    #out.puts "tnode [fontname=Helvetica,fontcolor=blue]"
    out.puts "tnode [fontname=Helvetica]"
    out.puts "tedge [fontname=Helvetica,fontsize=10]"
    Dir.glob("app/models/*rb") { |f|
      f.match(//([a-z_]+).rb/)
      classname = $1.camelize
      klass = Kernel.const_get classname
      if (klass.class != Module) && (klass.ancestors.include? ActiveRecord::Base)
        if klass.include? ActiveRecord::Acts::List::InstanceMethods
          scope = klass.new.scope_condition.sub(/(_id?) .*/,'').camelize
          out.puts "t#{classname} [label="#{classname}n(list in #{scope})"]"
        elsif klass.superclass != ActiveRecord::Base
          out.puts "t#{classname} -> #{klass.superclass.name} [arrowhead=empty]"
        else
          out.puts "t#{classname}"
        end
        klass.reflect_on_all_associations.select { |a| a.macro.to_s.starts_with? 'has_' }.each do |a|
          target = a.name.to_s.camelize.singularize
          if a.klass.name != target
            target = a.klass.name
            label = ",label="as #{a.name}""
          else
            label =""
          end
          case a.macro.to_s
          when 'has_many'
            out.puts "t#{classname} -> #{target} [arrowhead=crow#{label}]"
          when 'has_and_belongs_to_many'
            out.puts "t#{classname} -> #{target} [arrowhead=crow,arrowtail=crow#{label}]" if classname < target
          when 'has_one'
            out.puts "t#{classname} -> #{target} [arrowhead=diamond#{label}]"
          else
            $stderr.puts "No support for #{a.macro.to_s} in #{classname}"
          end
        end
      end
    }
    out.puts "}"
  end
  system "dot -Tpng model.dot -o model.png"
  system "/Applications/Graphviz.app/Contents/MacOS/dot -Tpng model.dot -o model.png" unless $?.success?
  puts "Could not write model.png. Please install graphviz (http://www.graphviz.org)." unless $?.success?
end

Put that in a file called “dot.rake” and put it in your lib/tasks directory and Dot’s your uncle. Aunt. Whatever…

Any suggestions? Should I be writing the output file to a subdirectory, like maybe db, instead?

(BTW, it looks like OmniGraffle doesn’t support the font style features of DOT, so all the nodes are 12-point black on import :-( .)

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Advanced Proxy Usage, Part I

Pivotal Labs
Wednesday, August 8, 2007

One of the more underutilized features of ActiveRecord is the Assocation Proxy. But they are also one of the most powerful weapons in the ActiveRecord armory, and Rails apps that take advantage of them are better organized and easier to maintain.

What is a Proxy?

When in an ActiveRecord you declare an Association:

class Hand < ActiveRecord::Base
  has_many :fingers
end

Instances of Hand now have a fingers method. Contrary to appearances, and contrary to the LIE told to you by hand.fingers.class, the fingers method does not return an Array of Fingers. Rather it returns a Proxy object, one that smells and tastes like an Array of fingers but actually has a rich creamy behavior all its own.

Scoped Access

The most basic use of Proxies is to “scope” the reading and writing of your ActiveRecords. For example, if you have a controller that allows CRUD on a User’s Assets, you can read and write to the collection of Assets as in the following examples:

@asset = current_user.assets.find(params[:id])
@asset = current_user.assets.create(params[:asset])
@asset = current_user.assets.build(params[:asset]) # equivalent to 'new'-ing an object rather than 'create'-ing it.

There are other ways of doing this, of course:

@asset = Asset.create({:user => current_user}.merge(params[:asset))

But the Proxy Code is much better: not only is the Proxy code terse, but it meaningfully expresses the relationship between objects in your domain: Users have many assets; this Asset is created in the context of this User.

Special Queries (or Custom Finders)

The various Association declarations--has_many, belongs_to, etc.--allow you to express much more than a simple Foreign Key relation. We can richly express in the Proxy Declarations concepts like 'Assets that belong to a User' and 'Assets that don't belong to a User':

current_user.my_assets
current_user.other_assets

simply by declaring:

class User
  has_many :my_assets, :class_name => 'Asset', :conditions => 'user_id = #{id}'
  has_many :other_assets, :class_name => 'Asset', :conditions => 'user_id != #{id}'
end

Notice, in this last example, something peculiar: the use of single quotes ('') with variable substitution (#{...}). This is not a typo: the use of double-quotes, would perform variable interpolation when the has_many declaration is invoked. This is in the class-context--i.e., there is no instance yet. Rails always calls eval with a Binding of self when a call to one of the Proxy methods is performed, ensuring that this all comes together.

Let's consider an alternative to this approach: declaring finders as instance methods.

class User
  def other_assets
    assets.find(:conditions => ["user_id != ?", id])
  end
end

What’s wrong with this approach? Well, if you want to use this query in anything non-trivial–such as selecting the first ten of a User’s Assets–you have to write fancy code:

def other_assets(options)
  assets.find({:conditions => ...}.merge(options))
end

But good luck using this strategy to do pagination. You need to define my_assets and my_assets_count, too–have fun keeping your code DRY. With a proxy, we can just do something like:

current_user.my_assets.count
current_user.my_assets.sum
current_user.my_assets.average(:price)

In fact, all the richness of ActiveRecord class methods (and any other class methods of the Target type) are available to you here. Want to find all of a User’s assets that are in State pending?

current_user.my_assets.find_by_state(State[:pending])

Another example:

class Asset
  def self.find_portrait_assets
    find(:all, :conditions => 'height > width')
  end
end

Then,

current_user.my_assets.find_portrait_assets

returns only those portrait assets owned by a user.

Proxy Options

Proxy declarations accept a number of interesting parameters. There are even “lifecycle” callbacks, like after_add, and before_destroy just like a normal ActiveRecord has before_create and so forth. You can hook into these by using an option.

class User
  has_many :assets, :after_add => [:send_email] do
  end
  def send_email(r)
  end
end

This after_add could be defined in the Asset class. But suppose Assets had a Polymorphic association. Both Users and Articles have many Assets. Our Business Rule is only to send email when a User adds an Asset, not an Article. We could write:

class Asset
  def after_create
     case owner
     when User
       # send email
     when Article
  end
end

But this is clumsy! When we have logic to express about the relationship between things, the Proxy is the right place for it. Anywhere else is just smearing logic throughout your code.

Proxy Extensions

Consider the following example:

has_many :assets do
  def to_s
    self.join(',')
  end
end

You can actually extend your Proxy Objects with an Anonymous module! When you have logic that applies to a Collection of ActiveRecords, your has_many Proxy is probably the proper place for it. For example:

class Table
  has_many :cells do
    def to_matrix
      # convert from list to matrix form.
    end
  end
end

Another example of this technique is the following. Suppose an Asset as many Versions, such as small, medium, etc. We’d prefer a shorter way of finding the proper version of an Asset than saying asset.versions.find_by_name('thumbnail'), we’d like to just say asset.versions[:thumbnail]. Just define the brackets ([]) operator on the Proxy:

class Asset
  has_many :versions, :class_name => 'Asset', :foreign_key => :parent_id do
    def [](version_name)
      find_by_name(version_name)
    end
  end
end

Suppose we want to go one step further. If a particular version doesn’t exist, it shall be created on-the-fly:

def [](version_name)
  if version = find_by_name(version_name)
    version
  else
    # create a new version here.
  end
end

Advanced Extensions

In some cases, we want to write generic Extensions–these should work regardless of the particular classes involved. In the context of a Proxy there are three methods you should be aware of: proxy_owner, proxy_target, and proxy_reflection.

Suppose we want to implement something like the build method, but one that doesn’t have the side effect of adding it to the owner in memory:

has_many :foo do
  def new(options = {})
    proxy_reflection.klass.new({proxy_reflection.primary_key_name => proxy_owner.id}.merge(options))
  end
end

Extensions are so useful–it just requires a little imagination–that I’m going to give one more example, this one apropos of Access Control:

class User
  has_many :draft_articles do
    def readable_by?(user)
      user == proxy_owner
    end
  end
end

Some Miscellany

  1. The has_one and belongs_to Proxies behave a bit oddly: here, cyclops.build_eye is used rather than the more obvious cyclops.eye.build.

  2. In general, has_one and belongs_to will shadow methods on the Target. Don’t name any database columns target or owner, for instance. This is one of the biggest complaints against the current implementation of Proxies!

  3. Both build and create will work even if the Proxy Owner is new. For example, u = User.new; u.assets.build; u.save. In this example, both objects will be saved with the Foreign Key set correctly.

  4. Both build and create can take an array of attributes hashes. For example: u.assets.build([{...}, {...}]). This will build two assets at once. (This is quite nice where in a Controller you have a form that allows the upload of multiple Assets at once. The Controller code looks identical (in simple cases) regardless of whether the form allows a single or multiple upload!)

That’s the basic idea. In part II of this Article (to be released in the coming weeks), I will discuss ‘static’ Proxy methods and I will release version 0.1 of a new plugin that builds upon a lot of exciting work in this area. In the meantime, check this out.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (783)
  • rails (117)
  • testing (90)
  • ruby (86)
  • ruby on rails (71)
  • jobs (62)
  • javascript (59)
  • techtalk (44)
  • ironblogger (42)
  • rspec (39)
  • bloggerdome (34)
  • productivity (34)
  • activerecord (30)
  • rubymine (30)
  • git (29)
  • gogaruco (29)
  • nyc (27)
  • design (24)
  • mobile (23)
  • pivotal tracker (22)
  • process (21)
  • cucumber (21)
  • jasmine (19)
  • ios (18)
  • tracker ecosystem (17)
  • webos (17)
  • objective-c (17)
  • fun (16)
  • android (16)
  • palm (16)
  • ci (16)
  • "soft" ware (16)
  • bdd (15)
  • tdd (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • css (14)
  • gem (13)
  • mouse-free development (12)
  • selenium (12)
  • goruco (12)
  • bundler (12)
  • api (12)
  • keyboard (11)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
Subscribe to activerecord Feed
  1. ←
  2. 1
  3. 2
  4. 3
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Tools
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >