How to use a scope in combination with a Model method? - ruby-on-rails

I'm currently working on some scope filters, and I'd like to have it filter on a method (don't know how to name it otherwise). My code:
class Product < ActiveRecord::Base
def price_with_discount
price-discount.to_f
end
scope :by_price, lambda {|min,max|{ :conditions => { :price_with_discount => min.to_f..max.to_f }}}
end
This doesn't work: "No attribute named price_with_discount exists for table products". How can I trick my scope into using the method I defined instead of searching for a column named price_with_discount?
Bjorn

You can't use ruby methods inside :conditions. This is not an exclusive scope thing, it applies in all ActiveRecord queries:
# This won't work either
Product.where(:price_with_discount => min.to_f)
The reason for this is that what ActiveRecord needs is something that can be "translated" into database fields. The symbol :price_with_discount can not be translated to the database.
Besides, if you are on rails 3, you don't really need scopes any more. A regular class method will do the same, and it's easier to write (no lambdas).
class Product < ActiveRecord::Base
def self.by_price(min, max)
# Assuming that products.discount is a database field:
where([ "(products.price - products.discount) >= ? AND " +
"(products.price - products.discount) <= ?",
min, max
])
end
end

You can't try to do a SQL restriction on our Ruby method. Avoid using it and I suppose it's works if your price-discount is not a String
scope :by_price, lambda {|min,max|{ :conditions => { :price-discount => min.to_f..max.to_f }}}

Related

Rails - ActiveRecord Reputation System scoped query issue

I get the following error whenever I try to execute find_with_reputation or count_with_reputation methods.
ArgumentError: Evaluations of votes must have scope specified
My model is defined as follows:
class Post < ActiveRecord::Base
has_reputation :votes,
:source => :user,
:scopes => [:up, :down]
The error raises when I try to execute for example:
Post.find_with_reputation(:votes, :up)
or
Post.find_with_reputation(:votes, :up, { order: "likes" } )
Unfortunately, the documentation isn't very clear on how to get around this error. It only states that the method should be executed as follows:
ActiveRecord::Base.find_with_reputation(:reputation_name, :scope, :find_options)
On models without scopes ActiveRecord Reputation System works well with methods such as:
User.find_with_reputation(:karma, :all)
Any help will be most appreciated.
I've found the solution. It seems that ActiveRecord Reputation System joins the reputation and scope names on the rs_reputations table. So, in my case, the reputation names for :votes whose scopes could be either :up or :down are named :votes_up and :votes_down, respectively.
Therefore, find_with_reputation or count_with_reputation methods for scoped models need to be built like this:
Post.find_with_reputation(:votes_up, :all, { conditions: ["votes_up > ?", 0] })
instead of:
Post.find_with_reputation(:votes, :up, { conditions: ["votes_up > ?", 0] })
Note that you'll need to add the conditionsoption to get the desired results, otherwise it will bring all the records of the model instead of those whose votes are positive, for example.

Is possible to rewrite this query using only named scopes?

I would love to write this query using named scopes only. The reason is simple, I don't want to change code everywhere when I change the way a Client is considered active (same for User considered connectable)
Here is my code
client.rb
class Client < ActiveRecord::Base
has_one :user
scope :actives, -> { where(starting_date: a_date, finishing_date: another_date, enabled: true) }
end
user.rb
class User < ActiveRecord::Base
belongs_to :client
scope :connectable, -> { where.not(access_token: nil, instance_url: nil) }
end
And here you can find my attempts to write this query:
# What I would like to write
User.eager_load(:client).connectable.where("client is in :actives scope")
# Current options
# 1 - (but this violates dry)
User.eager_load(:client).connectable.where(['clients.starting_date = ? AND clients.finishing_date = ? AND clients.enabled = ?', a_date, another_date, true).references(:client)
# 2 - My best attempt so far
User.connectable.where(client_id: Client.actives.pluck(:id))
And a link to the GIST for reference
What you're looking for is ARel's merge method. (An article on this here.)
Try this:
User.connectable.includes(:client).merge(Client.actives).references(:clients)
The includes(:client) will introduce the :client relation from User which will make the .merge(Client...) scope work. And the .references is required by Rails 4+ to explicitly state which table the includes statement will be referencing.
I'm not sure how good of an idea it is, but as long as you ensure your column names are name spaced properly (ie. you can't have 'foo' in both Client and User without prefixing the table name) then this should work:
User.eager_load(:client).connectable.where(Client.active.where_values.map(&:to_sql).join(" AND "))
There must be a nicer way, but the above works for me:
Course model:
scope :valid, -> { enabled.has_country }
scope :has_country, -> { where.not(country_id:nil) }
Rails console:
> Course.where(Course.valid.where_values.map(&:to_sql).join(" AND ")).to_sql
=> "SELECT \"courses\".* FROM \"courses\"
WHERE (\"courses\".\"is_enabled\" = 't'
AND \"courses\".\"country_id\" IS NOT NULL)"

Query based on a child collection count with has_many association is showing always no results

I'm trying to base a query on the number of documents in the child collection.
This is my context:
class Pro
include Mongoid::Document
has_many :recommendations
scope :lost, -> { where(:recommendation_ids => []) }
scope :normal, -> { where(:recommendation_ids.ne => []) }
end
And the child collection:
class Recommendation
include Mongoid::Document
belongs_to :pro
end
So, now I execute:
>> Pro.lost.count
0
>> Pro.all.select{|p| p.recommendations.count == 0}.count
1
What am I doing wrong? I've tried also with Pro.with_size(recommendation_ids: 0) and some other variations but nothing new.
Any ideas would be highly appreciated, thanks in advance.
Moped 2.0.1, Mongoid 4.0.0, Rails 4.0.6
You've coded the scope to search for IDs in an empty array, so it's always searching for an array of 0 ids.
scope :lost, -> { where(:recommendation_ids => []) }
Maybe something like this, if you want to pass an argument to it.
scope :lost, ->(rec_ids) { where(:recommendation_ids => [rec_ids]) }
I tried to find a solution for this problem several times already and always gave up. I just got an idea how this can be easily mimicked. It might not be a very scalable way, but it works for limited object counts. The key to this is a sentence from this documentation where it says:
Class methods on models that return criteria objects are also treated like scopes, and can be chained as well.
So, instead of declaring a scope you can define a class function like so:
def self.lost
ids = Pro.all.select{|p| p.recommendations.count == 0}.map(&:id)
Pro.where(:id.in => ids)
end
The advantage is, that you can do all kinds of queries on the associated (Recommendations) model and return those Pro instances, where those queries are satisfied (which was the case for me) and most importantly you can chain further queries like so:
Pro.lost.where(:some_field => some_value)

Rails-y way to query a model with a belongs_to association

I have two models:
class Wine
belongs_to :region
end
class Region
has_many :wines
end
I am attempting to use the #where method with a hash built from transforming certain elements from the params hash into a query hash, for example { :region => '2452' }
def index
...
#wines = Wine.where(hash)
...
end
But all I get is a column doesn't exist error when the query is executed:
ActiveRecord::StatementInvalid: PGError: ERROR: column wines.region does not exist
LINE 1: SELECT "wines".* FROM "wines" WHERE "wines"."region" =...
Of course, the table wines has region_id so if I queried for region_id instead I would not get an error.
The question is the following:
Is there a rails-y way to query the Wine object for specific regions using the id in the #where method? I've listed some options below based on what I know I can do.
Option 1:
I could change the way that I build the query hash so that each field has _id (like { :region_id => '1234', :varietal_id => '1515' } but not all of the associations from Wine are belongs_to and thus don't have an entry in wines for _id, making the logic more complicated with joins and what not.
Option 2:
Build a SQL where clause, again using some logic to determine whether to use the id or join against another table... again the logic would be somewhat more complicated, and delving in to SQL makes it feel less rails-y. Or I could be wrong on that front.
Option(s) 3..n:
Things I haven't thought about... your input goes here :)
You could set up a scope in the Wine model to make it more rails-y ...
class Wine < ActiveRecord::Base
belongs_to :region
attr_accessible :name, :region_id
scope :from_region, lambda { |region|
joins(:region).where(:region_id => region.id)
}
end
So then you can do something like:
region = Region.find_by_name('France')
wine = Wine.from_region(region)
Edit 1:
or if you want to be really fancy you could do a scope for multiple regions:
scope :from_regions, lambda { |regions|
joins(:region).where("region_id in (?)", regions.select(:id))
}
regions = Region.where("name in (?)", ['France','Spain']) # or however you want to select them
wines = Wine.from_regions(regions)
Edit 2:
You can also chain scopes and where clauses, if required:
regions = Region.where("name in (?)", ['France','Spain'])
wines = Wine.from_regions(regions).where(:varietal_id => '1515')
Thanks to all who replied. The answers I got would be great for single condition queries but I needed something that could deal with a varying number of conditions.
I ended up implementing my option #1, which was to build a condition hash by iterating through and concatenating _id to the values:
def query_conditions_hash(conditions)
conditions.inject({}) do |hash, (k,v)|
k = (k.to_s + "_id").to_sym
hash[k] = v.to_i
hash
end
end
So that the method would take a hash that was built from params like this:
{ region => '1235', varietal => '1551', product_attribute => '9' }
and drop an _id onto the end of each key and change the value to an integer:
{ region_id => 1235, varietal_id => 1551, product_attribute_id => 9 }
We'll see how sustainable this is, but this is what I went with for now.

is there a nicer/rails-like way for find all where something IS NOT NULL query?

is there a nicer/rails-like way for this IS NOT NULL query?
MyModel.find(:all, :conditions=>"some_reference_id IS NOT NULL")
The more Rails-like way would be with scopes, since they are now native to Rails 3. In Rails 2, you can use named_scope which is similar.
class MyModel < ActiveRecord::Base
named_scope :referenced, :conditions => "some_reference_id IS NOT NULL"
end
#Then you can do this
MyModel.referenced
In Rails 3 it would be something like this.
class MyModel < ActiveRecord::Base
scope :referenced, where "some_reference_id IS NOT NULL"
end
Assuming MyModel belongs_to :some_reference, you could also use
MyModel.all.find_all{ |e| e.some_reference }
or
MyModel.all.find_all{ |e| e.some_reference_id }
really depends on what you are trying to achieve. (2.) would be equivalent (in terms of result contents) to your IS NOT NULL query, (1.) will only return records whose some_reference_id is not null AND points to valid some_references record.

Resources