duplicate rows in join table with three has_many :through associations - sql

Description
I have four models:
User
Organization
Role
OrganisationUserRole
The idea is that one user can belong to many organizations and can have many roles, but just one per organization.
My models look like this:
user.rb
class User < ActiveRecord::Base
has_many :roles, :through => :organization_user_roles
has_many :organizations, :through => :organization_user_roles
has_many :organization_user_roles
end
organization.rb
class OrganizationUserRole < ActiveRecord::Base
has_many :organization_user_roles
has_many :users, :through => :organization_user_roles
has_many :roles, :through => :organization_user_roles
end
role.rb
class Role < ActiveRecord::Base
end
organization_user_role.rb
class OrganizationUserRole < ActiveRecord::Base
belongs_to :user
belongs_to :organization
belongs_to :role
end
I am seeding my db with following seeds.rb
require 'faker'
# seed with standard roles
role_list = [
[ "superadmin" ],
[ "admin" ],
[ "user" ],
[ "owner" ],
]
role_list.each do |role|
Role.create( :name => role[0] )
end
# create default superadmin & organization
p = User.create(email: 'thomas#aquarterit.com', password: 'password')
o = Organization.create(name: 'A Quarter IT', website: 'www.aquarterit.com')
o.users << User.find_by_email('thomas#aquarterit.com')
p.roles << Role.find_by_name("superadmin")
# 30 organizations, 3 users each
30.times do |organization|
o = Organization.create(name: Faker::Company.name, website: Faker::Internet.domain_name)
3.times do |user|
p = User.create(email: Faker::Internet.email, password: 'password')
p.roles << Role.find_by_name("user")
o.users << User.last
end
end
Problem
Migrations and rake db:seed run successfully, but afterwards the table
organization_user_roles
contains duplicate rows per user:
Row 1: User_id 1 -> Organization_id 1
Row 2: User_id 1 -> Role_id 1
How can I associate the user, organization and role at the same time in one row?
Thanks a lot in advance, you guys are are always an amazing help!

you need to add a database unique key for the three params, something like
add_index "organization_user_roles", ["user_id", "organization_id", "role_id"], name: "unique_roles", unique: true, using: :btree
then in your organization_user_role model
validates_uniqueness_of :role_id, scope: [:user_id, :organization_id]
i did a similar app with unique columns in my db and this solution worked

You need has_many through with 3 tables, look on this link:
Rails 3 has_many through with 3 tables

I ended up following the instructions here:
How to join mutli-role, multi organisation tables in Rails
And it worked like a charm.

Related

DB with multiple foreign keys linked to same Model on differents attributes

I am having difficulties building my DB relation, if anyone could give me a little help i would greatly appreciate!
I have one table named Person and another one called Company and Company has many Persons and Person belongs_to Company
Here the trick Company has_many Person threw an attribute called person and has_many Person threw another attribute called administrator
Could be see like
Coca-cola = Company.new
jonathan = Person.new / nicolas = Person.new
Coca-cola : {
person: jonathan,nicolas
administrator: nicolas
}
I did that first migration :
def change
add_reference :persons, :person, index: true
add_reference :persons, :administrator, index: true
add_foreign_key :persons, :companys, column: :person_id
add_foreign_key :persons, :companys, column: :administrator_id
end
then I added this relation to my model
class Company < ApplicationRecord
has_many :persons,
:class_name => "Person",
:foreign_key => "person_id"
has_many :administrators,
:class_name => "Person",
:foreign_key => "administrator_id"
end
and
class Person < ApplicationRecord
belongs_to :person,
:class_name => "Company",
optional: true
belongs_to :administrator,
:class_name => "Company",
optional: true
end
And unfortunately that doesnt working, any lead on what could cause the problem ?
Thanks a lots.
Jonathan.
Let me know if I understood your problem. Requirements are:
Person belongs_to Company
Company has_many Persons
Company has_many Administrators
I guess you could solve with the following code:
class ChangePersons < ActiveRecord::Migration
def change
add_column :persons, :administrator, :boolean, default: false
end
end
class Company < ApplicationRecord
has_many :persons, -> { where(administrator: false) }
has_many :administrators,
class_name: "Person",
foreign_key: "person_id",
-> { where(administrator: true) }
end
class Person < ApplicationRecord
belongs_to :company
end

Select from one table with two foreign keys in a single query

I have tow tables:
User:
user_id
user_blogs:
user_id | blog_id
blogs:
blog_id | source | identifier
Comments:
source | identifier | Field3
I want to be able to select all comments in the blogs that a user owns.
My models are related:
class User < ActiveRecord::Base
has_many :user_blogs
has_many :blogs, trhough: :user_blogs
end
class blogs < ActiveRecord::Base
has_many :comments,
:foreign_key => :source,
:primary_key => :source,
:conditions => Proc.new {
{:identifier=> self.identifier}
}
end
Right now I can retrieve all user comments using this:
User.first.blogs.map{|b| b.comments}
But this creates one query for each blog.
Is there a way to do this in one single step?
class User < ActiveRecord::Base
has_many :user_blogs
has_many :blogs, through: :user_blogs
has_many :comments, through: :blogs
end
class Blog < ActiveRecord::Base
has_many :comments, -> { where(identifier: identifier) }, foreign_key : source, primary_key: source
end
User.find(ID).comments
Yes, you need to use Rails eager_loading feature.
u = User.includes(blogs: :comments)
# now you can do
u.first.blogs.map { |b| b.comments }
Or, you can modify your model association definition also :
class User < ActiveRecord::Base
has_many :user_blogs
has_many :blogs, -> { includes(:comments) }, through: :user_blogs
end
Now, you can do the below without hitting multiple queries for each blog.
User.first.blogs.map { |b| b.comments }

Rails 4 has_many through with condition, joining same table twice

I have 3 models, User, Division and Staff. Staff belongs to User & Division respectively and stored a value called role. There are currently 2 roles which are "1" and "2". Below is my relationship.
User:
class User < ActiveRecord::Base
has_many :staffs, :dependent => :destroy
# manager view
has_many :manage_divisions, -> { where staffs: { role: 1 } }, :through => :staffs, :source => :division
has_many :manage_division_staffs, :through => :manage_divisions, :source => :staffs
# staff view
has_many :work_for_divisions, -> { where staffs: { role: 2 } }, :through => :staffs, :source => :division
has_many :work_for_division_staffs, :through => :work_for_divisions, :source => :staffs
end
I want to get the divisions which the user manage through the staff table by User.manage_divisions. Then get the staff list of those divisions by User.manage_division_staffs. But now when I call User.manage_division_staffs, what return seems the staffs with role = 1 of the divisions(but not all staffs). Also I want to do something similar for User.work_for_divisions.
Below is the sql query generated when I called User.manage_division_staffs:
SELECT "staffs".*
FROM "staffs"
INNER JOIN "divisions" ON "staffs"."division_id" = "divisions"."id"
INNER JOIN "staffs" "staffs_manage_division_staffs_join" ON "divisions"."id" = "staffs_manage_division_staffs_join"."division_id"
WHERE "staffs"."role" = 1
AND "staffs_manage_division_staffs_join"."user_id" = ? [["user_id", 3]]
I know what I am doing wrong but I don't know the way to correct it. Any ideas??

rails complex many-to-many query

I've got 3 models: User, Team, and Membership -
class Team < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships, :source => :user
end
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :teams, :through => :memberships
def team_mates
teams = Team.where(:members => id)
team_mates = teams.members
end
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :team
validates :user_id, :presence => true
validates :team_id, :presence => true
end
And, I can't quite figure out how to write the team_mates method in the User model. It should return an array of the other users that are in a team with the current_user. My thought is that I should be useing a scope to limit Team to only include teams where the current user is a member but I can't quite figure out the syntax. Any help on this would be greatly appreciated.
Thanks!
Use the membership table to find users who share any team with the user you are calling the method on. Then to filter out duplicate users who share more than 1 team with current user, use distinct.
I haven't tested the below code, but hopefully it will get you on the right path:
def team_mates
m = Membership.scoped.table
Users.join(m).where(m[:team_id].in(team_ids)).project('distinct users.*')
end
UPDATE
Looks like some of the Arel methods in that answer don't have an easy mapping back to ActiveRecord land. (If someone knows how, I'd love to know!) And also it returns the 'current user'. Try this instead:
def team_mates
User.joins(:memberships).where('memberships.team_id' => team_ids).where(['users.id != ?', self.id]).select('distinct users.*')
end
How about?
User.joins(:memberships).joins(:teams).where("teams.id" => id).uniq
Maybe something like this
scope team_mates, lambda {|user_id, teams| joins(:memberships).where(:team_id => teams, :user_id => user_id)}
def team_mates
User.team_mates(self.id, self.teams.collect {|t| t.id})
end
Here is more info:
http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html

Mixing has_one and has_and_belongs_to_many associations

I'm trying to build a database of urls(links). I have a Category model that has and belongs to many Links.
Here's the migration I ran:
class CreateLinksCategories < ActiveRecord::Migration
def self.up
create_table :links_categories, :id => false do |t|
t.references :link
t.references :category
end
end
def self.down
drop_table :links_categories
end
end
Here's the Link model:
class Link < ActiveRecord::Base
validates :path, :presence => true, :format => { :with => /^(#{URI::regexp(%w(http
https))})$|^$/ }
validates :name, :presence => true
has_one :category
end
Here's the category model:
class Category < ActiveRecord::Base
has_and_belongs_to_many :links
end
And here's the error the console kicked back when I tried to associate the first link with the first category:
>>link = Link.first
=> #<Link id: 1, path: "http://www.yahoo.com", created_at: "2011-01-10...
>>category = Category.first
=> #<category id : 1, name: "News Site", created_at: "2011-01-11...
>>link.category << category
=> ActiveRecord::StatementInvalid: SQLite3::Exception: no such column :
categories.link_id:
Are my associations wrong or am I missing something in the database? I expected it to find the links_categories table. Any help is appreciated.
Why are you using HABTM here at all? Just put a belongs_to :category on Link, and a has_many :links on Category. Then in the db, you don't need a join table at all, just a :category_id on the links table.
But, if you do want a HABTM here, from a quick glance, the first thing I noticed is that your join table is named incorrectly -- it should be alphabetical, categories_links.
The second thing is that you can't mix has_one and has_and_belongs_to_many. HABTM means just that -- A has many of B and A belongs to many of B; this relationship implies that the opposite must also be true -- B has many of A and B belongs to many of A. If links HABTM cateogries, then categories must HABTM links.
See http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many