I was wondering what's the best way to make an scope using an instance method to filter records. This is my model:
class Promotion < ActiveRecord::Base
scope :availables, lambda{ all.select{ |obj| obj.is_available? } }
def is_available?
Date.today <= Date.strptime(valid_thru, '%m/%d/%Y')
...more validations here
end
end
The problem here is this scope returns an array instead of an ActiveRecord::Relation and I'm not able to chain other scopes.
Any suggestion guys?
There is no way to accomplish what you want. If you want to apply logic on ruby objects you can no longer return an ActiveRecord::Relation.
The only way to achieve something like this is have the logic on a database level. In that case you could use a class method to achieve what you want like so:
def availables
where('valid_thru => ?', Date.today)
end
Related
I am using find my sql, and I want to calculate something in the database and add to my model.
I wil try to simplify the code
class User < ActiveRecord::Base
attr_accessor :comments_count
end
And somewhere else:
#users = User.find_by_sql("select * (select count(*) from comments where user_id=u.id) as comments_count from Users u")
#user.map{|u| puts u.comments_count}
Any help?
ActiveRecord will, unfortunately, not just match any selected columns to attributes on the model. However, something like that would probably be fairly easy to put together. First you'd need to overload find_by_sql to something like this -
def self.find_by_sql(sql)
values = connection.select_all(sql)
objects = []
values.each do |row|
objects << self.new(row)
end
objects
end
Then the initialize function for your model can mass assign the schema-based attributes, and then handle assigning your custom attributes. Its not as clean or simple a solution as you were probably hoping for, but it should accomplish the task. And you could probably write the initialize function so that it is fairly generic, in that it can mass assign all the schema-based attributes, and then match leftover keys that were passed in with any attributes that may be present on self.
Ok, i got it.
It has nothing to do with attr_accessor. I had to remove it.
#users = User.find_by_sql("select * (select count(*) from comments where user_id=u.id) as comments_count from Users u")
#user.map do |u|
puts u.comments_count if #user.has_attribute?(:comments_count)
end
I have a scope defined in my ActiveRecord class, let's say:
class Book < ActiveRecord::Base
scope :old, where( "published_at < ?", 1.year.ago )
end
I would like to ask to any instance of this class something like this:
book.old?
Is there any way to do this without duplicating the where definition?
Run another query on the database, using your scope:
Book.old.exists?(book.id)
If you want to re-use this in multiple places, it would be better to implement a method:
def old?
Book.old.exists?(id)
end
book.old?
This is something I'm trying to do now: A has_many Bs. B has certain callbacks that need to be triggered. Just, when I save from A, I want something to be updated in Bs. Since the Bs can be 10000, I would like not to load them into memory, and still have the benefit of seeing the callbacks triggered. What is the best strategy for this?
And please, no "find_each" or any find by batches variant, I'm aware of them and they will be my last resort in case nothing else works.
When I encountered this problem, I use this solution
define "callback methods" in a class and use they with ids,
define really callbacks in a instance and pass id of record in "class callback"
example of code:
class Post < AR
has_many :comments
after_save do |post|
Post.recalculate_counters(post.comment_ids)
end
end
class Comment < AR
belongs_to :post
after_save :recalculate_couters
def self.recalculate_couters(ids)
... huge and slow update statement ...
end
def recalcuate_couters
self.class.recalculate_couters([id])
end
end
I don't think there's any way you can have the callbacks executed without loading the models. However, if you give up using callback you can use update_all that performs really fast.
You just use:
B.update_all({:timestamp => Time.now}, { :a_id => id })
I'm finding myself writing very similar code in two places, once to define a (virtual) boolean attribute on a model, and once to define a scope to find records that match that condition. In essence,
scope :something, where(some_complex_conditions)
def something?
some_complex_conditions
end
A simple example: I'm modelling a club membership; a Member pays a Fee, which is valid only in a certain year.
class Member < ActiveRecord::Base
has_many :payments
has_many :fees, :through => :payments
scope :current, joins(:fees).merge(Fee.current)
def current?
fees.current.exists?
end
end
class Fee < ActiveRecord::Base
has_many :payments
has_many :members, :through => :payments
scope :current, where(:year => Time.now.year)
def current?
year == Time.now.year
end
end
Is there a DRYer way to write a scopes that make use of virtual attributes (or, alternatively, to determine whether a model is matched by the conditions of a scope)?
I'm pretty new to Rails so please do point out if I'm doing something stupid!
This in not an answer to the question, but your code has a bug (in case you use something similar in production): Time.now.year will return the year the server was started. You want to run this scope in a lambda to have it behave as expected.
scope :current, lambda { where(:year => Time.now.year) }
No, there's no better way to do what you're trying to do (other than to take note of Geraud's comment). In your scope you're defining a class-level filter which will generate SQL to be used in restricting the results your finders return, in the attribute you're defining an instance-level test to be run on a specific instance of this class.
Yes, the code is similar, but it's performing different functions in different contexts.
Yes, you can use one or more parameters with a lambda in your scopes. Suppose that you have a set of items, and you want to get back those that are either 'Boot' or 'Helmet' :
scope :item_type, lambda { |item_type|
where("game_items.item_type = ?", item_type )
}
You can now do game_item.item_type('Boot') to get only the boots or game_item.item_type('Helmet') to get only the helmets. The same applies in your case. You can just have a parameter in your scope, in order to check one or more conditions on the same scope, in a DRYer way.
I'd like to make a dynamic named scope in rails 3 conditional on the arguments passed in. For example:
class Message < AR::Base
scope :by_users, lambda {|user_ids| where(:user_id => user_ids) }
end
Message.by_users(user_ids)
However, I'd like to be able to call this scope even with an empty array of user_ids, and in that case not apply the where. The reason I want to do this inside the scope is I'm going to be chaining several of these together.
How do I make this work?
To answer your question you can do:
scope :by_users, lambda {|user_ids|
where(:user_id => user_ids) unless user_ids.empty?
}
However
I normally just use scope for simple operations(for readability and maintainability), anything after that and I just use class methods, so something like:
class Message < ActiveRecord::Base
def self.by_users(users_id)
if user_ids.empty?
scoped
else
where(:user_id => users_id)
end
end
end
This will work in Rails 3 because where actually returns an ActiveRecord::Relation, in which you can chain more queries.
I'm also using #scoped, which will return an anonymous scope, which will allow you to chain queries.
In the end, it is up to you. I'm just giving you options.