I'm working with models analogous to the following:
class Owner < ActiveRecord::Base
has_many :owned
end
class Owned < ActiveRecord::Base
belongs_to :owner
end
You can presume that owned_id and owner_id are in the right places. The trouble is that, in a controller for a different mvc chain in the app,
#owner = Owned.find_by_id(owned_id, :include => :owner)
doesn't work. I get the owner_id, column, naturally, but can't then do
#owned.owner # is just nil
What gives? I mean, I could do the assignment directly before passing the result on to the view:
#owned.owner = Owner.find_by_id(#owned.owner_id)
but that just seems silly. Come on, embarrass me. What's the obvious thing that I've missed? This works in other places in my app, but I can't spot the differences. Are there some common traps? Anything helps.
Thank you
I just keep winning. The corresponding 'Owner' object had been deleted from the owners table.
The funny thing is, before I created an account, I had tons of karma on my cookie-based identity. Then my cookies became corrupted, and I can't ask anything but stupid questions anymore, and my karma sits at 1. Oh well.
Reputation on StackOverflow is not cookie based. You may have to log in again or something.
Your question seems to imply that you have an owned_id field in the owner table. You don't need that and should remove it.
You just need an owner_id integer field in the owned table.
You can access your records and relationships in a number of ways. First let's start by accessing the owner record first.
owner = Owner.find(owner_id)
owned = owner.owned # this is an array since you a 'has_many' relationship
Normally you'd want to access the owned records in the following way:
for owned in owner.owned
puts owned.name # or access any other attributes
end
If you would like to access the owned records first you could do the following:
#owned = Owned.find(:all, :conditions => [ "owner_id = ?", owner_id ])
# #owned is an array so you need to iterate through it
for owned in #owned
puts owned.owner.name # or access any other attribute from the owner
end
Once you've got these queries working fine you can worry about eager loading by adding :include in your find statements. Note that this can be of interest for optimization but not necessary from the get go.
I hope this helps.
Related
So for some reason, my client will not drop inactive users from their database. Is there a way to globally exclude all inactive users for all ActiveRecord calls to the users table?
EX: User.where("status != 'Inactive'")
I want that to be global so I don't have to include that in EVERY user statement.
Yes, you can set a default scope:
class User < ActiveRecord::Base
default_scope where("status != 'Inactive'")
end
User.all # select * from users where status != 'Inactive'
... but you shouldn't.
It will only lead to trouble down the road when you inevitably forget that there is a default scope, and are confused by why you can't find your records.
It will also play havoc with associations, as any records belonging to a user not within your default scope will suddenly appear to belong to no user.
If you had a simple setup with posts and users, and users had a default scope, you'd wind up with something like this:
# we find a post called 1
p = Post.first # <#post id=1>
# It belongs to user 2
p.user_id # 2
# What's this? Error! Undefined method 'firstname' for `nil`!
p.user.first_name
# Can't find user 2, that's impossible! My validations prevent this,
# and my associations destroy dependent records. Can't be!
User.find(2) # nil
# Oh, there he is.
User.unscoped.find(2) <#user id=2 status="inactive">
In practice, this will come up all the time. It's very common to find a record by it's ID, and then try to find the associated record that owns it to verify permissions, etc. Your logic will likely be written to assume the associated record exists, because validation should prevent it from not existing. Suddenly you'll find yourself encountering many "undefined method blank on nil class" errors.
It's much better to be explicit with your scope. Define one called active, and use User.active to explicitly select your active users:
class User < ActiveRecord::Base
scope :active, -> where("status != 'Inactive'")
end
User.active.all # select * from users where status != 'Inactive'
I would only ever recommend using a default_scope to apply an order(:id) to your records, which helps .first and .last act more sanely. I would never recommend using it to exclude records by default, that has bitten me too many times.
Sure, in your model define a default scope
see here for more info
eg
class Article < ActiveRecord::Base
default_scope where(:published => true)
end
Article.all # => SELECT * FROM articles WHERE published = true
As an alternative to #meagar's suggestion, you could create a new table with the same structure as the Users table, called InactiveUsers, and move people into here, deleting them from Users when you do so. That way you still have them on your database, and can restore them back into Users if need be.
I have two models one of them is a User, and another Comments. Comments belong to User.
class User < ActiveRecord::Base
act_as_paranoid
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
end
When I do user.delete in my controller I get the expected result of the deleted_at column being set and the record being hidden.
My problem is the Comment associations for the user are set to null. So now on the site it shows no user owning the comment. I'd like the comment to still show the user's name not be "None" or "Anonymous" etc.
Looking at the source on github https://github.com/goncalossilva/rails3_acts_as_paranoid/blob/rails3.2/lib/acts_as_paranoid/core.rb it seems to call run_callbacks which in turn results in Rails 3 falling back to Nullify default for associations.
In my case I just want the user account to be closed off when deleted. Not showing up in queries anymore so that Authlogic will deny them and the User index page won't show them. But still allowing everything a user owns to still be owned by them (since they may come back, etc.).
Is there a better way to do this then acts_as_paranoid?
Rather then go to the trouble of overriding the destroy method I created a close method that simply sets closed_at to a timestamp. If you set default scope to something like:
default_scope { where("closed_at IS NULL") }
Then the model won't show up to any queries including User.All. You can delete the scope to get a full query essentially I took these ideas from act_as_paranoid but much more simplified. The problem is that then even though the Comments still have user_id set, the default scope runs with any association load. So say
c = Comment.first
c.user
That will output nil if user_id is a closed account. In my case the easiest solusion was to remove default scoping and modify my Authlogic function to:
def self.find_by_username_or_email(login)
u = User.find(:first, :conditions => ["lower(username) = ?", login.downcase]) || User.find_by_email(login)
return u unless u.closed_at
end
This way closed accounts can't login. Anywhere I list out users in my views I used a hide_closed scope.
Not sure if this was the best most elegant solution. But for my purposes it works.
I have a Record model and in order to edit this model, you must be logged in as an instance of Admin. I would like to have a column called last_modified_by which points to the Admin who last modified the Record. In the database, I was thinking it would be good in the records table to add a column that holds the Admin's id; however, the only way I know how to do that is with an association. These two models are not associated with each other so an association doesn't make a lot of sense. Is there any other way I might be able to accomplish this task without resorting to associations? Any advice would be much appreciated!
Hmm, I think the association is a good tool here. You might want to try to hack it somehow but I think nothing you can conjure up will ever be as good as an association via a foreign_key(also so fast). But perhaps you would like to name your association and do something like:
class Record < ActiveRecord::Base
belongs_to :culprit, :class_name => 'Admin', :foreign_key => 'last_modified_by'
end
or give it some more senseful naming?
You could create an Active Record before_save callback. The callback would save the admin's id into the last_modified_column. This would make sure the admin id is saved/updated each time there is a change to the model.
For example, assuming admin is #admin:
class Record < ActiveRecord::Base
before_save :save_last_modified
def save_last_modified
self.last_modified_column = #admin.id
end
As for getting #admin, you could employ a method similar to this, and set #admin = Admin.current (like User.current in the link) somewhere in the Record model.
Let's say that I'm looking for a specific item in a has_many association.
Is there a way to find that won't rehit the database if what I'm requesting is already cached?
Here's my situation (which is not working and does not do what I requested):
class User
has_many :friendships
def some_function(buddy)
f = friendships.detect{|friendship| friendship.user == self &&
friendship.friend == buddy}
f.do_something
end
end
The function is susceptible to N+1 problems, but most of the time I will already have either used :include or preloaded the user's friendships elsewhere. However, if I didn't do this, then this current function will fall back to requesting all the friendships, even though it only needs one. The solution would seem to be something like this:
f = Friendship.find_by_user_id_and_friend_id id, buddy.id
However, this will hit the database again even if all the friendships are cached and I have what I need right there.
Is there any way to either use the cached copy, or if none is available, search a specific record?
What you want is the #loaded? method of the association proxy:
class User
has_many :friendships
def some_function(buddy)
if friendships.loaded? then
f = friendships.detect{|friendship| friendship.user == self &&
friendship.friend == buddy}
else
Friendship.find_by_user_id_and_friend_id id, buddy.id
end
end
end
Unfortunately, #loaded? does not appear in the public documentation. You can always guard your call to #loaded? with friendships.respond_to?(:loaded?).
Documentation for AssociationProxy#loaded? on GitHub.
If you already gave called #user.friendships and want to access the cached version, you could use
def some_function(buddy)
f = friendships.select { |f| f.buddy_id == buddy.id }
f.do_something
end
The select method does not make a DB query and uses the cached friendships relationship. If you have not already called #user.friendships, the first call to this method will run the query and subsequent calls will use the cached version.
If you have already accessed the #user.friendships attribute (and thus, hitting the database to get the data), those associations will be stored in memory and you will no longer need to hit the database.
When it comes to finding a specific record out of an associated :has_many dataset, I'm not quite sure. One option is to do a find_by_sql to write the sql to grab that specific record. You could also do something like this:
class User
has_many :friendships
def some_function(buddy)
f = Friendships.find_by_user_id(self.id, :conditions => "buddy_id = #{buddy.id}")
f.do_something
end
end
Which does a basic SQL search to match any records that have the current users's ID and the buddy ID that is passed into the function.
The business logic is this: Users are in a Boat through a join table, I guess let's call that model a Ticket. But when a User instance wants to check who else is on the boat, there's a condition that asks if that user has permission see everyone on the Boat, or just certain people on the Boat. If a User can see everyone, the normal deal is fine: some_user.boats.first.users returns all users with a ticket for that boat. But for some users, the only people that are on the boat (as far as they're concerned) are people in, let's say the dining room. So if User's ticket is "tagged" (using an acts_as_taggable style system) with "Dining Room", the only Users returned from some_user.boats.first.users should be Users with tickets tagged "Dining Room".
Just for the record, I'm not trying to design something to be insane from the getgo - I'm trying to wedge this arbitrary grouping into a (mostly) existent system.
So we've got:
class User
has_many :tickets
has_many :boats, :through => :tickets
end
class Ticket
belongs_to :user
belongs_to :boat
end
class Boat
has_many :tickets
has_many :users, :through => :tickets
end
Initially, I thought that I could conditionally modify the virtual class like:
singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
has_many :tickets, :include => :tags, :conditions => ['tags.id in (?)', [#{tag_ids.to_s(:db)}]]
code
)
That gets all the way down to generating the SQL, but when generated, it generates SQL ending in:
LEFT OUTER JOIN "tags" ON ("tags"."id" = "taggings"."tag_id") WHERE ("tickets"._id = 1069416589 AND (tags.id in (5001,4502)))
I've tried digging around the ActiveRecord code, but I can't find anywhere that would prefix that 'id' in the SQL above with an underscore. I know that associations are loaded when an ActiveRecord class is loaded, and I'd assume the same with a singleton class. shrug.
I also used an alias_method_chain like:
singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
def tickets_with_tag_filtering
tags = Tag.find(etc, etc)
tickets_without_tag_filtering.scoped(:include => :tags, :conditions => {:'tags.id' => tags})
end
alias_method_chain :tickets, :tag_filtering
code
)
But while that approach produces the desired Tickets, any joins on those tickets use the conditions in the class, not the virtual class. some_user.boats.first.users returns all users.
Any type of comment will be appreciated, especially if I'm barking up the wrong tree with this approach. Thanks!
So a wild guess about your underscore issue is that Rails is generating the assocation code based on the context at the time of evaluation. Being in a singleton class could mess this up, like so:
"#{owner.table_name}.#{association.class.name}_id = #{association.id}"
You could get in there and define a class name property on your singleton class and see if that fixes the issue.
On the whole I don't recommend this. It creates behavior that is agonizing to track down and impossible to extend effectively. It creates a landmine in the codebase that will wound you or someone you love at a later time.
Instead, consider using a named_scope declaration:
class User
has_many :taggings, :through => :tickets
named_scope :visible_to, lambda { |looking_user|
{ :include => [ :tickets, :taggings ],
:conditions => [ "tickets.boat_id in (?) and taggings.ticket_id = tickets.id and taggings.tag_id in (?)", looking_user.boat_ids, looking_user.tag_ids ]
}
}
end
While you may have to go back and edit some code, this is much more flexible in the ways it can be used:
Boat.last.users.visible_to( current_user )
It's clear that a restriction is being placed on the find, and what the purpose of that restriction is. Because the conditions are dynamically calculated at runtime, you can deal with the next weird modification your client hits you with. Say some of their users have xray vision and clairvoyance:
class User
named_scope :visible_to, lambda { |looking_user|
if looking_user.superhuman?
{}
else
{ :include => [ :tickets, :taggings ],
:conditions => [ "tickets.boat_id in (?) and taggings.ticket_id = tickets.id and taggings.tag_id in (?)", looking_user.boat_ids, looking_user.tag_ids ]
}
end
}
end
By returning an empty hash, you can effectively nullify the effect of the scope.
Why not just grab all users on the boat and include their tags.
Then run a quick filter to include & return only the users with the same tag as the inquiring user.
What version of Rails are you using? Have you tried upgrading to see if the underscore issue is fixed? It's like it can't find the foreign key to put in as "tag_id" or somethin'.
My ruby-fu is limited, so I'm not sure how to dynamically include the correct method options at run-time.
Just to help you clarify, you have to worry about this two places. You want to filter a user's viewable users so they only see users with the same tags. Your structure is:
user <--> tickets <--> boats <--> tickets <--> users
... right?
So, you need to filter both sets of tickets down to the ones with the current_user's tags.
Maybe you just need a current_user.viewable_users() method and then filter everything through that? I'm not sure what existing functionality you've got to preserve.
Blech, I don't feel like I'm helping you at all. Sorry.
Your approach is the problem. I know it seems expedient at the moment to hack something in where you don't have to refactor the existing call sites, but I believe given time this will come back to haunt you as the source of bugs and complexity.
Sleeping dogs that lie come back to bite you hard, in my experience. Typically in the form of a future developer who doesn't know your association is "magic" and uses it assuming it's just pail ole rails. He/she likely won't even have a reason to write a test case that would expose the behavior either, which raises the odds you'll only find out about the bug when it's live in production and the client is unhappy. Is it really worth the time you're saving now?
Austinfrombostin is pointing the way. Different semantics? Different names. Rule number one is always to write code that says what it does as clearly as possible. Anything else is the path of madness.