I'm using the paper_trail-association_tracking gem to save PaperTrail versions for AR associations. I have a model, let's call it User, that has many Groups and has many Skills. Both of these associations have join tables (user_groups and user_skills). So my User model has two has_many :through associations. All of the models (User, Group, Skill, UserSkill, UserGroup) have PaperTrail enabled on them.
class User < ApplicationRecord
has_paper_trail
has_many :groups, through: user_groups
has_many :user_groups
has_many :skills, through: user_skills
has_many :user_skills
end
Say I want to reify an earlier version of the User model, along with their Skills (user_skills records), but not revert to an earlier version of the user's Groups (user_groups). The code looks something like this:
me = User.find(1)
older_me = me.versions.last.reify(has_many: true)
older_me.save
This will reify both UserGroups and UserSkills for this User, but I want it to reify just UserSkills. It seems the gem will reify all associations automatically, based on this source code I looked through.
So is there a way to specify which association I want to be reified? Or is it not best practices to do this? For my use case, I can't see a problem with this unless the user itself was actually deleted or something like that.
Sorry my models here sound kinda weird, I tried to use something generic and simplified, my real use case and models are a bit complex but this question demonstrates the bottom line of my issue.
Related
I'm trying to generate a one-to-many relationship between users and advice. One user can have many advices.
rails g model User
rails g model Advice user:references
I noticed that this doesn't automatically generate the line has_many :advices in the user.rb file. However, in the advice.rb file, the line belongs_to :user was auto-generated.
In the rails console, I am still able to create multiple Advices for one user without an error.
My question is, is the has_many :advices line necessary in the user.rb file? If not, then why do the Rails guides recommend it?
The has_many association tells Rails that the objects are related and adds methods to the object for querying the associated objects. You could live without it, but it makes things easier.
See the first chapter here:
http://guides.rubyonrails.org/v2.3.11/association_basics.html
Another reference:
https://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many
When you use one-to-many associations you are telling your User model that he has zero or more instances of the model Advice, while Advice belongs to only one model User and its reference to it.
This is how your models should be:
class User < ApplicationRecord
has_many :advices
end
class Advice < ApplicationRecord
belongs_to :user
end
Even if you don't need to declare it right now, you might need it in a long run, so it is always better to have it set. You can read more about this topic here.
So I'm using the rails3_acts_as_paranoid gem, and having some problems controlling scope with has_many :through associations.
For example
# User.rb
acts_as_paranoid
has_many :folders
has_many :files, :through => :folders
-
# Folder.rb
acts_as_paranoid
belongs_to :user
has_many :files, :dependent => :destroy
-
# File.rb
acts_as_paranoid
belongs_to :files
Now lets just say somewhere in the users_controller.rb i want to query all files belonging to a user, whether they are deleted, and/or belong to folders that have been deleted. So naturally I would assume to do something like the following
current_user.files.with_deleted
with_deleted method does it's job at removing the files.deleted_at IS NULL
...BUT... it doesnt remove the default_scope for folders which is used kind of behind the curtain. So we still have a folders.deleted_at IS NULL condition, preventing me from retrieving the files that belong to those folders where deleted_at is not null.
I want to keep using acts_as_paranoid, as it is incredibly useful in all other places of my app, and am trying not to do something like manual filtering and popping out elements of the .where_values array. But I don't know too much about handling complex scopes or what methods are available.
Well my question got down-voted, not sure why. But I found the answer:
When on a has_many through, the problem I was having was an inability to control the scope of the through model (Folders in this case).
Turns out you can just do this
#myvar = Folder.unscoped { current_user.files.with_deleted }
To whoever downvoted it - I'd like to know why, so I can ask better questions next time. Thanks!
I use paper_trail in rails to track my models versions. But the documentation on the github repo indicates that the gem doesn't support has_many, belongs_to associations.
Let's say I've an app that records the ceos names of some comapnies:
class Company < ActiveRecord::Base
has_many :ceos
has_paper_trail
end
class Ceo < ActiveRecord::Base
belongs_to :companies
has_paper_trail
end
The above example represent the information of ABC Inc.
company.name => "ABC"
company.ceo.past => "John Henry"
company.ceo.present => "Amy Warren"
How can I implement the following operation so it will reset the company and the company's ceos names to the last version?
You could attempt to re-model the association to remove the has_many because in the case of CEOs, a company may have_many CEOs through its life, but it only has_one CEO for a certain period.
The implementation of this might be a has_one to a join table made up of the ID of both CEO and Company, and the time periods it was valid for.
A beneficial side effect is it would become trivial to have a person be CEO of a company 2 times with another CEO in between and have easy traversal of that in the domain.
The instructions for how to handle this can be found in the README: https://github.com/airblade/paper_trail/blob/master/README.md#associations
Basically will need to create a version_associations table, either at installation time with the rails generate paper_trail:install --with-associations option or manually for this to work.
I suggest you read the full documentation on Github for details on how this works.
I have been around and around with this. Have seen similar questions here but it seems I have an extra complicating factor; what worked for them doesn't work for me.
I have models and tables for User, Group, GroupMember. A group is owned by a user, but each group can have an arbitrary number of group members, i.e., other users. Here are my associations:
In User,
has_many :groups
In Group,
belongs_to :user
has_many :group_members
has_many :members, :class_name => "User", :through=>:group_members
In GroupMember,
belongs_to :member, :class_name=>"User"
belongs_to :group
To get at the members of a group, then, in groups_controller.rb I do this:
#groupmembers = #group.group_members.all
However, that generates the following error:
NameError in GroupsController#show
uninitialized constant Group::GroupMember
Like I say, I have been around and around with this... where have I gone wrong? Thanks in advance for looking...
I finally got this working on my own. The part I was missing was in the User class; since User is the underlying class of Member, I needed this:
belongs_to :groupmember, :foreign_key=>"member_id"
Once that was in place, Rails was able to find everything as it should, e.g,
Group.find(1).members now finds all users who belong to the group with an ID of 1.
Assuming you have a model called GroupMembers (which you should given this is a has_many through association), your non-through association should look like this on both the Group and Member models:
has_many :group_members, :class_name => "GroupMembers"
For some reason rails isn't pluralizing the second model in the association, so just do it yourself.
Sometimes it can also be as simple as the belongs_to :model needs to be singular instead of plural. I made this mistake on my relationship today.
Greetings,
I have an application where Companies and Users need to belong to each other through a CompanyMembership model, which contains extra information about the membership (specifically, whether or not the User is an admin of the company, via a boolean value admin). A simple version of the code:
class CompanyMembership < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
class Company < ActiveRecord::Base
has_many :company_memberships
has_many :users, :through => :company_memberships
end
class User < ActiveRecord::Base
has_many :company_memberships
has_many :companies, :through => :company_memberships
end
Of course, this makes it simple to get all the members of a company via company.users.all, et al. However, I am trying to get a list of all Users in a Company who are admins of that Company (and also to test whether a user is an admin of a given company). My first solution was the following in company.rb:
def admins
company_memberships.where(:admin => true).collect do |membership|
membership.user
end
end
def is_admin?(user)
admins.include? user
end
While this works, something feels inefficient about it (it's iterating over each membership, executing SQL each time, right? Or is Relation smarter than that?), and I'm not sure if there's a better way to go about this (perhaps using scopes or the fancy new Relation objects that Rails 3 uses?).
Any advice on the best way to procede (preferably using Rails 3 best practices) would be greatly appreciated!
I believe I was going about this the wrong way, specifying conditions on company_memberships instead of users, which was what I actually wanted (a list of Users, not a list of CompanyMemberships). The solution I think I was looking for is:
users.where(:company_memberships => {:admin => true})
which generates the following SQL (for company with ID of 1):
SELECT "users".* FROM "users"
INNER JOIN "company_memberships"
ON "users".id = "company_memberships".user_id
WHERE (("company_memberships".company_id = 1))
AND ("company_memberships"."admin" = 't')
I'm not sure yet if I'll need it, but the includes() method will perform eager loading to keep down the number of SQL queries if necessary:
Active Record lets you specify in
advance all the associations that are
going to be loaded. This is possible
by specifying the includes method of
the Model.find call. With includes,
Active Record ensures that all of the
specified associations are loaded
using the minimum possible number of
queries.queries. RoR Guides: ActiveRecord Querying
(I'm still open to any suggestions from anyone who thinks this isn't the best/most effective/right way to go about this.)
An even cleaner way would be to add an association to your Company model, something like this:
has_many :admins, :through => :company_memberships, :class_name => :user, :conditions => {:admin => true}
You might have to dig into the rails doc to get the exact syntax right.
You shouldn't need :include, unless you have other classes associated with :user that you might reference in your view.
How about something like this:
Company.find(:id).company_memberships.where(:admin => true).joins(:user)
I stumbled across this answer and believe that nowadays there is a nicer way using has_many association scopes (has_many documentation):
has_many :admins, -> { where(admin: true) }, through: :company_memberships, class_name: :user
The second parameter of a has_many association can be a proc or lambda that contains your filter.
I made the activerecord_where_assoc gem to do this.
With it, no need for joins or includes. Doing eager loading remains your choice, not an obligation.
users.where_assoc_exists(:company_memberships, admin: true, company_id: #company.id)
The question is unclear on how to filter the company, so I added it myself. Without this, if a user can be in multiple company, it being admin in one company could mean being treated as an admin in another company.
The gem doesn't work in Rails 3, but we are 9 years later and Rails 4.1 and more are supported.
Here are the introduction and examples. Read more details in the documentation.