Team, looking for some help for a very specific (newbie) situation on a Rails 4 association.
We have 3 models:
class Brand < ActiveRecord::Base
has_many :lines, dependent: :destroy
has_many :products, through: :lines, dependent: :destroy
end
class Line < ActiveRecord::Base
belongs_to :brand
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :lines
has_many :brands, through: :lines
end
This configuration works well when trying to check for Products under specific Brand (or Line) and viceversa: different Brands (or Lines) available for a specific Product. However, when it comes to delete/destroy there is an issue. We are getting this Rspec error:
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection:
Cannot modify association 'Brand#products' because the source reflection
class 'Product' is associated to 'Line' via :has_and_belongs_to_many.
We have made research on this exception, checked for Rails API, with no luck, examples found are showing a different model configuration. What's missing on this approach?
Appreciate your help guys!
In my opinion, it should be something like this:
class Brand < ActiveRecord::Base
has_many :lines, dependent: :destroy
has_many :products, through: :lines, dependent: :destroy
end
class Line < ActiveRecord::Base
belongs_to :brand
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
belongs_to :brand, through: :line
has_and_belongs_to_many :lines
end
And in migrations:
create_table :brands , force: true do |t|
t.string :name
...
t.timestamps null: false
end
create_table :lines , force: true do |t|
t.string :name
t.belongs_to :brand
...
t.timestamps null: false
end
create_table :products , force: true do |t|
t.string :name
...
t.timestamps null: false
end
create_table :line_products, force: true, id: false do |t|
t.belongs_to :line, index: true
t.belongs_to :product, index: true
end
I hope it will help.
Related
I am new learner. I just started learning more about Backend with Ruby on Rails.
I have the following tables - User and User_Transaction.
So basically I want to have a transaction which holds information about the sender and the receiver. This personally sounds to me more like a has_and_belongs_to_many relation. However, I am really confused in how to approach this and how should I include the 2 foreign keys.
I am curious to learn more about this and I will be really happy if someone helps me :).
Migrations
User
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.integer :username
t.integer :password
t.timestamps
end
end
end
Transaction
class CreateTransactions < ActiveRecord::Migration[6.0]
def change
create_table :transactions do |t|
t.string :sender
t.string:receiver
t.decimal :amount
t.timestamps
end
end
end
Models
Transaction
class ::Transaction < ApplicationRecord
#We have two users per transaction 'Sender' and 'Receiver'
has_and_belongs_to_many :users
# belongs_to :sender, :class_name => 'User'
# belongs_to :receiver, :class_name => 'User'
end
User
class User < ApplicationRecord
# has_many :accounts
# has_many :transactions
has_and_belongs_to_many :transactions
end
how about this:
migrations
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :username
t.string :password
t.timestamps
end
end
end
class CreateTransactions < ActiveRecord::Migration[6.0]
def change
create_table :transactions do |t|
t.references :sender, index: true, null: false, foreign_key: {to_table: :users}
t.references :receiver, index: true, null: false, foreign_key: {to_table: :users}
t.decimal :amount
t.timestamps
end
end
end
models
class User < ApplicationRecord
has_many :send_transactions, class_name: "Transaction", foreign_key: :sender, inverse_of: :sender
has_many :receive_transactions, class_name: "Transaction", foreign_key: :receiver, inverse_of: :receiver
end
class Transaction < ApplicationRecord
belongs_to :sender, class_name: "User", inverse_of: :send_transactions
belongs_to :receiver, class_name: "User", inverse_of: :receive_transactions
end
I wonder is it ok to have like 2 associations with the same table. For example:
class CreateTeams < ActiveRecord::Migration[6.0]
def change
create_table :teams do |t|
t.string :name, null: false
t.references :manager, foreign_key: { to_table: 'users' }
end
end
end
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name, null: false
t.references :team
end
end
end
class Team < ApplicationRecord
has_many :users
belongs_to :manager, class_name: 'User', foreign_key: 'manager_id'
end
class User < ApplicationRecord
belongs_to :team
has_one :child_team, class_name: 'Team' # bad name, but just for example
end
Or it would be better to create a join table with team_id, user_id, and member_type?
Ruby/Rails versions do not matter but let's assume Ruby is 2.7.0 and Rails is 6.0.0
From a technical point of view - that's perfectly fine, but be careful with possible foreign key loop in the future.
This is more a question of architecture and your predictions of how system will evolve. A many-to-many relation with a explicit join model is more flexible. For example:
does manager always belong to the team? with a join table it's easier to fetch "all users from the team, no matter the role" or "all the teams a person has relation to, also no matter the role"
if there will be other roles or multiple people at same position - join table will also come handy
What you have seems fine.
Though a join table would be more flexible to provide more roles. This also avoids having a circular dependency in setting up teams and users.
class CreateTeams < ActiveRecord::Migration[6.0]
def change
create_table :teams do |t|
t.string :name, null: false
t.timestamps
end
end
end
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name, null: false
t.timestamps
end
end
end
class CreateTeamMembers < ActiveRecord::Migration[6.0]
def change
create_table :team_members do |t|
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :team, null: false, foreign_key: true
t.integer :role, null: false
t.timestamps
# Enforce one manager per team
t.index [:team_id],
name: :one_manager,
unique: true,
where: "role = 0"
end
end
end
class TeamMember < ApplicationRecord
enum role: { manager: 0, player: 1, fan: 2 }
belongs_to :user
belongs_to :team
end
class Team < ApplicationRecord
has_many :users, through: :team_members
has_many :team_members, dependent: :destroy
has_one :manager, -> { where(role: :manager) }, class_name: "TeamMember"
has_many :players, -> { where(role: :player) }, class_name: "TeamMember"
has_many :fans, -> { where(role: :fan) }, class_name: "TeamMember"
end
class User < ApplicationRecord
has_many :team_memberships, dependent: :destroy, class_name: "TeamMember"
has_many :teams, through: :team_memberships
end
You could even potentially take advantage of single table inheritance to differentiate your users by their role.
This is something you could migrate to later if necessary.
How do you associate a double many_to_many relationship? and also, what is it called? I know that there's no "double" many_to_many.
So have these models in rails, a User, Role, UserRole, Menu, RoleMenu.
A user can access menus depending on the roles. On console, I can do this User.first.roles.first.menus. My question is there a way to do like this User.first.menus, so it'll shorten? How do you associate User to Menu? What should I add to my models? what migration should I create?
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, through: :user_roles
end
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.timestamps null: false
end
end
end
class Role < ActiveRecord::Base
has_many :user_roles
has_many :users, through: :user_roles
has_many :role_menus
has_many :menus, through: :role_menus
end
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.string :name
t.timestamps null: false
end
end
end
class UserRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
class CreateUserRoles < ActiveRecord::Migration
def change
create_table :user_roles do |t|
t.belongs_to :user
t.belongs_to :role
t.timestamps null: false
end
end
end
class Menu < ActiveRecord::Base
has_many :role_menus
has_many :roles, through: :role_menus
end
class CreateMenus < ActiveRecord::Migration
def change
create_table :menus do |t|
t.string :name
t.timestamps null: false
end
end
end
class RoleMenu < ActiveRecord::Base
belongs_to :role
belongs_to :menu
end
class CreateRoleMenus < ActiveRecord::Migration
def change
create_table :role_menus do |t|
t.belongs_to :role
t.belongs_to :menu
t.timestamps null: false
end
end
end
Did you mean User.first.menus instead of User.menus ? Because, latter can't be achieved as you are trying to access menus through User class (which is more of a scope implementation) and not the particular user.
For the first case, as I can see that you are already aware of the has_many, through association. We will use the same to achieve that. Following should work.
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, through: :user_roles
has_many :menus, through: :roles
end
How do you associate a double many_to_many relationship? and also, what is it called? I know that there's no "double" many_to_many.
Well, yes, there's nothing called double many to many association but it is more aptly called multiple or nested many to many relation/association. And as mentioned above, it can be achieved through has_many, through
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, through: :user_roles
has_many :menus, through: :roles
end
adding another has_many.. through should work
This is my Migration code:
1.User
create_table :users do |t|
t.string :name
t.string :password
t.string :department
t.timestamps
end
2.Ploy
create_table :ploys do |t|
t.string :name
t.string :state
t.string :description
t.string :location
t.float :spend
t.references :creator, index: true, class_name: "User"
t.timestamps
end
3.participants
def change
create_table :participants do |t|
t.belongs_to :ploy
t.belongs_to :user
t.timestamps
end
This is my Model code:
class User < ActiveRecord::Base
has_one :create_ploys, class_name: "Ploy"
has_many :participants
has_many :ploys, through: :participants, source: :join_ploys
end
class Ploy < ActiveRecord::Base
belongs_to :creator, class_name: "User"
has_many :participants
has_many :users, through: :participants, source: "joiners"
end
class Participant < ActiveRecord::Base
belongs_to :user
belongs_to :ploy
end
When i call user.join_ploys, but give me a error: NoMethodError: undefined method `join_ploys'
So i think maybe something is wrong about :source, But i don't know how to do.
so where are you defining a model or association for joiners and join_ploys? You can't just create a :source without defining what it is. Check out this SO: Need help to understand :source option of has_one/has_many through of Rails
In my application, I have models for Users and Projects.
I want users to have the ability to follow many projects. So users has_many projects, and projects belongs_to users that not only created them but users that follow them too.
So I generated a migration called ProjectRelationship and tried to make it flow below, but it doesn't seem to work. Can somebody help me fix my associations?
Thanks for the help!
project_relationship.rb
class ProjectRelationship < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
project.rb
belongs_to :user
has_many :project_relationships
has_many :followers, through: :project_relationships, source: :user
user.rb
has_many :projects
has_many :project_relationships
has_many :projects_followed, through: :project_relationships, source: :project
schema.rb
create_table "project_relationships", :force => true do |t|
t.integer "follower_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "projectuser_id"
end
add_index "project_relationships", ["follower_id"], :name => "index_project_relationships_on_follower_id", :unique => true
add_index "project_relationships", ["projectuser_id"], :name => "index_project_relationships_on_projectuser_id"
projects/show.html.erb
<%= #project.followers.count %>
You need to specify the foreign keys. The ProjectRelationship model will be expecting the corresponding table to have a "user_id" and "project_id" columns. However, you used different names. So either specify the foreign keys:
class ProjectRelationship < ActiveRecord::Base
belongs_to :user, foreign_key: "follower_id"
belongs_to :project, foreign_key: "projectuser_id"
end
or change the column names in your migration:
create_table :project_relationships do |t|
t.integer :user_id
t.integer :project_id
...
end
You will also need to specify the foreign key in your other models:
class Project < ActiveRecord::Base
belongs_to :user
has_many :project_relationships, foreign_key: "projectuser_id"
has_many :followers, through: :project_relationships, source: :user
end
class User < ActiveRecord::Base
has_many :projects
has_many :project_relationships, foreign_key: "follower_id"
has_many :projects_followed, through: :project_relationships, source: :project
end
#roma149 - Thanks for your response. I updated the controllers, routes, and what you said. No errors generate, but when I click the button follow in _follow.html.erb, it does not seem to follow the project or update the count "#project.followers.count"
Moved details to here: Why doesn't my user follow/unfollow button work?