Using ActiveRecord scope for calculation with multiple columns? - ruby-on-rails

I have an ActiveRecord model that has two database attributes, total and processing_fees.
It has various scopes defined, such as (just as examples):
class Item < ActiveRecord::Base
scope :completed, joins(:order).where(:orders => {:status => Order::Status::COMPLETED})
scope :for_client, lambda {|client| where("client_id=?", client.id)}
...etc...
end
I can easily do something like:
Item.completed.for_client(client).sum(:total)
but what I would like to do is something like:
Item.completed.for_client(client).calculate(:total - :processing_fees) # obv not valid
Is there any way to do such a thing?

Something like this should work:
Item.completed.for_client(client).calculate(:sum, "item.total - item.processing_fees")

Related

How could I define methods on an association?

UPDATE:
I've put this in my person class
has_many :things, :dependent => :destroy do
def [](kind)
where("kind = ?", kind.to_s)
end
end
but when I invoke <Person Instance>.things[:table], I get this error:
undefined method `where' for #<Class:0x111dc3ba8>
ORIGINAL QUESTION:
I have a person, who has_many things.
I'd like to be able to do something like:
<Person Instance>.things[:table]
that would be defined similar to
def things[](arg)
self.things.find(:first, :conditions => ["kind = ?", arg.to_s])
end
currently, that method gives me this error:
syntax error, unexpected '[', expecting '\n' or ';'
So, how do I properly define things[]?
What you're looking for are called association extensions in rails.
Read about it here
Your implementation might look something like:
has_many :things do
def [](kind)
where(:kind => kind)
end
end
I think [] are not allowed in method names...
You could just skip them in your method name, but then your association method would be overwritten/replaced, if you've set up your assocations properly.
I'd do something like this:
# in your Person model
def things_of_kind(kind)
self.things.find(:first, :conditions => ["kind = ?", arg.to_s]
end
# then you could call
<PersonInstance>.things_of_kind(:table)
Alternatively there are association extensions which use this technique but in the proper place.
And there are also scopes, which can be helpful.

Accessing attributes via Rails relations

I have a Position model for which I have a scope defined:
scope :default, where('is_default = ?', 1)
Idea being that I want to know which is the default position. I can do something like: #profile.positions.default and this returns an activerecord relation and the default position record. The issue is that now that I have the default record, I need to access other attributes of Positions such as title..
#profile.positions.default.title
but the above returns an error: NoMethodError: undefined method `title' for #
Any clues? Thanks.
A scope turns a collection of objects, not a single object, so you're trying to call title on an array of ActiveRecord results.
You probably want something like this:
#profile.positions.default.first.title
Or if you always want just one record, you may switch from a scope to a class method:
def self.default
where('is_default = ?', 1).first
end
class Profile < ActiveRecord::Base
has_many :positions
has_one :default_position, :class_name => 'Position',
:conditions => ['is_default = ?', true]
end
Then
#profile.default_position.title

DRYing up query on a Rails polymorphic assocation

I have polymorphic likes model, for storing productions, comments, whatever that people like. Is there a way to dry up this query?
user.likes.where(:likeable_id => thing.id, :likeable_type => thing.class)
This doesn't work:
user.likes.where(:likeable => thing)
How about this:
class Like
...
# scope
def self.for(object)
scoped.where(:likeable_id => object.id, :likeable_type => object.class.to_s)
end
end
You would then use it like this:
user.likes.for(thing)

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.
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)
Although #MZaragoza's answer was complete, it seems that this syntax is no longer allowed:
scope :rock_n_rolla, where(occupation: "Rockstar")
Use procs instead:
summary:
Scopes in Mongoid must be procs that wrap criteria objects.
resolution:
Change the scope to be a proc wrapped critera.
Example:
  class Band
    include Mongoid::Document
    scope :inactive, ->{ where(active: false) }
  end
Mongoid v 7.0.3

How to use common named_scope for all ActiveRecord models

Hi how to build a named_scope which will be common for all models.
I do that by putting this code in lib/has_common_named_scopes.rb:
module HasCommonNamedScopes
def self.included(base)
base.class_eval {
# Named scopes
named_scope :newest, :order => "#{base.table_name}.created_at DESC"
named_scope :freshest, :order => "#{base.table_name}.updated_at DESC"
named_scope :limit, lambda { |limit| {:limit => limit} }
}
end
end
and then include the module in each model where I need them:
class MyModel < ActiveRecord::Base
include HasCommonNamedScopes
I'd recommend that you use base.table_name to qualify the table when referring to columns in these named scopes like I do in the example. Otherwise you run into problems with ambiguous references when you combine these named scopes with other scopes that join in other tables.
Update:
scope is used in Rails > 3 and named_scope was used in previous versions.
There's also Thoughtbot's Pacecar, which adds a bunch of very common named scopes to every model. It might come with what you're looking for. If you need something custom, though, Casper Fabricius has the right idea.
For a Rails4 project I achieved this by extending ActiveRecord::Base, the class all Rails models inherit from, in an initializer (monkey patching approach, beware)
# in /config/initializers/shared_scope_initializer.rb
module SharedScopes
extend ActiveSupport::Concern
module ClassMethods
def named_scope
return where(attribute: value) # query here
end
end
end
ActiveRecord::Base.send(:include, SharedScopes)

Resources