Maintain associations with act_as_paranoid - ruby-on-rails

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.

Related

where constraint on a related record

I'm not getting a concept (nothing new there) on how to scope a Active Record query. I want to only receive the records where there is a certain condition in a related record. The example I have happens to be polymorphic just in case that is a factor. I'm sure there is somewhere where this is explained but I have not found it for whatever reason.
My Models:
class User < ActiveRecord::Base
belongs_to :owner, polymorphic: true
end
class Member < ActiveRecord::Base
has_one :user, as: :owner
end
I want to basically run a where on the Member class for related records that have a certain owner_id/owner_type.
Lets say we have 5 Members with ids 1-5 and we have one user with the owner_id set to 3 and the owner_type set to 'Member'. I want to only receive back the one Member object with id 3. I'm trying to run this in Pundit and thus why I'm not just going at it form the User side.
Thanks for any help as always!!!
Based on your comment that you said was close I'd say you should be able to do:
Member.joins(:user).where('users.id = ?', current_user.id)
However based on how I'm reading your question I would say you want to do:
Member.joins(:user).where('users.owner_id = ?', current_user.id)
Assuming current_user.id is 3.
There may be a cleaner way to do this, but that's the syntax I usually use. If these aren't right, try being a little more clear in your question and we can go from there! :)

ActiveRecord exclude results for all queries

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.

Override mongomapper has_many association

I use Rails 3 with MongoMapper.
I want to add some records to the result of has many association.
For example, user has_many posts
class User
include MongoMapper::Document
many :posts
end
By default it will show only posts which belongs to the user, but if he/she specify special option in query (or in the user's settings menu, say show-commented=true), then I also need to add posts where user left any comments. So I think to override posts method
def posts
super + (show_commented_posts ? commented_posts : [])
end
But of course it doesn't work. How can I correctly override this method using mongo_mapper? Or is there any better approach for that problem?
Overriding methods on mongomapper is a very bad idea, you should try to refrain from doing it as it creates a lot of problems that are hard to trace back (I've been burned before by this).
Instead, you should consider using a scope such as
class Post
scope :related_to_user, lambda {|user| where('$or' => [ {user_id: user.id}, {'comments.user_id' => user.id}]) }
end
Then you can call
Post.related_to_user(current_user)

How to implement a last_modified_by (person) attribute on two unrelated models - Rails

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.

Eager loading not working in Rails 2.2.2

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.

Resources