Active Record Associations to prevent duplicate database entries - ruby-on-rails

I am setting up a Ruby on Rails application where a User will have an Album collection. My initial thinking was to set it up with a simple User has_many Albums and Albums belongs to Users. The problem that arises here is that the Album table will have duplicates entries, only distinguished by user_id.
id | album_name | artist_id | created_at | updated_at | user_id
-----+-------------+-----------+----------------------------+----------------------------+---------
2 | SuperRando | 3 | 2015-11-13 00:03:51.790759 | 2015-11-13 00:03:51.790759 | 1
3 | SuperRando | 3 | 2015-11-13 00:19:08.438907 | 2015-11-13 00:19:08.438907 | 2
So what would be the best course of action so I could have an Album table with all unique albums?

You could model it using a join table:
class Album < ActiveRecord::Base
has_many :user_albums
has_many :users, through: :user_albums
end
class User < ActiveRecord::Base
has_many :user_albums
has_many :albums, through: :user_albums
end
class UserAlbum < ActiveRecord::Base
belongs_to :user
belongs_to :album
end
So, your schema would like somewhat like this:
create_table "albums", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "user_albums", force: :cascade do |t|
t.integer "user_id"
t.integer "album_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "email"
t.string "first_name"
t.string "last_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Now, the way you would call this code in your controller or console. you can do this:
Create User: user = User.create(params)
Find or Create an album: album = Album.find_or_create_by(params)
Associate user with that album user.albums << album & then save it by user.save
Now to see user's album. you can do:
User.take.albums
to see users of a particular album, you can do
Album.take.users
Hope this answers your question.
For more information have a look at the rails guides:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

Related

How to query an object from one model, starting from another model? Ruby

I am trying to make an association that is not working.
I have the following scope:
ActiveRecord::Schema.define(version: 2020_04_05_125812) do
create_table "accounts", force: :cascade do |t|
t.string "social_network"
t.string "name_account"
t.integer "person_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["person_id"], name: "index_accounts_on_person_id"
end
create_table "lists", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "lists_people", id: false, force: :cascade do |t|
t.integer "list_id", null: false
t.integer "person_id", null: false
end
create_table "people", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "posts", force: :cascade do |t|
t.string "post_text"
t.date "date"
t.string "link"
t.integer "account_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_posts_on_account_id"
end
add_foreign_key "accounts", "people"
add_foreign_key "posts", "accounts"
end
I wish I could consult, for example:
I want to consult the person named "Test_name"
That person belongs to lists, which is a collection of people. In addition, that person has accounts and those accounts have Posts.
class List < ApplicationRecord
has_and_belongs_to_many :people
end
class Person < ApplicationRecord
has_and_belongs_to_many :lists
end
class Account < ApplicationRecord
has_many :posts
belongs_to :person
end
class Post < ApplicationRecord
belongs_to :account
end
How could I have a return like the one below:
List | Name | social_network
1 | Test_name | facebook
2 | Test_name | twitter
All the queries I make, either return the wrong type, or return only the list.
Problems such as:
List | Name | social_network
1 | Test_name | facebook
2 | Test_name | twitter
1 | Second_name | twitter
And I don't want to see the data "second_name"
I try this:
#lists = List.from(
Person.left_outer_joins(:list).where('people.name LIKE ?', "Renata Rempel"),
:list
)
but, doesn't works =/
To start off with you want to setup a many to many association between Person and List. This can be done with has_and_belongs_to_many but there are many reasons why has_many through: may be a better choice. The primary one is that it will let you add features like keeping track of banned users or when a user joined a list.
# rails g model list_membership member:belongs_to user:belongs_to
class ListMembership < ApplicationRecord
belongs_to :member, class_name: 'Person'
belongs_to :list
end
We then have to fix the foreign key in the association:
class CreateListMemberships < ActiveRecord::Migration[6.0]
def change
create_table :list_memberships do |t|
t.belongs_to :list, null: false, foreign_key: true
t.belongs_to :member, null: false, foreign_key: { to_table: :people }
t.timestamps
end
# can be a good idea to add a compound index
# add_index [:list_id, :member_id], unique: true
end
end
class Person < ApplicationRecord
has_many :list_memberships, foreign_key: :member_id
has_many :lists, through: :list_memberships
has_many :accounts
has_many :posts, through: :accounts
end
class List
has_many :list_memberships
has_many :members,
through: :list_memberships
end
Your from query will not work as your actually selecting rows from people but you just alias the table lists. That won't magically select the right data! If you really wanted to use from you would do:
List.from(
List.joins(:members).where("people.name LIKE ?", "Renata Rempel"),
:lists
).eager_load(members: :posts)
If you want to create a bunch of lists with a random number of members in your seed file you can just do:
ids = 10.times.map do
Person.create!(name: Faker::Name.name).id
end
lists = 10.times.do
List.create!(member_ids: ids.sample(2))
end

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 4 active record query

I have 2 models, Project and TodoItem. Please see below table structures and Models below.
What I am trying to do query all todo_items where assigned = current_user.fullname and completed_at is nil grouped_by project name and Ordered_by the project name.
Then I want to loop out something similar to this:
PROJECT 1 Name
ToDo_Item 1 content
ToDo_Item 2 content
ToDo_Item 3 content
PROJECT 2 Name
ToDo_Item 1 content
ToDo_Item 2 content
ToDo_Item 3 content
Table Structure:
create_table "projects", force: :cascade do |t|
t.string "name"
t.text "details"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "todo_items", force: :cascade do |t|
t.string "content"
t.string "assigned"
t.integer "project_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "completed_at"
end
Models:
class Project < ActiveRecord::Base
has_many :todo_items, :dependent => :destroy
validates_presence_of :name
end
class TodoItem < ActiveRecord::Base
belongs_to :project
end
Thank you in advance.
For your complete solution
you can try this
#projects = Project.includes(:todo_items).where(todo_items: {assigned: current_user.fullname, completed_at: nil }).group("projects.name").order("projects.name")
You're almost there.
You just need to add .where:
#projects = Project.includes(:todo_items).where(completed_at: nil)

has_many belongs_to assocation issue

I have two models: University and Market
A University belongs to a single Market, and a Market can have many universities. For example, An instance of Market like Boston might have University instances like MIT, Harvard, Boston University, etc.
I want to be able to do something like this in console:
University.first.market.name
But I get the following error:
NoMethodError: undefined method `market' for #
I can get the market id from University.first.market_id, but I can't get the name from market.name.
Here is how I have my models set up:
class University < ApplicationRecord
belongs_to :markets
class Market < ApplicationRecord
has_many :universities
end
And here is my schema - I think with the market_id integer column and index correctly implemented (?)
create_table "universities", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "full_name"
t.integer "market_id"
t.index ["market_id"], name: "index_universities_on_market_id"
end
Here's my markets schema:
create_table "markets", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
end
What am I doing incorrectly?
In a one-to-many relationship, the "belongs_to" end must be singular, as below:
class University < ApplicationRecord
belongs_to :market
end
class Market < ApplicationRecord
has_many :universities
end
Let me know if that helps

Rails InvalidForeignKey

I am developing a portfolio for my website, I decided to add skills to each portfolio item.
class PortfolioSkill < ApplicationRecord
belongs_to :portfolio
belongs_to :skill
end
class Portfolio < ApplicationRecord
has_many :portfolio_skills
has_many :skills, through: :portfolio_skills
def all_tags=(names)
self.skills = names.split(",").map do |name|
Skill.where(name: name.strip).first_or_create!
end
end
def all_tags
self.skills.map(&:name).join(", ")
end
def remove_skill_tags
PortfolioSkill.where(portfolio_id: id).destroy_all
end
end
create_table "portfolio_skills", force: :cascade do |t|
t.integer "portfolio_id"
t.integer "skill_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["portfolio_id"], name: "index_portfolio_skills_on_portfolio_id"
t.index ["skill_id"], name: "index_portfolio_skills_on_skill_id"
end
create_table "portfolios", force: :cascade do |t|
t.string "name"
t.string "client"
t.date "completed"
t.text "about"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "long_landscape"
t.string "cover"
t.integer "category_id"
t.index ["category_id"], name: "index_portfolios_on_category_id"
end
When I click destroy on the index page I get the
SQLite3::ConstraintException: FOREIGN KEY constraint failed: DELETE FROM "portfolios" WHERE "portfolios"."id" = ?
error. All the associations look right. I used this same pattern for my tags on other models and it worked with no issues. Any help would be great.
You are deleting from portfolios table, but table portfolio_skills has a column referencing it as foreign key. Hence the error.
Trying to delete a parent without checking and deleting its associated children can lead to data inconsistency. This exception is in place to prevent that.
Rails dependent destroy will take care of removing associated children rows while removing a parent.
Try using a dependent destroy:-
class Portfolio < ApplicationRecord
has_many :portfolio_skills, :dependent => :destroy
...
end

Resources