Multiple associations on same schemas - schema

Using Ecto v2.2.6, phoenix 1.3
I have a scenario in which a user can create posts, and then other users can like posts. Users have a one-to-many relationship with posts via creation, and a many-to-many relationship with likes, via a linking table.
Setup:
mix phx.gen.json Account User users name:string
mix phx.gen.json Content Post posts title:string content:text user_id:references:users
mix phx.gen.json Content Like likes user_id:references:users post_id:references:posts
Schemas:
schema "users" do
field :name, :string
has_many :posts, SocialNetwork.Content.Post, foreign_key: :users_id
many_to_many :posts, SocialNetwork.Content.Post, join_through: "likes"
timestamps()
end
schema "posts" do
field :content, :string
field :title, :string
belongs_to :user, SocialNetwork.Accounts.User
many_to_many :users, SocialNetwork.Accounts.User, join_through: "likes"
timestamps()
end
schema "likes" do
belongs_to :user, SocialNetwork.Accounts.User
belongs_to :post, SocialNetwork.Content.Post
timestamps()
end
When I run mix phx.server, I get this error:
== Compilation error in file lib/social_network/account/user.ex ==
** (ArgumentError) field/association :posts is already set on schema
Is there a way that I can set up more than one association to the same schema, but through a different context?

Is there a way that I can set up more than one association to the same schema, but through a different context?
Yes, but you'll have to choose a different name for the two associations, like this:
has_many :posts, SocialNetwork.Content.Post, foreign_key: :users_id
many_to_many :liked_posts, SocialNetwork.Content.Post, join_through: "likes"
You shouldn't need to modify anything in Post since Post already uses different names for the two associations.

Related

How can I introduce a has_one_belongs_to_one association in Rails model?

My Rails application has users and tasks created by users. Users can also create a task and assign another user to it. I am not quite sure how to build associations here.
I know since a task is created by a user, I can have an association as below,
class User
has_many :tasks, dependent: :destroy, foreign_key: :user_id
end
class Task
belongs_to :user
end
I also want to add an association for creator in the Task model but I am not sure how to do it since a creator will also be an instance of the User class and I already have a belongs_to association with User
I tried to add an association for creator in Task model in the following way but it didn't work.
has_one :user, foreign_key: :creator_id, class_name: "User"
Since you've already defined the belongs_to :user method, the method #task.user is already taken by that association. You can't use the same name for a different method, so you'll have to use a different name for that association.
The association name doesn't have to be the same as the model. You can name the creator association something else, like "creator":
has_one :creator, foreign_key: 'creator_id', class_name: "User"
Since the task has a foreign key for the creator, you should be able to use belongs_to for both associations:
class Task
belongs_to :user
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
end
Here's a discussion about the difference between has_one and belongs_to: What's the difference between belongs_to and has_one?
Either way way you can do:
#task.user # the User who is assigned the task
#task.creator # the User who created the task

Finding all users with specific attribute in a through relation

See the details below to make sense.
I am trying to write a line of code that will be searching the database and show the users that have a specific skill.
My clue is that I should be using something like #user.where('skill = ?', skillvariable) or a specific query. Any point/guidance to the right direction is greatly appreciated.
Details:
I have a users model, a skills model and a user_skills model.
In user.rb I have the relation has_many :skills, through: :user_skills.
In the skill.rb I have the relations has_many :user_skills and has_many :users, through: :user_skills,
and then in user_skills.rb I have the belongs_to :user and belongs_to :skill.
The user_skills schema is:
t.integer "user_id"
t.integer "skill_id"
And the skills schema is:
t.string "name"
t.string "slug"
You can simply do:
User.includes(:skills).where(skills: { name: 'Archery' })
This will letterally:
Retrieve all users having at least one skill named 'archery'.
Similar questions:
association named not found perhaps misspelled issue in rails association
Rails active record querying association with 'exists'
Rails 3, has_one / has_many with lambda condition
Rails 4 scope to find parents with no children
Join multiple tables with active records
Rails: Finding all Users whose relationship has a specified attribute

Ruby data modellng has_many relationship two way

I've got a question about a certain data model in Ruby. I've got a set of business requirements:
User can create many Workshops
User can attend many workshops
Workshop has one owner (user)
Workshop has many attendees (users)
The first part of this relationship is easy to setup:
#user.rb
class User < ActiveRecord::Base
has_many :workshops
end
#workshop.rb
class Workshop < ActiveRecord::Base
belongs_to :user
end
But how will i make the "other has_many" relationship from workshops to Users. Can i do something like a workshop belongs_to :user, :as :owner. and a workshop has_many :users, :as :attendees?
What are your thoughts about this? To make it worse a Workshop has a attendee limit so i need validations...
Thanks,
Daniel
You have a has_many to has_many relationship, so you'll need to create a new associative table to associate the relationships (lets call it attendances):
create a db migration:
rails g model attendance
Then in your migration do something like:
create_table :attendances do |t|
t.integer :attendee_id
t.integer :workshop_id
end
add_index :attendances, :attendee_id
add_index :attendances, :workshop_id
add_index :attendances, [:workshop_id, :attendee_id]
So now you have a table you where you can associate many attendees to many workshops.
Now in your user model:
has_many :attending, through: :attendances, foreign_key: 'attendee_id', class_name: 'Workshop', source: :workshop
In your workshop model:
has_many :attendees, through: :attendances, class_name: 'User', source: :attendee
So now 'some_user.attending' will return the ActiveRecord relation of all the workshops some_user is attending, and 'some_workshop.attendees' will give you all the users attending some_workshop.

Rails model naming convention

I have an application which has projects and users. I don't care about tracking the many-to-many relationships between users and projects but I am interested in tracking the many-to-one relationship between projects and a specific user, which is the project manager. Ideally, I would like to create a column in the projects table called projectmanager_id and link it to a user. However, rails convention dictates that I use user_id instead. While this works, I feel the semantics aren't quite right. Is there a way to have both?
One way I thought of is to create a projectmanagers table with user_id and project_id and have a has_many :projects, :through=> :projectmanagers in the user model, and has_one :user, :through => :projectmanagers in the project model. Is there a better way?
You could try this if you keep the db column as user_id:
In your project.rb file:
belongs_to :projectmanager, :foreign_key => "user_id", :class_name => "User"
And in your user.rb file still have:
has_many :projects
Or if it's that you want your db column to be projectmanager_id
In your project.rb file:
belongs_to :projectmanager, :foreign_key => "projectmanager_id", :class_name => "User"
In your user.rb file:
has_many :projects, :foreign_key => "projectmanager_id", :class => "Project"

Table and Ruby ActiveRecord Class design for (sub)categories

I guess i have a rather simple question since I'm new to Ruby and even newer to ActiveRecords.
What I want to achieve is a class representation with ActiveRecords (and the corresponding SQL schema) that models the following problem:
There exist categories and subcategories (modeled by parent_id)
Products belong to only one category
Each product can have 0..inf features
Features simply have some data fields and are only referenced by the products
My current schema is shown below in the picture:
Is this schema suitable for ActiveRecords? How would the classes look like? I simply cant figure out how the JoinTable fits into the ActiveRecord structure.
Further, how can i model the link from parent_id->categories.id?
Any help appreciated!
cheers
To model the relationships you described you would do:
models/category.rb
class Category < ActiveRecord::Base
has_many :products
has_many :subcategories, :class_name => "Category", :foreign_key => :parent_id
end
models/product.rb
class Product < ActiveRecord::Base
belongs_to :product
has_many :features, :through => :product_features
has_many :product_features
end
models/feature.rb
class Feature < ActiveRecord::Base
has_many :product_features
has_many :products, :through => :product_features
end
models/productfeature.rb
class ProductFeature < ActiveRecord::Base
belongs_to :product
belongs_to :feature
end
Given this structure then you have the join modelled as a Many-to-Many relation. This is useful since the HABTM style of join is going away in Rails 3.1
To get the information, I often use the console rails console for testing and this would allow you do do
#category = Category.first #get the first category
#category.subcategories #returns an array of categories
The traversal of the links is via the relations that you setup in the models, with the intention that its readable, in the context of using sensible names. The self-joins, as per your question, is also covered in Rails Guides: Associations with a good example. The rest of this guide also details the other relationships.
One other thing to remember is to create your migrations so that the join table is created with the id's which are the foreign keys.
My models would look like this:
class Category < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :category
has_many :product_features
has_many :features, :through => :product_features
end
class ProductFeature < ActiveRecord::Base
belongs_to :product
belongs_to :feature
end
class Feature < ActiveRecord::Base
has_many :product_features
has_many :products, :through => :product_features
end
Rails has an association called has_and_belongs_to_many. Rails expects a table with two columns to store the join data. I usually use dual has_many to achieve the same results as it gives you flexibility to add additional information in the join table.
Sample code
product.category
product.category = category1
category.products
category.products << product1
product.features
product.features << feature1
feature.products
feature.products << product1
Here is the API for ActiveRecord::Associations::ClassMethods
There are a lot of examples in there of different relationships and how to construct them. It's worth taking the time to understand how/why you construct these associations.
For the Many-to-many join you will want to look at
has_many ..., :through => ...
has_and_belongs_to_many ...
The docs explain when and why to use each.