Many-to-many relationship only working one way in Rails - ruby-on-rails

I don't know if anyone can help me as this is a little odd.
I have a moderately complicated set of relations in a database, which roughly has a structure something like this:
Delivery Director has Account Directors has Pods has Account Managers has Companies.
Therefore, Delivery Directors should have Companies.
This whole structure is working, all the way down to Companies, and then suddenly stops. The Delivery Director returns [] on companies.
class DeliveryDirector < User
has_many :account_directors
has_many :pods, through: :account_directors
has_many :account_managers, through: :pods
has_many :companies, through: :account_managers
end
And the company class looks like:
class Company < ApplicationRecord
belongs_to :account_manager
has_one :pod, through: :account_manager
has_one :account_director, through: :pod
has_one :delivery_director, through: :account_manager
end
Like I say, everything is working. The Company even has a Delivery Director! It's just the DeliveryDirector.all.first.companies returns [].
If anyone could even just point me in the right direction, that would be great. There is no error message, and nothing seems to be going wrong at all.
Oh, in case it helps, here is the SQL generated by the query:
Company Load (0.7ms) SELECT "companies".* FROM "companies" INNER JOIN "users" ON "companies"."account_manager_id" = "users"."id" INNER JOIN "pods" ON "users"."pod_id" = "pods"."id" INNER JOIN "users" "account_directors_companies" ON "pods"."account_director_id" = "account_directors_companies"."id" WHERE "users"."type" IN ('AccountDirector') AND "account_directors_companies"."delivery_director_id" = $1 [["delivery_director_id", 2]]
Thanks!
Edit: Request for other models, schema
Pod:
class Pod < ApplicationRecord
belongs_to :account_director
has_many :account_managers
has_many :companies, through: :account_managers
end
Account Manager:
class AccountManager < User
belongs_to :pod
has_one :account_director, through: :pod
has_one :delivery_director, through: :account_director
has_many :companies
end
Schema:
ActiveRecord::Schema.define(version: 2018_10_19_141416) do
enable_extension "plpgsql"
create_table "companies", force: :cascade do |t|
t.string "name"
t.string "officelocation"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "campaign_link"
t.string "company_logo"
t.string "website"
t.integer "account_manager_id"
end
create_table "images", force: :cascade do |t|
t.string "name"
t.string "location"
t.bigint "company_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["company_id"], name: "index_images_on_company_id"
end
create_table "jwt_blacklist", id: :serial, force: :cascade do |t|
t.string "jti", null: false
t.index ["jti"], name: "index_jwt_blacklist_on_jti"
end
create_table "markets", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "markets_users", id: false, force: :cascade do |t|
t.bigint "market_id", null: false
t.bigint "talent_manager_id", null: false
end
create_table "pods", force: :cascade do |t|
t.string "name"
t.integer "account_director_id"
t.integer "delivery_director_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "table_campaigns", force: :cascade do |t|
t.bigint "user_id"
t.bigint "company_id"
t.string "name"
t.integer "iterations"
t.integer "interviews"
t.index ["company_id"], name:
"index_table_campaigns_on_company_id"
t.index ["user_id"], name: "index_table_campaigns_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "jobtitle"
t.string "linkedin"
t.string "office"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "type"
t.integer "team_lead_id"
t.integer "delivery_director_id"
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.bigint "pod_id"
t.string "user_photo"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["pod_id"], name: "index_users_on_pod_id"
t.index ["reset_password_token"], name:
"index_users_on_reset_password_token", unique: true
end
add_foreign_key "table_campaigns", "companies"
add_foreign_key "table_campaigns", "users"
end
And now adding Account Director:
class AccountDirector < User
belongs_to :delivery_director
has_one :pod
has_many :account_managers, through: :pod
has_many :companies, through: :account_managers
end

You use Single Table Inheritance. 3 of your models: DeliveryDirector, AccountDirector and AccountManager are descendants of User model. When doing shallow request it works fine, but when you construct requests which involve all 3 models Rails cannot build the right query. If you try to project how to find all companies of a delivery director in terms of database, you will come to the chain of tables:
companies -> users (account managers) -> pods -> users (account directors) -> users (delivery directors)
The SQL query for your request may look like:
SELECT companies.* FROM companies
INNER JOIN users AS account_managers ON companies.account_manager_id = account_managers.id
INNER JOIN pods ON account_managers.pod_id = pods.id
INNER JOIN users AS account_directors ON pods.account_director_id = account_directors.id
INNER JOIN users AS delivery_directors ON account_directors.delivery_director_id = delivery_directors.id
WHERE delivery_directors.id = 2;
but obviously, Rails does not add AS clause to the query to distinguish user roles and uses users table name instead. To filter results it uses condition "users"."type" IN ('AccountDirector') which is not enough in your case, because in your query there should be also AccountManager (as a link between pods and companies).
Another sign that Rails is confused: despite correct association in your models Rails tries to use table account_directors_companies which you obviously do not have.
I would recommend to review your database schema and extract user roles and relationship between them into separate substances.
UPDATE:
For example, user authentication/registration data can be left in users table as it is now. All info about user roles and their relations can be moved to extra tables, backed up by models:
class DeliveryDirector < ApplicationRecord
belongs_to :user
has_many :account_directors
has_many :pods, through: :account_directors
has_many :account_managers, through: :pods
has_many :companies, through: :account_managers
end
class AccountDirector < ApplicationRecord
belongs_to :user
has_one :pod
has_many :account_managers, through: :pod
has_many :companies, through: :account_managers
end
class AccountManager < ApplicationRecord
belongs_to :user
has_many :companies
end
Each of these models has their own table in the database.
Thus, to fetch companies of delivery director you could call:
DeliveryDirector.find_by(user_id: user_id).companies

Related

How to get fields from has_many :throught association

I have a many-to-many association throught RoomsUsers model and in this model i have a role field, association works well but i can't access this field.
My schema looks like:
create_table "messages", force: :cascade do |t|
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "room_id"
end
create_table "rooms", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "rooms_user_id"
end
create_table "rooms_users", force: :cascade do |t|
t.integer "user_id"
t.integer "room_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "role"
t.integer "last_checked"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "mail"
t.integer "rooms_user_id"
end
User model:
class User < ApplicationRecord
has_secure_password(validations: false)
has_many :messages
has_many :rooms_users
has_many :rooms, through: :rooms_users
accepts_nested_attributes_for :rooms_users
attr_accessor :register, :mail_confirmation, :login
end
Room model:
class Room < ApplicationRecord
has_many :rooms_users
has_many :users, through: :rooms_users
accepts_nested_attributes_for :rooms_users
has_many :message
end
RoomsUsers model:
class RoomsUsers < ApplicationRecord
belongs_to :user
belongs_to :room
end
And i am trying to get role field from first user's room.
User.first.rooms.first.role
It give's me NoMethodError (undefined method `role' for #). What's wrong?
You're trying to access role field in the rooms table, but it is in rooms_users table. Should be:
User.first.rooms_users.first.role
And remove rooms_user_id from rooms and users table, you don't need it
If you want to access "role" field through Rooms model, you will need to change the place of your "role" field from rooms_users table to rooms table. Doing it you can access "role" using User.first.rooms.first.role.
However if you want to keep "role" field in rooms_users table, so you will need to use User.first.rooms_users.first.role as Vasilisa has already mentioned.
t.integer "rooms_user_id" are not necessary in rooms and users tables. The has_many used in rooms and users are already linking rooms_users with them.

Rails - Associate three Models

Having three models: Datum, Author, and Book .
class Datum < ApplicationRecord
has_many :authors
end
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
belongs_to :datum
has_many :books, dependent: :destroy
end
For exercise purpose, I wanted to model it that Datum(more general), can have many authors, which can have books.
After creating a datum object and an associated author for it, I could call nameofdatum.authors, but if I added a book to that author, it could not be recognized through nameofdatum.authors.books. Am I having wrong expectations ? (Should this be done with 'through'(an explanation of it would be much appreciated)
(Schema here if needed)
create_table "authors", force: :cascade do |t|
t.string "name"
t.integer "age"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "book_id"
t.integer "datum_id"
t.index ["book_id"], name: "index_authors_on_book_id"
t.index ["datum_id"], name: "index_authors_on_datum_id"
end
create_table "books", force: :cascade do |t|
t.string "name"
t.string "book_type"
t.integer "pages"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "author_id"
t.index ["author_id"], name: "index_books_on_author_id"
end
create_table "data", force: :cascade do |t|
t.string "region"
t.integer "budget"
t.date "aval"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Should this be done with 'through'?
Yes, Datum has_many books through the authors assocition:
class Datum < ApplicationRecord
has_many :authors
has_many :books, through: :authors
end
And the books can be selected via:
Datum.last.books
It's actually selects books using the following query:
SELECT "books".* FROM "books" INNER JOIN "authors" ON "authors"."id" = "books"."author_id" WHERE "authors"."datum_id" = ?
If you want to add a new book through author, you have to assign an author. So you can try:
nameofdatum.author.books.build ....
your codenameofdatum.authors.books, you can't use a plural(author) to add a new book.
Hope to help you.

add a foreign key constraint on a users primary address in rails activerecord

create_table "addresses", force: :cascade do |t|
t.integer "user_id"
t.string "code_postal"
t.string "street_name"
t.string "street_number"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["code_postal"], name: "index_addresses_on_code_postal"
t.index ["user_id"], name: "index_addresses_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "name_first"
t.string "name_last"
t.date "date_birth"
t.string "address_email"
t.integer "address_primary_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
A user can have many addresses, but a user can only have one primary address.
How do I add a foreign key constraint on address_primary_id?
I'm assuming your associations look like this:
class User < ApplicationRecord
belongs_to :address_primary, class_name: Address
has_many :addresses
end
class Address < ApplicationRecord
belongs_to :user
has_one :user_as_primary, class_name: User, foreign_key: :address_primary_id
end
You can create a foreign key you want in a migration with this line:
add_foreign_key :users, :addresses, column: :address_primary_id
Here are the docs on foreign keys in migrations.

Rails 5: find all Users who belong to Companies, which belong to current_user

In my app User can have many Companies and vice versa. In Accounts table id of User and id of its Company is stored.
I want to find all Users who belong to Companies, which belong to current_user. Let's assume that the current_user is like master User (not Admin, as that would be system Admin) of those companies.
How do I do this? My guess is to do it with Arel, but then how should it look in Model, Controller, View? Many thanks for any help. I'm on Rails 5.
models/user.rb
class User < ApplicationRecord
has_many :accounts, dependent: :destroy
has_many :companies, through: :accounts
models/account.rb
class Account < ApplicationRecord
belongs_to :company
belongs_to :user
accepts_nested_attributes_for :company, :user
models/company.rb
class Company < ApplicationRecord
has_many :accounts, dependent: :destroy
has_many :users, through: :accounts
accepts_nested_attributes_for :accounts, :users
My schema.rb looks like this:
create_table "accounts", force: :cascade do |t|
t.integer "company_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["company_id"], name: "index_accounts_on_company_id"
t.index ["user_id"], name: "index_accounts_on_user_id"
end
create_table "companies", force: :cascade do |t|
t.string "name"
t.string "legal_name"
t.string "reg_number"
t.string "address"
t.string "bank_acc"
t.string "description"
t.string "website"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "role", default: 0
t.integer "currency", default: 0
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "remember_digest"
t.boolean "admin", default: false
t.index ["email"], name: "index_users_on_email", unique: true
end
You can find current user's companies, and eager load users who belong to those companies
#companies = current_user.companies.includes(:users)
To list all users(may be in a view), loop through #companies and all its users
#companies.each do |company|
company.users.each do |user|
puts user.name
end
end
Or use map/collect to assign them to a variable.
#users = #companies.map(&:users).flatten

Rails association in model error

I am using rails 4.2.0. and I am getting this error:
ActiveRecord::HasManyThroughAssociationNotFoundError:
Could not find the association :taggings in model Article
Here are my models:
class Tag < ActiveRecord::Base
has_many :taggings
has_many :articles, :through => :taggings
end
class Article < ActiveRecord::Base
has_many :comments
has_many :taggings
has_many :tags, :through => :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :article
end
Tagging is an intermediary model for the many-to-many relationship between Article and Tag.
And if it helps, my schema:
ActiveRecord::Schema.define(version: 20150224161732) do
create_table "articles", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "comments", force: :cascade do |t|
t.string "author_name"
t.text "body"
t.integer "article_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "comments", ["article_id"], name: "index_comments_on_article_id"
create_table "taggings", force: :cascade do |t|
t.integer "tag_id"
t.integer "article_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "taggings", ["article_id"], name: "index_taggings_on_article_id"
add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id"
create_table "tags", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
I run this code in rails console to test my associations:
a = Article.first
a.tags.create name: "cool"
And I get the above error.
I have seen similar questions where the response "if you have through: :x, you have to have has_many :x first," but I don't think that is my issue.
This might be a silly question, but have you tried creating a Tagging independent of the Article model? If you haven't, than it could be something messed up with the database not having the Tagging model. Otherwise, associations look fine and should work. The only other thing I can think of is incorrect file names for your models folder

Resources