Rating with Rails (many_to_many) - ruby-on-rails

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

What kind of relationship should be used for tables Transaction and User - Ruby on Rails?

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

has_one and belong_to associations with the same table

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 to set up multiple belongs_to and has_many relations in Rails::ActiveRecord?

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

Rails nested attributes: validates_uniqueness_of doesn't work

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

Rails 4 HABTM has_many :through

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.

Resources