2

Is it possible to pass a Hash as a parameter for the ActiveRecord's select? I mean like this (here I have simple model: LineItem has id's of Product and Order):

Product.select(
    :orders => [{:name => :buyer}, :email], # orders.name as buyer
    :products => {:title => :product}).     # products.title as product
  joins(:line_items => :order)

To get the following SQL:

SELECT "orders"."name" as "buyer", "orders"."email", "products"."title" as "product" 
FROM "products"
INNER JOIN "line_items" ON "line_items"."product_id" = "products"."id" 
INNER JOIN "orders" ON "orders"."id" = "line_items"."order_id"

If it isn't possible, then I suggest some backward-compatible (can be used in old way, with 1 simple string as a peremeter) extension for the select method, but I can't figure out how to make it as transparent (i.e. w/o _h at the end as I did below) application-wide replacement:

class ActiveRecord::Base
  def self.select_h(*fields) # TODO: rid of this ugly _h
    hash_of_fields = fields.last.is_a?(Hash) ? fields.pop : {}
    fields_in_hash = hash_of_fields.map do |table, field_or_fields|
      (field_or_fields.is_a?(Array) ? field_or_fields : [field_or_fields]).map do |field|
        field = "#{field.first[0]}\" as \"#{field.first[1]}" if field.is_a? Hash
        "\"#{table}\".\"#{field}\""
      end
    end
    # calling original select
    select (fields+fields_in_hash).join(', ')
  end
end

I'll appreciate pointing me at some details about internals of ActiveRecord's implementation regarding it's pattern that was used to make this very obscure-to-exam gem :)

jdoe
  • 31
  • 3

2 Answers2

1

The source of the select method--

# File activerecord/lib/active_record/relation/query_methods.rb, line 34
def select(value = Proc.new)
  if block_given?
    to_a.select {|*block_args| value.call(*block_args) }
  else
    relation = clone
    relation.select_values += Array.wrap(value)
    relation
  end
end

-- suggests that it won't do what you're asking it to. However, as it can optionally take a block you might be able to coerce it into the behavior you're looking for...

Whenever I get confused about what I can and can't do with the Arel stuff, I just peruse the source at:

Arel API

I'm not sure this helps but at the very least it might send you in the right direction to explore...

jaydel
  • 14,389
  • 14
  • 62
  • 98
  • Snippet shows that the block for `select` was designed mostly for testing purposes: `select` with a block just fetches all the rows from respective table (via exhaustive `SELECT * FROM table`) into an array and applies `Array#select` to it, thus filtering it using Ruby language rather than SQL. – jdoe Jun 27 '11 at 14:17
0

For your final question of how to avoid the _h, see When monkey patching a method, can you call the overridden method from the new implementation?

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  old_bar = instance_method(:bar)

  define_method(:bar) do
    old_bar.bind(self).() + ' World'
  end
end

Foo.new.bar # => 'Hello World'
Community
  • 1
  • 1