Circular model dependencies with ruby on rails - mysql

I'm trying to design the database model hierarchy and the migrations for my database for a group chat app and I'm new to rails so I'm quite confused about how I should build this... Any guidance would be greatly appreciated!
It breaks down as follows:
Groups <==> Users <==> Posts
Groups ==> Posts
So the groups have a bunch of users and users have a bunch of groups, but also the groups have the posts that the users make. Each post belongs to one group and one user. The post must also maintain a reference to the user that posted it.
Should I just make a joined table of all 3? Is that practical/efficient?
I'm just confused where I should use belongs_to, has_one, has_many, has_and_belongs_to_many and how I should write the migration files...
thanks for any help ahead of time

you can do it in next way
Groups
has_and_belongs_to_many :users
has_many :posts
Users
has_and_belongs_to_many :groups
has_many :posts
Posts
belongs_to :group
belongs_to :user
posts table should have group_id and user_id columns
also create table groups_users
migration:
class CreateGroupsUsers < ActiveRecord::Migration
def change
create_table :groups_users, id: false do |t|
t.references :group, index: true
t.references :user, index: true
end
end
end
This should work!
EDIT
for adding association between User and Group:
group = Group.create(name: 'New Group')
user = User.last
user.groups << group
or
user.build_group(name: 'New Group')
user.save
or you can use nested_attributes
User.create(params[:user])
where params[:user] = {user_name: 'James Bond', group_attributes: {name: 'New Group'}}

Related

search association having class_name

i have a Book model looking like this:
class Book < ActiveRecord::Base
belongs_to :author, class_name: 'User', touch: true
belongs_to :translator, class_name: 'User'
end
How can i search for books with specific author name? i join the tables first:
Book.joins(:author).joins(:translator)
But i cant chain
.where('authors.name = "Tolkien"')
because there is no "authors" table in the database. is
.where('users.name = "Tolkien"')
a safe approach here? is there no risk concerning both translators and authors being users in fact?
(select() method is not an option, i need activerecord::relation here)
You need to pass table name in query
Book.joins(:author).where(users: {name: 'Tolkien'})
is .where('users.name = "Tolkien"') a safe approach here?
If you are concerned about two associations using same table you might need to add a field to distinguish them
For ex. a boolean field author and use condition with association which will solve the problem
class Book < ActiveRecord::Base
belongs_to :author, -> { where(author: true) }, class_name: 'User', touch: true
belongs_to :translator, -> { where(author: false) }, class_name: 'User'
end

Rails association - Customised list of items from existing list

I am developing a rails app where users can add tasks they wish to do to a customised list of their own. Each task can also belong to 0 or more categories. So far I've tried this:
user.rb
has_one :user_list
has_many :tasks, through: :user_list
user_list.rb
belongs_to :user
has_many :tasks
tasks.rb
has_and_belongs_to_many :categories
[timestamp}_migration.rb
create_table :user_lists do |t|
t.integer :user_id
t.integer :task_id
t.timestamps null: false
end
The issue I am having is in the console I try to run User.find(1).tasks it cannot find the column tasks.user_list_id when using the following query:
SELECT "tasks".* FROM "tasks" INNER JOIN "user_lists" ON "tasks"."user_list_id" = "user_lists"."id" WHERE "user_lists"."user_id" = ? [["user_id", 1]]
This query should be joining the tasks id from the tasks table with the tasks id on the user_lists table. Are the associations correct and if so what can I do to change the query?
To allow tasks to be placed on many lists you need a M2M join table which joins the user_lists and tasks table.
class User < ActiveRecord::Base
has_many :user_lists
has_many :tasks, through: :user_lists
end
class Task < ActiveRecord::Base
has_many :user_list_items
has_many :user_lists, through: :user_list_items
end
class UserListItem < ActiveRecord::Base
belongs_to :task
belongs_to :user_list
has_one :user, through: :user_list
# optional
validates_uniqueness_of :task_id, scope: :user_list_id
end
class UserList < ActiveRecord::Base
belongs_to :user
has_many :user_list_items
has_many :tasks, through: :user_list_items
end
You can create the join model and migration with:
rails g model UserListItem user_list:belongs_to task:belongs_to
You may also want to open up the migration and add a compound index:
add_index :user_list_items, [:user_list_id, :task_id], unique: true
Setting it as unique is optional - but in most cases you want join table entries to be unique for table A and B.
See:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
http://guides.rubyonrails.org/active_record_migrations.html
Your use case calls for a task to be assigned to more than one user and a user has only one task list. This sounds like a HABM association between users and tasks.
The simplest way to express that would be:
class User
has_and_belongs_to_many: :tasks
...
end
class Task
has_and_belongs_to_many: :users
...
end
and a migration to create the join table:
create_join_table :users, :tasks, do |t|
t.index :user_id
t.index.task_id
end
You don't need to create a TaskUser model to match the join table until you need to track additional attributes. Rails will take care of that automatically.
If a user needs more than one task list, you'll need that TaskList model. Let me know and I'll update my answer.
Here's the documentation on HABM and join migration

Trouble locating and displaying list of records from a relationship model in Rails app

I have a relationship model that tracks customers (users) of shops in a has_many :through association. I'm having trouble with my ShopsController and finding the list of relationships for a given shop, then displaying them.
My error is a RecordNotFound, Couldn't find Relationship with 'id'= so I'm having issues with how relationships are created and identified. They need to be found based on a user_id when a shop is logged in (current_shop_id is then given) How can I rework this to make relationships/show work?
In the current version a shop has_many users through relationships:
class Shop < ActiveRecord::Base
has_many :relationships
has_many :users, through: :relationships
Relationships takes the user's id and shop's id and creates a relationship id:
schema.rb
create_table "relationships", force: :cascade do |t|
t.integer "user_id"
t.integer "shop_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "relationships", ["shop_id"], name: "index_relationships_on_shop_id"
add_index "relationships", ["user_id", "shop_id"], name: "index_relationships_on_user_id_and_shop_id", unique: true
add_index "relationships", ["user_id"], name: "index_relationships_on_user_id"
A user is added in the shop model:
# New relationship
def add(user)
relationships.create(user.id)
end
# Unfollows a user.
def remove(user)
relationships.find_by(user.id).destroy
end
#relationships is defined in the shop controller:
def relationships
#title = "Following"
#relationships = Relationship.find(params[:relationship_id])
#users = #shop.relationships.paginate(page: params[:page])
render 'show_relationships'
end
and the relationship is created in the relationship controller
def create
user = User.find(params[user_id: user.id])
current_shop.add(user)
redirect_to user
end
Then these relationships should be shown in relationships/show
<div class="col-md-8">
<h3><%= #title %></h3>
<% if #relationships.any? %>
<ul class="users follow">
<%= render #relationships %>
</ul>
<% end %>
</div>
Your relationships method in your controller is all over the place.
You're trying to find relationshipS using a find by id with a non-existent params[:relationship_id] which is causing the error you're seeing.
You're then setting #users to be all the relationships for the #shop.
Then you're rendering a template show_relationships but you refer later to a relationships/show template.
Additionally in the shop model you're calling create on relationships with just a user id whereas you'd expect to be passing in some attributes for the relationship.
It looks like this code has got messier and messier as you've tried to solve the problem. To be perfectly honest I'd go back to the beginning and start again.
If I understand your use case, a shop has many customers and a customer frequents many shops.
The result of doing this the Rails way is you can retrieve customers belonging to a specific shop just by using an instance of that shop and calling shop.customers You can find a specific customer for that shop, by calling shop.customers.find(:id) If you try calling for another shops customer, you get a ActiveRecord::RecordNotFoundError
Ditto the other way round. To retrieve the shops a customer frequents, ideally you want customer.shops etc as above. To add a new shop to a customer, use customer.shops.create("Arkwrights Dairy")
As Shadwell stated your current efforts to create the structure are far more complicated than you need. And you do not set this up through the controllers, only through the models.
Models, 2 only
class Shop < ActiveRecord::Base
has_and_belongs_to_many :customers
class Customer < ActiveRecord::Base
has_and_belongs_to_many :shops
Migrations, 3
class SampleMigration < ActiveRecord::Migration
def change
create_table :shops do |t|
t.string :name
end
create_table :customers do |t|
t.string :name
end
create_table :customers_shops, id: false do |t|
t.belongs_to :customer
t.belongs_to :shop
end
end
end
Add more detail to the models as you see fit, but this should be enough to establish the relationships. Things to watch are the plurals, shop verse shops is important. Shop the class and belongs_to :shop are both singular. References to many shops is plural and tables hold many shops so are also plural. And the join table has to be in alphabetical order, customers_shops not shops_customers.
Once set up, run your migrations then try.
shop = Shop.create(name: "Dairy")
=> #<Shop id:1, name: "Dairy">
shop.customers.create(name: "Bill")
=> #<Customer id:1, name: "Bill">
customer = Customer.find(1)
=> #<Customer id:1, name: "Bill">
customer.shops
=> [#<Shop id:1, name: "Dairy">]
Note that shops returns an array. Access the first element using customer.shops[0] or alternatively customer.shops.find(1) to retrieve the shop using its :id
Hopefully that will give you the foundation you need :-)
... for a given shop ...
This suggests that you should be finding an instance of Shop, and from it using associations to retrieve the appropriate Users. I'd expect to see this in a shop-oriented view, not a relationship view, and for there to be an association on Shop of:
has_many :users, through => :relationships

Validate uniqueness of both ids in a join table

I have a list model which has_and_belongs_to_many postings and vice versa.
I have a join table:
create_table :postings_lists do |t|
t.integer :posting_id
t.integer :list_id
end
Users have many lists.
I already validate the list uniqueness for the user with:
validates :name, presence: true, uniqueness: {scope: :user_id}
In the join table, how can I validate the uniqueness of both the :posting_id and the :list_id so that a posting can't belong to a list more than once?
I have tried adding, uniq: true to both has_and_belongs_to_manys in the models but it messes things up and I have tried added custom validations in the list model but it wasn't working.
I think the simplest thing would be just to validate both ids in the join table but I don't know if I can do that without creating a model?
I would use a has_many :through instead of HABTM.
class List < ActiveRecord::Base
has_many :postings
has_many :posts, through: :postings
end
class Posting < ActiveRecord::Base
belongs_to :list
belongs_to :post
validate :post_id, uniqueness: {scope: :list_id}
end
class Post < ActiveRecord::Base
has_many :postings
has_many :lists, through: postings
end
I ended up adding uniq: true
Lists
has_and_belongs_to_many :postings, uniq: true
Postings
has_and_belongs_to_many :lists, uniq: true
This causes some issues if you use things like pluck on postings because things have to be "distinct" before doing other things like "order_by" to them.
But you can work around this issue by using different queries like 'map' instead.

How to add a new attribute to Associate Table column in rails

In my rails application, I had to add a new column named is_leader for an association table.
The relationship for the association table is as follows:
has_and_belongs_to_many :analysis_responses, :join_table => "analysis_responses_participants"
Following is the code where the participant details have been saved to the db:
organization.people.create(participant)
the participant has got the following values
name: Test User
position:
birthdate:
id:
lead: "1"
If the lead value is 1, the is_leader column value should be 1 for the particular record.
I wanted to know how can I save the is_leader value in that association table in rails
Thanks
If you need to save attributes on the join table, then you will have to use a join model instead of a HABTM.
class Organization
has_many :analysis_responses
has_many :people, through: :analysis_responses
end
class AnalysisResponse
belongs_to :organization
belongs_to :person
end
class Person
has_many :analysis_responses
has_many :organizations, through: :analysis_reponses
end