Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you set up chainable scopes based on relations in Mongoid

Problem fixed... Turned out there was an active record method that got over written, now everything works as expected

I am trying to set up scopes so I can make a call that looks like

Competitor.of_type(type).at_event(event)

that will return all Competitors of type that attended event My Models looks something like

class Competitor < Competitor
  belongs_to :type
  has_and_belongs_to_many :events
  scope :at_event, ->(event) {where(:event_ids.in => event.competitor_ids)}
  scope :of_type, ->(type) where(:type_id => type.id)                
end

The following works (return mongoid criteria)

Competitor.of_type(type)
Competitor.at_event(event)

But when I chain them, it prints out something that looks like this:

#<Competitor:0x00000109e2b210>
#<Competitor:0x00000109e2ab08>
-------=-=------------------------------------
=> #<Mongoid::Criteria
selector: {},
options:  {},
class:    Competitor,
embedded: false>

There is a Competitor entry for each of Competitor.of_type(type) (the first chained criteria) and if I run .count on the query, I get the total number of Competitors in the database.

At the top of the mongoid documentation for scopes, it says All scopes are chainable and can be applied to associations as well, the later being discussed in the relations section. Unfortunately I did not see a relations sub section, not could I find a single reference to scope in the main relations section.

I was able to get the following to return the results I wanted:

where(:id.in => event.competitor_ids).where(:type_id => type.id)

but if any part of the query is split into a separate method or scope it fails and provides the result I showed above.

like image 726
MiGnH Avatar asked Dec 06 '11 22:12

MiGnH


1 Answers

scopes

Similar to Active Record, Mongoid allows you to define scopes on your models as a convenience for filtering result sets. Scopes are defined at the class level, either using the scope macro or by defining class methods that return a criteria object. All scopes are chainable and can be applied to associations as well, the later being discussed in the relations section.

Named scopes are defined at the class level using a scope macro and can be chained to create result sets in a nice DSL.

class Person
  include Mongoid::Document
  field :occupation, type: String
  field :age, type: Integer

  scope :rock_n_rolla, where(occupation: "Rockstar")
  scope :washed_up, where(:age.gt => 30)
  scope :over, ->(limit) { where(:age.gt => limit) }
end

# Find all the rockstars.
Person.rock_n_rolla

# Find all rockstars that should probably quit.
Person.washed_up.rock_n_rolla

# Find a criteria with Keith Richards in it.
Person.rock_n_rolla.over(60)

Note that definitions are evaluated at class load time. For evaluation at runtime you will want to make sure to define using a proc or lambda. In the following example the first date is set as the date of class load, where the second scope sets the date at the time the scope is called.

scope :current, where(:start_date.lte => Date.today)
scope :current, -> { where(:start_date.lte => Date.today) }

class methods

For those who prefer a Data Mapper style syntax, class methods that return criteria can be treated as chainable scopes as well.

class Person
  include Mongoid::Document
  field :occupation, type: String
  field :age, type: Integer

  class << self

    def rock_n_rolla
      where(occupation: "Rockstar")
    end

    def washed_up
      where(:age.gt => 30)
    end

    def over(limit)
      where(:age.gt => limit)
    end
  end
end

# Find all the rockstars.
Person.rock_n_rolla

# Find all rockstars that should probably quit.
Person.washed_up.rock_n_rolla

# Find a criteria with Keith Richards in it.
Person.rock_n_rolla.over(60)

Named scopes and class methods that return a criteria can be chained together - that's the beauty of Mongoid's powerful criteria API.

class Person
  include Mongoid::Document
  field :occupation, type: String
  field :age, type: Integer

  scope :washed_up, where(:age.gt => 30)
  scope :over, ->(limit) { where(:age.gt => limit) }

  def self.rock_n_rolla
    where(occupation: "Rockstar")
  end
end

# Same queries apply here as well.
Person.rock_n_rolla
Person.washed_up.rock_n_rolla
Person.rock_n_rolla.over(60)
like image 149
MZaragoza Avatar answered Nov 04 '22 16:11

MZaragoza