has_many or HABTM - ruby

This question has been asked numerous times but I can't project any of the answers to my specific situation, so here goes.
I have an events, options and a templates table. There is also a join table called events_templates with the fields:
option_id
template_id
event_id
An event has one template per option. But obviously can have many templates because there are multiple options.
Currently my event model has the method
has_many :templates, class_name: "EventsTemplate"
Question is, is this the right setup or should I use HABTM?
THANKS so much in advance!

I think you should set this up a bit different, based on the requirements you specified.
has_many :templates, class_name: "EventsTemplate"
This won't work the way you want it to. It will give event an association templates, but calling that association will return EventTemplates instead.
You probably want to set this up using a through association.
class EventTemplate < ActiveRecord::Base
belongs_to :option
belongs_to :template
belongs_to :event
end
class Event
has_many :event_templates
has_many :templates, through: :event_templates
end

Related

Reify only one has_many :through association with PaperTrail?

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.

Check Before Destroy or Destroy all Associated? Rails 4

I currently have a Vendor Model which acts as sort of a router for my users. This Vendor Model links up to locations that the vendor owns and campaigns as well.
Once a user is created, it is given a Vendor association which basically unlocks the locations and campaigns that pertain to that given Vendor.
Now, my question is, is there a way that when a Vendor is "Destroyed" that is can destroy all associated locations, campaigns and users linked to it? If not, is there a way to run a validation that if a Vendor is being destroyed to check the database for associations and not allow the action of destroy if it finds records?
The reason I ask is because if I destroy the Vendor, it leaves the users without locations and campaigns without an associated Vendor which basically crashes my application.
User Model
belongs_to :vendor #user can only have one vendor
has_many :locations, :through => :vendor
has_many :clients, :through => :vendor
has_many :campaigns, :through => :vendor
Location Model
belongs_to :vendor
has_many :campaigns
Campaign Model
belongs_to :location
belongs_to :user
belongs_to :vendor
has_one :client
Vendor Model
has_many :locations #vendor can own many locations
has_many :clients #vendor can have many clients. Allows multiple employees to see client list
has_many :campaigns #vendor can have many campaigns. Allows multiple employees to see campaigns
In short you can do something like this by using the dependent option on your assoication. Can use either:
:destroy - destroys every associated object calling their destroy method.
:delete_all - deletes every associated object without calling their destroy method.
Update
Taking your models into consideration you should do the following:
class Vendor < ActiveRecord::Base
has_many :locations, dependent: :destroy
has_many :clients, dependent: :destroy
has_many :campaigns, dependent: :destroy
end
As stated above using the destroy option will destroy every associated object with that vendor.

Rails acts_as_paranoid and has_many :through

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!

Uninitialized constant error: Can't get this has_many :through correct

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.

Filtering child objects in a has_many :through relationship in Rails 3

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.

Resources