lost password?

home
•  xaraya
•  rails +
•  django
•  webdev
•  xamp
•  musings

rss
Tag this page
   

» Blogs that link here
last modified: Nov 14, 2008
(first posted: Nov 11, 2008)
(261 Reads)
keywords: active record
Permalink

has one of many

Lets you make has_many associations look like a has_one (Note, i've renamed it, was has_one_current (Nov 14))

i just wrote a cool module, should make it into a plugin, if i had time.

has_one_of_many  lets you make a has_many association look like a has_one at runtime. In my app, X has_many Y but each user can only make one Y per X. I wanted to push the association find down into the model but normally models dont know about current_user. There's other uses as well, could be quite handy any time an action is attending to a single, currently selected item in a collection.

So, for example, if Article has many reviews, but each user can write only one review, now you can say @article.review and get the current user's one.

In the Article model you'd declare

    has_one_of_many :reviews, :by => :user_id

Then in your controller, you could say Article.current_review_user_id = current _user.id

Internally it does a self.reviews.find_by_user_id 

Possible extensions:

  • has_some_of_many to return a filtered collection. The problem is i dont like the idea of replacing, for example, #reviews with the filtered one, but could use alias_method_chain. Anyway, named finders can do this already.
  • :condition => or other find options to better control the find, such as the the most recent

Note, my app also uses a home-grown version of nested_params http://rails.lighthouseapp.com/projects/8994/tickets/1202-add-attributes-writer-method-for-an-association , so I generate a review_attributes=() method too.

Some have commented this violates MVC, but I strongly disagree. First, filtering on user is just an example. The model never knows about current_user, it only knows there's an attribute value used to constrain the collection to a single item.

Here's the code:

  # ------------------------------------
  # has_one_of_many
  # ------------------------------------
  # eg  has_one_of_many :reviews, :by => :user_id
  # implements class methods:
  #   current_review_user_id=( value ) # sets the value used to query the has_many
  #   current_review_user_id # get the value used
  # instance methods:
  #   review # => the current child, as if it were has_one
  #   review_attributes=( attribs ) # sets attributes in the current, or new if none

    @@has_current = {} # :assoc => :attrib
    @@current_value = {} # :assoc => value

    # names of associations that has current
    def self.current_ones
      @@has_current.keys
    end 

    #    
    def self.has_one_of_many(association_id, options={}) # public
      names = association_id.to_s
      name = name.singularize
      by = options[:by]
      @@has_current[name] = by
      self.class_eval %{
        def self.current_#{name}_#{by}=(value)
          @@current_value['#{name}'] = value
        end
        
        def self.current_#{name}_#{by}
          @@current_value['#{name}']
        end

        # instance methods
        def #{name}
          #{namepl}.find_by_#{by}( @@current_value['#{name}'] )
        end
        
        def #{name}_attributes=(new_attributes)
          record = self.#{names}.find_by_#{by}( @@current_value['#{name}'] )
          if record
            record.update_attributes(new_attributes)
          else
            self.#{names}.new(new_attributes)
          end
        end
      }, __FILE__, __LINE__  
    end
    
    # kludgey but handles :include => :review
    # since the association now looks like a has_one, 
    # but since its not really an association, AR barfs
    # so just delete it from the :include
    class << self
      def find_with_current_ones_removed(*args, &block)
        current_ones.each do |name|
          args.last[:include].delete(name.to_sym)
        end if args.last[:include]
        find_without_current_ones_removed( *args, &block )
      end
      alias_method_chain :find, :current_ones_removed
    end

 

There are no comments attached to this item.

Post a new comment

How many days in a week?

Name :