I have a model "Trip" and I want to be able to have only certain users in "Group" create "comments".
When I first created the trip model, I had it set so that only one user can edit it. Now, I want to be able to invite other users to edit it as well. The part that is tripping me up (pun intended) right now is that I have both trip belong_to :user (the user who created it) and trip has_many :users, :through => :group.
Questions:
Is this allowed per Rails convention?
Based on my model, group will have both user_id and trip_id. Is this the best way to approach this problem? That is, should there be a new record in the database for every user I invite to the group?
Thanks.
user.rb
class User < ActiveRecord::Base
has_many :trips, :through => :groups
has_many :trips, :dependent => :destroy
has_many :groups
has_many :comments, :dependent => :destroy
end
group.rb
class Group < ActiveRecord::Base
belongs_to :trip
belongs_to :user
end
trip.rb
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :traveldeal
has_many :groups
has_many :users, :through => :groups
end
comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, :polymorphic => true
end
view (show.html.erb)
<% unless #user.trips.empty? %>
<% #user.trips.each do |trip| %>
<!-- Content here -->
<% end %>
<% end %>
Normal Rails convention would have been to call the Group model something like TripPerson to specify the two tables that it was joining, but these join tables are a natural byproduct of relational databases. You'll have to continue creating a new Group object for every person invited to the trip.
Related
The image shows part of my data model. I would like to fetch all items that are associated with a user (through organizations and items_group). How should I change the models and write this query in the controller? Using :through => organizations I can get all items_groups but I don't how to include one more relation to query related items.
class User < ActiveRecord::Base
has_and_belongs_to_many :organizations
has_many :items_groups, :through => :organizations
end
class Organization < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :items_groups
has_many :items, :through => :items_groups
end
class ItemsGroup < ActiveRecord::Base
has_many :items, :inverse_of => :items_group
has_and_belongs_to_many :organizations
has_many :users, :through => :organizations
end
I think you might have to do it back-to-front and find items joined back to your user.
You could define method like this in your User class:
def items
Item.joins(:items_group => {:organizations => :users}).where("users.id" => self.id).select("distinct items.*")
end
The items it returns will be read-only because of the explicit select but I think you'll want that to avoid returning individual items more than once.
If you set in your models the relationships this should work:
users.organizations.item_groups.items
Though for it to work your models should contain this:
class User < ActiveRecord::Base
has_many :organizations, :through => :organization_users
end
class Organization < ActiveRecord::Base
has_many :item_groups, :through => :items_groups_organizations
end
class ItemsGroup < ActiveRecord::Base
belongs_to :item
end
Hope it works for you!
I basically followed the ROR guide, http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association, to create the relationship models as shown below.
Because of the through association, I figured that #user.trips would give you both the trips that the user created and the trips that belong to the user. However, when I do #user.trips.count in console, the result was only the number of trips that the users created; the trips that belonged to the user through the 'group' association was not counted.
Question: How do I get my view to display both the trips that the user created and the trips that the user belongs to through 'group'?
user/show.html.erb
<% unless #user.all_trips.empty? %>
<% #user.all_trips.each do |trip| %>
<!-- Content -->
<% end %>
<% end %>
user.rb
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:source => :trip
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
self.trips | self.group_trips
end
end
trip.rb
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :traveldeal
has_many :groups
has_many :users, :through => :groups
end
group.rb
class Group < ActiveRecord::Base
belongs_to :trip
belongs_to :user
end
Thanks!
Edit: Modified code per TSherif's partial solution.
Edit 2: Fixed up the all_trips method. Everything appears to work for me at this point.
Oh! I think I get what you're trying to do and why it's a problem. I was wondering why has_many :trips was called twice. But from what I understand, you have two different User-Trip relationships. These two can't have the same name, otherwise one will hide the other. Try something like this:
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:class_name => "Trip"
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
Trip.joins(:groups).where({:user_id => self.id} | {:groups => {:user_id => self.id}})
end
end
Or if you're using an older version of Rails that doesn't have MetaWhere:
def all_trips
Trip.joins(:groups).where("(trips.user_id = ?) OR (groups.user_id = ?)", self.id, self.id)
end
First I'm using Rails 3.1 from the 3-1-stable branch updated an hour ago.
I'm developing an application where I have 3 essential models User, Company and Job, Here's the relevant part of the models:
class User < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :companies, :through => :companies_users, :source => :company
end
class Company < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :employees, :through => :companies_users, :source => :user
has_many :jobs, :dependent => :destroy
end
class Job < ActiveRecord::Base
belongs_to :company, :counter_cache => true
end
class CompaniesUsers < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
The code works just fine, but I have been wondering if it's possible to:
I want to link a job with an employer, so think of this scenario: A user John who's an employee at Example, he posted the job Rails Developer, so I want to access #job.employer and it should get me back the user John, in other words:
#user = User.find_by_name('john')
#job = Job.find(1)
#job.employer == #user #=> true
So I thought of two possible solutions
First solution
class Job
has_one :employer, :through => :employers
end
class User
has_many :jobs, :through => :employers
end
class Employer
belongs_to :job
belongs_to :user
end
Second solution
class Job
has_one :employer, :class_name => "User"
end
class User
belongs_to :job
end
Which route should I go? Is my code right ?
I have another question, how to get rid of the class_name => "CompaniesUsers" option passed to has_many, should the class be Singular or Plural ? Should I rename it to something like Employees ?
P.S: I posted the same question to Ruby on Rails: Talk
Unless I'm missing something, I'd suggest simply doing
class Job
belongs_to :employer, :class_name => "User"
end
class User
has_many :jobs
end
This would give you methods like
user = User.first
user.jobs.create(params)
user.jobs # array
job = user.jobs.first
job.employer == user # true
You'll need an employer_id integer field in your Jobs table for this to work.
Typically you want to name your pass through model:
company_user
Then you don't need this:
class_name: "CompaniesUsers"
Just make sure the name of your database table is:
company_users
What you have works for you, so that's great. I just find when I don't follow convention I
run in to trouble down the road.
Given the following database model, how and where would you define the deletion relationships between the models? I figured out the basic table association setup but when I want to add dependencies to enable the deletion of nested objects I get lost.
Here is the relationship model I created.
class User < ActiveRecord::Base
has_many :studies
end
class Study < ActiveRecord::Base
has_many :internships
belongs_to :student, :class_name => "User", :foreign_key => "user_id"
belongs_to :subject
belongs_to :university, :class_name => "Facility", :foreign_key => "facility_id"
accepts_nested_attributes_for :subject, :university, :locations
end
class Subject < ActiveRecord::Base
has_many :studies
end
class Internship < ActiveRecord::Base
belongs_to :study
belongs_to :company, :class_name => "Facility", :foreign_key => 'facility_id'
accepts_nested_attributes_for :company, :study
end
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
accepts_nested_attributes_for :locations
end
class Location < ActiveRecord::Base
belongs_to :facility
end
Where would you put :dependent => :destroy and :allow_destroy => true to enable the following scenarios? I do not want to confuse you. Therefore, I leave out my tryings.
Internship scenario: A user wants to delete an internship.
Its associated company (facility) can be deleted if the company is not related to another internship.
If so, the locations of the associated company can be deleted.
The related study will not be affected.
Study scenario: A user wants to delete a study.
Its associated subject can be deleted if no other study refers to this subject.
Its associated university (facility) can be deleted if no other study refers to this university.
Its associated internships can be deleted. The company can only be deleted if no other internship refers to it.
I am totally unsure whether I can add :dependent => :destroy only after has_one and has_many or also after belongs_to.
Edit: To simplify the problem please stick to the following (reduced) example implementation.
class Study < ActiveRecord::Base
belongs_to :subject
accepts_nested_attributes_for :subject, :allow_destroy => true
end
class Subject < ActiveRecord::Base
has_many :studies, :dependent => :destroy
end
In my view I provide the following link.
<%= link_to "Destroy", study, :method => :delete, :confirm => "Are you sure?" %>
The path is based on the named routes given by a restful configuration in routes.rb.
resources :studies
resources :subjects
The study will be deleted when I click the link - the subjects stays untouched. Why?
I think your relations are the wrong way around here...
The accepts_nested_attributes_for should be declared on the model that has_many for the model that it has_many of. Also, in your example, destroying the subject would enforce dependent_destroy on the many studies, not the other way around.
You can add :dependent => :destroy to all three but I'm not sure if that'll give you enough power to do the checks required before determining whether an associated object should be destroyed.
You have a few options.
Add a before_destroy callback on each model that raises an exception or stops the delete from occurring.
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
def before_destroy
raise SomethingException if internships.any? || ...
# or
errors.add(...
end
end
or do it silently by overriding destroy
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
def destroy
return false if internships.any || ...
super
end
end
Note: this is basically meant for guidance only and may not be the correct way of overriding destroy etc...
In RoR3,
I have Users and Skills and each skill is created by a user. I wanted to record that, so I created a one to many relationship.
class User < ActiveRecord::Base
has_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :user
end
However, each user also has many skills in the sense that, user "Bob" created skill "Kung Fu", user "Charlie" created skill "Karate" and user "Bob" both created and is able to do both "Kung Fu" and "Karate"
How should I represent this with ActiveRecord? Should I just create a new table "user_skills" which has_many :skills? and belong_to :user?
There are two different associations here. The first is a one-to-many association. An user can be the creator of any number of skills. The second one is a many-to-many association, an user can have many skills and a skill can have many users.
The first one is a simple belongs_to <-> has_many declaration. For the second one, you either need a has_and_belongs_to_many declaration in both models, and a related join table, or a dedicated join model, and a has_many :through declaration. Let's try the first one:
Method 1: HABTM
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_and_belongs_to_many :users
end
This requires a join table called "skills_users" that has columns named user_id and skill_id
Method 2: Has many through (Join model)
The second one is similar, but adds a model that acts as the middleman. This has an added benefit that you can include additional columns in the join model, like for example a skill level.
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_many :user_skills
has_many :skills, :through => :user_skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_many :user_skills
has_many :users, :through => :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
Having those two models
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
has_and_belongs_to_many :users
end
You would have to create an extra migration (without the model)
rails generate migration CreateSkillsUsersJoin
which will give you
class CreateSkillsUsersJoin < ActiveRecord::Migration
def self.up
create_table :skills_users, id => false do |t|
t.references "user"
t.references "skill"
end
add_index :skills_users,["user_id","skill_id"]
end
def self.down
drop_table :skills_users
end
end
The methods self.up and self.down you will have yo add them
You'd be well served using a gem like acts_as_taggable_on which you'd be able to simply setup and use in your User model, something like:
acts_as_taggable_on :skills
Honestly, they've figured all this stuff out, as it's not as simple as what you're trying to do, OR I should rephrase that and say, what you are trying to do is overtly 'complex' and this gem allows you to just keep on, keeping on after it's set up.
Read the Readme.