Working on a multi-tenant app where most of my models will have a tenant_id field so I can ensure read permissions by finding through the association (current_tenant.applications.find(params[:id])):
class Application < ActiveRecord::Base
belongs_to :tenant
has_many :app_questions, :conditions => proc {{:tenant_id => tenant_id}}, :dependent => :destroy
end
I like how this allows me to elegantly create a new AppQuestion with the tenant_id set automatically:
#application = current_tenant.applications.find(params[:app_question][:application_id])
#question = #application.app_questions.build(params[:app_question])
#...
Problem is, when I try to use includes() to eager-load the association it throws an error:
current_tenant.applications.where(:id => params[:id]).includes(:app_questions => :app_choices).first
NoMethodError (undefined method `tenant_id' for #<Class:0x007fbffd4a9420>):
app/models/application.rb:7:in `block in <class:Application>'
I could refactor so that I don't have to have the proc in the association conditions, but am wondering if anyone has a better solution.
The ref does say: "If you need to evaluate conditions dynamically at runtime, use a proc"
I've replied to the other question with more details trying to explain why this cannot work.
When they say dynamic is because the proc can be executed at runtime, but not in the context of an existing instance of the Application class because it doesn't exist when you invoke this relation
Application.where(:id => params[:id]).includes(:app_questions => :app_choices)
The ability for :conditions to accept a proc isn't documented in the ref. I suspect it doesn't work the way you guessed it might.
:conditions accepts either an SQL WHERE clause, or a hash that can be turned into on. It's inserted into the SQL that gets the :app_questions records, and if it's a proc it's only called once to get the snippet for the SQL statement to be constructed.
It might help to have a look at your database relationships. Should app_questions link to tenants or applications?
Assuming the relation itself works you could try preload instead of includes
Related
I have an issue where my ActiveRecord::Relation isn't working I have 3 Db's Users,Games,Achievements The relation defined between them is such
Users
has_many :games
Games
belongs_to :user
has_many :achievements
Achievements
belongs_to :game
The problem is when i try to call
Game.where(:appid => game["appid"]).achievements.new
it gives me and error saying that
undefined method `achievements' for #<Game::ActiveRecord_Relation:0x730f9f8>
I am running on Ruby on Rails 4.1.8 and I have no clue why this is happening (I do have the belongs_to :game,index: true column in my Achievements table I can't think of why its not working)
You are getting an association here:
Game.where(:appid => game["appid"])
... this is realized as an array of objects (even if the sql returns no records, then it is still an array, although it is empty).
You need to select one of them ... probably the first, like this:
Game.where(:appid => game["appid"]).first.achievements.new
Or you can run through the values:
Game.where(:appid => game["appid"]).each { |game| game.achievements.new }
or some such.
Probably, you should select one model
Game.where(:appid => game["appid"]).first
and then get relational models
.achievements.new
The answer is in the exception. #where returns an ActiveRecord_Relation, not a Game, even if there is only one Game in the relation. So you really have an enumerable. i.e:
puts Game.where(:appid => game["appid"]).first.achievements.new
If you just want the first game that meets the criteria, you can use find_by
Game.find_by(:appid => game["appid"])
but I am not sure that's what you are looking for
I'm currently using Rails 2.3.9. I understand that specifying the :joins option in a query without an explicit :select automatically makes any records that are returned read-only. I have a situation where I would like to update the records and while I've read about different ways to approach it, I was wondering which way is the preferred or "proper" way.
Specifically, my situation is that I have the following User model with an active named scope that performs a JOIN with the subscriptions table:
class User < ActiveRecord::Base
has_one :subscription
named_scope :active, :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end
When I call User.active.all, the user records that are returned are all read-only, so if, for instance, I call update_attributes! on a user, ActiveRecord::ReadOnlyRecord will be raised.
Through reading various sources, it seems a popular way to get around this is by adding :readonly => false to the query. However, I was wondering the following:
Is this safe? I understand the reason why Rails sets it to read-only in the first place is because, according to the Rails documentation, "they will have attributes that do not correspond to the table’s columns." However, the SQL query that is generated from this call uses SELECT `users`.* anyway, which appears to be safe, so what is Rails trying to guard against in the first place? It would appear that Rails should be guarding against the case when :select is actually explicitly specified, which is the reverse of the actual behavior, so am I not properly understanding the purpose of automatically setting the read-only flag on :joins?
Does this seem like a hack? It doesn't seem proper that the definition of a named scope should care about explicitly setting :readonly => false. I'm also afraid of side effects if the named scoped is chained with other named scopes. If I try to specify it outside of the scope (e.g., by doing User.active.scoped(:readonly => false) or User.scoped(:readonly => false).active), it doesn't appear to work.
One other way I've read to get around this is to change the :joins to an :include. I understand the behavior of this better, but are there any disadvantages to this (other than the unnecessary reading of all the columns in the subscriptions table)?
Lastly, I could also retrieve the query again using the record IDs by calling User.find_all_by_id(User.active.map(&:id)), but I find this to be more of a workaround rather than a possible solution since it generates an extra SQL query.
Are there any other possible solutions? What would be the preferred solution in this situation? I've read the answer given in the previous StackOverflow question about this, but it doesn't seem to give specific guidance of what would be considered correct.
Thanks in advance!
I believe that it would be customary and acceptable in this case to use :include instead of :join. I think that :join is only used in rare specialized circumstances, whereas :include is pretty common.
If you're not going to be updating all of the active users, then it's probably wise to add an additional named scope or find condition to further narrow down which users you're loading so that you're not loading extra users & subscriptions unnecessarily. For instance...
User.active.some_further_limiting_scope(:with_an_argument)
#or
User.active.find(:all, :conditions => {:etc => 'etc'})
If you decide that you still want to use the :join, and are only going to update a small percentage of the loaded users, then it's probably best to reload just the user you want to update right before doing so. Such as...
readonly_users = User.active
# insert some other code that picks out a particular user to update
User.find(readonly_users[#index].id).update_attributes(:etc => 'etc')
If you really do need to load all active users, and you want to stick with the :join, and you will likely be updating most or all of the users, then your idea to reload them with an array of IDs is probably your best choice.
#no need to do find_all_by_id in this case. A simple find() is sufficient.
writable_users_without_subscriptions = User.find(Users.active.map(&:id))
I hope that helps. I'm curious which option you go with, or if you found another solution more appropriate for your scenario.
I think the best solution is to use .join as you have already and do a separate find()
One crucial difference of using :include is that it uses outer join while :join uses an inner join! So using :include may solve the read-only problem, but the result might be wrong!
I ran across this same issue and was not comfortable using :readonly => false
As a result I did an explicit select namely :select => 'users.*' and felt that it seemed like less of a hack.
You could consider doing the following:
class User < ActiveRecord::Base
has_one :subscription
named_scope :active, :select => 'users.*', :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end
Regarding your sub-question: so am I not properly understanding the purpose of automatically setting the read-only flag on :joins?
I believe the answer is: With a joins query, you're getting back a single record with the User + Subscription table attributes. If you tried to update one of the attributes (say "subscription_num") in the Subscription table instead of the User table, the update statement to the User table wouldn't be able to find subscription_num and would crash. So the join-scopes are read-only by default to prevent that from happening.
Reference:
1) http://blog.ethanvizitei.com/2009/05/joins-and-namedscopes-in-activerecord.html
This seems like a bug in Rails to me, but there's probably not much I can do about that. So how can I accomplish my expected behavior?
Suppose we have:
class User < ActiveRecord::Base
has_many :awesome_friends, :class_name => "Friend", :conditions => {:awesome => true}
end
And execute the code:
>> my_user.awesome_friends << Friend.new(:name=>'jim')
Afterwards, when I inspect this friend object, I see that the user_id field is populated. But I would also expect to see the "awesome" column set to 'true', which it is not.
Furthermore, if I execute the following from the console:
>> my_user.awesome_friends << Friend.new(:name=>'jim')
>> my_user.awesome_friends
= [#<Friend id:1, name:"jim", awesome:nil>]
# Quit and restart the console
>> my_user.awesome_friends
= []
Any thoughts on this? I suppose the conditions hash could be arbitrarily complex, making integration into the setter impossible. But in a way it feels like by default we are passing the condition ":user_id => self.id", and that gets set, so shouldn't others?
Thanks,
Mike
EDIT:
I found that there are callbacks for has_many, so I think I might define the relationship like this:
has_many :awesome_friends,
:class_name => "Friend",
:conditions => {:awesome => true},
:before_add => Proc.new{|p,c| c.awesome = true},
:before_remove => Proc.new{|p,c| c.awesome = false}
Although, it's starting to feel like maybe I'm just implementing some other, existing design pattern. Maybe I should subclass AwesomeFriend < Friend? Ultimately I need a couple of these has_many relationships, and subclassing get's messy with all the extra files..
EDIT 2:
Okay, thanks to everyone who commented! I ultimately wrapped up the method above into a nice little ActiveRecord extension, 'has_many_of_type'. Which works like follows:
has_many_of_type :awesome_friends, :class_name => "Friend", :type=>:awesome
Which just translates to has_many with the appropriate conditions, before_add, and before_remove params (and it assumes the existence of a column named friend_type).
You need use:
my_user.awesome_friends.create(:name=>'jim') or my_user.awesome_friends.build(:name=>'jim')
In documentation:
has_many (:conditions)
Record creations from the association are scoped if a hash is used. has_many :posts, :conditions => {:published => true} will create published posts with #blog.posts.create or #blog.posts.build.
It's :class_name rather than :class, for one thing.
This isn't a bug I don't think. The :conditions hash only deterimines how you query for the objects. But I don't think it's rational to just assume that any object you stuff in the collection could be made to conform to the conditions.
In your simple example it makes sense, but you could also put more complex logic in there.
The documentation seems pretty clear on this as well:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
:conditions
Specify the conditions that the associated object must meet in order to be included as a WHERE SQL fragment, such as authorized = 1.
I have a polymorphic association like this -
class Image < ActiveRecord::Base
has_one :approval, :as => :approvable
end
class Page < ActiveRecord::Base
has_one :approval, :as => :approvable
end
class Site < ActiveRecord::Base
has_one :approval, :as => :approvable
end
class Approval < ActiveRecord::Base
belongs_to :approvable, :polymorphic => true
end
I need to find approvals where approval.apporvable.deleted = false
I have tried something like this -
#approvals = Approval.find(:all,
:include => [:approvable],
:conditions => [":approvable.deleted = ?", false ])
This gives "Can not eagerly load the polymorphic association :approvable" error
How can the condition be given correctly so that I get a result set with approvals who's approvable item is not deleted ?
Thanks for any help in advance
This is not possible, since all "approvables" reside in different tables. Instead you will have to fetch all approvals, and then use the normal array methods.
#approvals = Approval.all.select { |approval| !approval.approvable.deleted? }
What your asking, in terms of SQL, is projecting data from different tables for different rows in the resultset. It is not possible to my knowledge.
So you'll have to be content with:
#approvals = Approval.all.reject{|a| a.approvable.deleted? }
# I assume you have a deleted? method in all the approvables
I would recommend either of the answers already presented here (they are the same thing) but I would also recommend putting that deleted flag into the Approval model if you really care to do it all in a single query.
With a polymorphic relationship rails can use eager fetching on the polys, but you can't join to them because yet again, the relationships are not known so the query is actually multiple queried intersected.
So in the end if you REALLY need to, drop into sql and intersect all the possible joins you can do to all the types of approvables in a single query, but you will have to do lots of joining manually. (manually meaning not using rails' built-in mechanisms...)
thanks for your answers
I was pretty sure that this couldn't be done. I wanted some more confirmation
besides that I was hoping for some other soln than looping thru the result set
to avoid performance related issues later
Although for the time being both reject/select are fine but in the long run I
will have to do those sql joins manually.
Thanks again for your help!!
M
I have a Rails 3 project. With Rails 3 came Arel and the ability to reuse one scope to build another. I am wondering if there is a way to use scopes when defining a relationship (e.g. a "has_many").
I have records which have permission columns. I would like to build a default_scope that takes my permission columns into consideration so that records (even those accessed through a relationship) are filtered.
Presently, in Rails 3, default_scope (including patches I've found) don't provide a workable means of passing a proc (which I need for late variable binding). Is it possible to define a has_many into which a named scope can be passed?
The idea of reusing a named scope would look like:
Orders.scope :my_orders, lambda{where(:user_id => User.current_user.id)}
has_many :orders, :scope => Orders.my_orders
Or implicitly coding that named scope in the relationship would look like:
has_many :orders, :scope => lambda{where(:user_id => User.current_user.id)}
I'm simply trying to apply default_scope with late binding. I would prefer to use an Arel approach (if there is one), but would use any workable option.
Since I am referring to the current user, I cannot rely on conditions that aren't evaluated at the last possible moment, such as:
has_many :orders, :conditions => ["user_id = ?", User.current_user.id]
I suggest you take a look at "Named scopes are dead"
The author explains there how powerful Arel is :)
I hope it'll help.
EDIT #1 March 2014
As some comments state, the difference is now a matter of personal taste.
However, I still personally recommend to avoid exposing Arel's scope to an upper layer (being a controller or anything else that access the models directly), and doing so would require:
Create a scope, and expose it thru a method in your model. That method would be the one you expose to the controller;
If you never expose your models to your controllers (so you have some kind of service layer on top of them), then you're fine. The anti-corruption layer is your service and it can access your model's scope without worrying too much about how scopes are implemented.
How about association extensions?
class Item < ActiveRecord::Base
has_many :orders do
def for_user(user_id)
where(user_id: user_id)
end
end
end
Item.first.orders.for_user(current_user)
UPDATE: I'd like to point out the advantage to association extensions as opposed to class methods or scopes is that you have access to the internals of the association proxy:
proxy_association.owner returns the object that the association is a part of.
proxy_association.reflection returns the reflection object that describes the association.
proxy_association.target returns the associated object for belongs_to or has_one, or the collection of associated objects for has_many or has_and_belongs_to_many.
More details here: http://guides.rubyonrails.org/association_basics.html#association-extensions
Instead of scopes I've just been defining class-methods, which has been working great
def self.age0 do
where("blah")
end
I use something like:
class Invoice < ActiveRecord::Base
scope :aged_0, lambda{ where("created_at IS NULL OR created_at < ?", Date.today + 30.days).joins(:owner) }
end
You can use merge method in order to merge scopes from different models.
For more details search for merge in this railscast
If you're just trying to get the user's orders, why don't you just use the relationship?
Presuming that the current user is accessible from the current_user method in your controller:
#my_orders = current_user.orders
This ensures only a user's specific orders will be shown. You can also do arbitrarily nested joins to get deeper resources by using joins
current_user.orders.joins(:level1 => { :level2 => :level3 }).where('level3s.id' => X)