I'm trying to build a rating system into my app and currently stuck on the associations and indices. I'm quite new to this so I need all the help I can get...
User model
class User < ActiveRecord::Base
has_many :demos
has_many :ratings
has_many :reviewed, through: :ratings, source: :reviewed'Demo'
That's part of it.
Demo model
class Demo < ActiveRecord::Base
belongs_to :user
belongs_to :subject
has_many :ratings
has_many :reviewers, through: :ratings, source: :reviewer
The subject is irrelevant.
Rating model
class Rating < ActiveRecord::Base
belongs_to :reviewed
belongs_to :reviewer
validates :rating, presence: true, numericality: { :greater_than_or_equal_to => 0, :less_than_or_equal_to => 5}
end
end
And now you can tell I'm not really sure what I'm doing.
Ratings Migration Table
class CreateRatings < ActiveRecord::Migration
def change
create_table :ratings do |t|
t.float :rating, default: 2.5
t.belongs_to :reviewed, index: true
t.belongs_to :reviewer, index: true
t.timestamps null: false
end
end
end
Demos Migration Table
class CreateDemos < ActiveRecord::Migration
def change
create_table :demos do |t|
t.references :user, index: true
t.references :subject, index: true
t.string :name
t.text :content
t.string :materials
t.timestamps null: false
end
add_index :demos, [:user_id, :subject_id, :created_at]
end
end
I haven't built a controller for ratings yet since I'm just testing the relationships using the sandbox console.
I've successfully created a Rating, but #user.reviewed and #user.ratings returns a blank array in the Active Record even though #rating has all the Demo and User ids.
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.
I am trying to create a movies app where a movie can have multiple categories and a category can have multiple movies. I want to access categories of a movie like this:
aMovie.categories
I expect this query to return ActiveRecord_Associations_CollectionProxy
and the reverse also applies
aCategory.movies
Below are my models and migrations
class Movie < ApplicationRecord
validates :name, presence: true
end
class Category < ApplicationRecord
validates :name, presence: true
end
class CreateMovies < ActiveRecord::Migration[5.2]
def change
create_table :movies do |t|
t.string :name
t.text :description
t.integer :year
t.float :rating
t.timestamps
end
end
end
def change
create_table :categories do |t|
t.string :name
t.timestamps
end
end
end
How should i adjust my migrations and models?
Thanks in advance
You should create intermediate join table
Movie_categories
belongs_to :movie
belongs_to :category
Movie
has_many :movie_categories
has_many :categories, through: :movie_categories
Category
has_many :movie_categories
has_many :movies, through: :movie_categories
You can refer to has_many through relationship in https://guides.rubyonrails.org/association_basics.html
I have 3 models:
class UserLanguage < ActiveRecord::Base
belongs_to :user
belongs_to :language
end
class Language < ActiveRecord::Base
has_many :user_languages
has_many :users, :through => :user_languages
end
class User < ActiveRecord::Base
has_many :user_languages, :dependent => :destroy
has_many :languages, :through => :user_languages
accepts_nested_attributes_for :user_languages, :allow_destroy => true
end
I'm using nested_form gem to help user select which language(s) they can speak in. The CRUD for that is working fine.
But, I can't validate uniqueness of the UserLanguage. I try this 2 syntax but they didn't work for me:
validates_uniqueness_of :language_id, scope: :user_id
validates :language_id, :uniqueness => {:scope => user_id}
My schema for user_languages table:
create_table "user_languages", force: :cascade do |t|
t.integer "user_id"
t.integer "language_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "user_languages", ["language_id"], name: "index_user_languages_on_language_id", using: :btree
add_index "user_languages", ["user_id"], name: "index_user_languages_on_user_id", using: :btree
What should I do to make sure one user can choose only a language once? Say, if I select English inside the dropdown, my second English will not be saved (duplicate) and rejected.
This is how I did it finally:
class UserLanguage < ActiveRecord::Base
belongs_to :user
belongs_to :language
def self.delete_duplicated_user_languages(user_id)
user_languages_ids = UserLanguage.where(user_id: user_id).pluck(:language_id).sort
duplicate_language_ids = user_languages_ids.select {|language| user_languages_ids.count(language) > 1}
duplicate_language_ids.uniq!
keep_this_language = []
duplicate_language_ids.each do |language_id|
keep_this_language << UserLanguage.find_by(user_id: user_id, language_id: language_id).id
end
single_language = user_languages_ids.select {|language| user_languages_ids.count(language) == 1}
single_language.each do |language|
keep_this_language << UserLanguage.find_by(user_id: user_id, language_id: language).id
end
UserLanguage.where(user_id: user_id).where.not(id: keep_this_language).destroy_all
end
end
I save all UserLanguages first and delete them (duplicate ones) later.
If User has and should only have one Language, then you could change the cardinality between the models:
class Language < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
has_one :language
end
Then by definition your users will only have one language at a time and your overall model will be simpler.
You can read more about active record associations and cardinalities in this Association Basics guide
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.