Coming from Rails 2 to Rails 3 I've never worked so hard to understand something (side editorial).
Anyway, In a Rails 3 app i have the following models...
User:
has_many :answers
Answer:
belongs_to :user
belongs_to :question
scope :user_answers, where (:user_id => current_user.id)
Question:
has_many :answers
scope :qs_w_user_ans, joins(:questions) & (:user_answers)
The current error i am getting is "undefined method `includes_values' for :user_answers:Symbol"
There is a Question id and a User id. Each answer has question_id and user_id.
I need the questions with a user's answers linked appropriately via the ids. Can you show me where my models are wrong?
Thank you.
The & operator (which I believe is recently deprecated) is an alias for merge, which allows you to essentially merge scopes. :user_answers isn't a scope, so you can't use this method.
As Dinatih pointed out, you can call joins multiple times. In this case, creating different scopes for each join won't buy you much, so his method suits your case.
More info on scopes: http://archives.edgerails.info/articles/what-s-new-in-edge-rails/2010/02/23/the-skinny-on-scopes-formerly-named-scope/index.html
Update
Sorry for my misunderstanding. :user_answers is a scope, but you're not calling it correctly in this case. You want the following:
scope :qs_w_user_ans, joins(:questions) & Answer.user_answers
When merging scopes, you call the merged scopes like class methods.
In the article I linked, the scope :published on Post is merged with the scope :published on User:
scope :published, lambda {
joins(:posts).group("users.id") & Post.published
}
Rails 4
Question.joins(Answer.user_answers)
[ http://guides.rubyonrails.org/active_record_querying.html#joining-tables ]
joins(:questions).joins(:user_answers)
Related
I have a model for user.rb, in which I define a scope for admins, which is users that have the role of admin through a permissions table.
has_many :permissions
has_many :roles, :through => :permissions
The scope works like this:
scope :admins, joins(:permissions).merge(Permission.admin_permissions)
I'd also like to make a scope called non-admins or something like that, which is all users that do NOT have the admin role.
What's the easiest way to do this?
If you want to have an inverted SQL query, you will have to do it yourself manually. There is no built-in ActiveRecord solution.
scope :admins, joins(:permissions).merge(Permission.where("permissions.admin = true"))
scope :non_admins, joins(:permissions).merge(Permission.where("permissions.admin = false"))
If there are a lot of scopes or they are complex, consider excluding them by id:
User.where("id not in (?)", User.admins.pluck(:id))
# or if you are already using admin records
admins = User.admins
User.where("id not in (?)", admins.map(&:id))
Depending on number of rows and complexity of the original query, this could be slower or faster than the previous way.
An easy way, which would not be performant for a lot of users:
admin_user_ids = User.admins.map(&:id)
non_admin_users = User.all.reject { |u| admin_user_ids.include?(u.id) }
I have the following relationships:
Category has_many :posts
Post has_many :comments
Post has_many :commenters, :through => :comments
I have the following eager load, giving me posts, comments and commenters (note that I need all 3, and hence the includes as opposed to joins)
category.posts.includes(:comments, :commenters)
However, I'd like to limit comments (and if possible commenters) to only those created in the past two weeks while still returning the same set of posts. Initially I thought I could specify a condition on the includes:
category.posts.includes(:comments, :commenters).where("comments.created_at > ?", 2.weeks.ago)
But found that this returns only the posts that meet the condition. I'm thinking that I may need to do something like performing a subquery on comments and then performing a join. Is there an easy way to do this with AR of would I be better off doing this with sql?
Finally managed to figure this out from reading this page:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
I simply needed to create an association in my Post model like:
Post has_many :recent_comments, :class_name = 'Comment', :conditions => ["created_at > ?", 2.weeks.ago]
Then I could do the following to get the desired ActiveRecord::Association object:
category.posts.includes(:recent_comments => :commenters)
There was also a suggestion of doing this by using a scope on a model. However, I read somewhere (I think it was on SO) that scopes are on their way out and that ARel has taken their place so I decided to do this without scopes.
Try :
category.posts.all(:includes => {:comments =>:commenters}, :conditions => ["comments.created_at = ? AND commenters.created_at = ?", 2.weeks.ago, 2.weeks.ago]
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.
Apologies for the long title, but this is bothering me. I'm new to Rails, so this is my first project. Rails 3.0.3.
In my model, a User may or may not have read many Entries; this is tracked in a model called ReadEntries. This many-to-one relationship is properly defined in the code, I think.
User.rb:
has_many :read_entries
Entry.rb:
has_many :read_entries
ReadEntry.rb:
belongs_to :entry
belongs_to :user
This table has to be populated at some point. If I try to do this:
user.read_entries.find_or_create_by_entry_id(entry.id, :read => false)
I get the error Unknown key(s): read. Leave out trying to set :read, and it works.
However, if I create the same row with this, it works:
ReadEntry.find_or_create_by_entry_id_and_user_id(entry.id, user.id, :read => false)
Logically, these methods should be identical, right? Thanks.
I've also had weird experiences with find_or_create. I would love it if it worked, but it seems inconsistent.
I'm currently having the same issue as you, and I think it may be due to calling find_or_create on an association as opposed to the model directly. Here's my example:
permission_assignments.find_or_create_by_role_id(:role_id => role_id, :is_allowed => false)
This works to create the assignment, except the "is_allowed" field gets set to it's default of "true". This code works for me (in the Permission model, hence the self reference)
PermissionAssignment.find_or_create_by_permission_id_and_role_id(:permission_id => self.id, :role_id => role_id, :is_allowed => false)
It's more verbose, unfortunately, but it works. The only problem that I still notice is that the object that is returned has no id assigned (the record does get created in the database, however, but if I wanted to update any more attributes I wouldn't be able to without the id). Don't know if that's a separate issue or not.
Rails 3.0.4 here with Postgres 8.4
You cannot pass in other fields like that as Rails will assume they are options for the find. Instead, you will need to make your method call longer:
user.read_entries.find_or_create_by_entry_id_and_read(entry.id, false)
Or alternatively use a shorter, custom syntax for that.
For your final example, my thoughts are that Rails will take the second argument and use that as options. Other than that, I am not sure.
I'm using Devise and have models set up like this:
User
has_one :profile
Profile
belongs_to :user
has_one :address
Address
belongs_to :profile
For testing, I caused errors on the form (it's a nested form, using all 3 models). This is the resulting errors hash (#user.errors hash):
{
:email=>["can't be blank"],
:password_confirmation=>[],
:password=>["can't be blank"],
:"profile.first_name"=>["can't be blank","is too short (minimum is 1 characters)"]
:"profile.address.street"=>["can't be blank"],
}
All the errors are expected, but the hash's key format is unexpected (:"profile.address.street") - I would have thought each nested model's errors hash would contain the errors for that model ?
I checked inside the #user.profile.errors hash, and that is empty!
How do I get the nested-model specific errors into the relevant errors hash in the nested model?
UPDATE
I hacked out a solution here. Had a question on that too, so the solution isn't posted as an answer to this question :)
Ran into this not too long ago and had to cook up some code to pluck these out myself. It isn't supported by default in Rails - Ryan Daigle touches on it here a bit.
Try this:
#user.profile.valid?
#user.profile.errors