Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

map in scope in active record

I have a active record, where I have a usecase: I want to use map in the ActiveRecord scopes. Following is my code:

class SupportedGeoIdAndLocation < ActiveRecord::Base

   scope :supported_geo_ids,            lambda { all.map(&:geo_id).uniq }
   # also tried: scope :supported_geo_ids,            lambda { select('distinct geo_id').map(&:geo_id).uniq }
   scope :locations_for_geo_id, lambda { |geo_id| where(:geo_id => geo_id).map(&:location) }
end

but when I do SupportedGeoIdAndLocation.supported_geo_ids I get empty array, while SupportedGeoIdAndLocation.all.map(&:geo_id).uniq gives me desired result.

I am looking for a way so that this can be done using lambda in one go, not via chaining of different methods and completely avoiding class functions.

Looks like I am using scope in wrong way, Please suggest me what can be correct way.

version: padrino:0.10.5, ruby: ruby-1.9.3-p429, ActiveRecord: 3.1.6

like image 831
Saurabh Avatar asked Jan 27 '26 05:01

Saurabh


2 Answers

scope should return an ActiveRecord::Relation, so scope could be chained with another scope or class methods etc.

If you want to get an array, use class method instead.

def self.supported_geo_ids
  all.map(&:geo_id).uniq
end
like image 103
xdazz Avatar answered Jan 29 '26 22:01

xdazz


After playing around with some similar code in one of my models I finally found this question (as I got the same error message (in Rails 3.2 that was)). The accepted answer states that ActiveRecord implicitly assumes scopes to be "chainable" (in ARel sense) which the array you return obviously is not.

So while you can do whatever you want in a class method you define yourself (as in the answer of @xdazz) you will have to adhere to ActiveRecord conventions if you insist on using scope (which at first glance just defines a class method with whatever lambda you provided it). There seems to be more to this than I could spot.

Update

After some more fiddling I think I got as close as it gets. You can write a scope that returns just supported geo ids, i.e. as follows:

class SupportedGeoIdAndLocation < ActiveRecord::Base
  scope :supported_geo_ids,  lambda { select("distinct(geo_id)") }
end

However that will return an ARel relation that can be turned into an array of SupportedGeoIdAndLocation objects with just the geo_id attribute set (and, sorry to disappoint you, it is a class method, even if it ends up being defined through the scope class method). So if you need an array of attributes you can turn it into one using the method you proposed in another class method:

class SupportedGeoIdAndLocation < ActiveRecord::Base
  def self.geo_ids_array
    supported_geo_ids.map(&:geo_id)
  end
end

Note that there is no need to call uniq now as this has been taken care of by the distinct in the scope. Up from rails 4.0.2 ActiveRecord does support the distinct method by the way, so you won't have to resort to using a string if you can update your ActiveRecord gem as Padrino should not depend on it.

like image 26
Patru Avatar answered Jan 29 '26 22:01

Patru



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!