Ruby on Rails has_many :through Associations. Would this work? - ruby-on-rails

I have a model Shop and a model Customer. A shop can have many customers and a Customer
can buy stuff from many shops. for this relationship I've created a join model
ShopCustomers.
create_table :shop_customers do |t|
t.integer :shop_id
t.integer :customer_id
t.timestamps
end
Models
class Shop < ActiveRecord::Base
has_many :shop_customers, :dependent => true
has_many :customers, :through => shop_customers
has_many :customers_groups
end
class Customer < ActiveRecord::Base
has_many :shop_customers, :dependent => true
has_many :shops, :through => shop_customers
belongs_to :customers_group_membership
end
class ShopCustomer < ActiveRecord::Base
belongs_to :shop
belongs_to :customer
end
Shop owners want to be able to group customers and therefore I added another
model CustomersGroups.
class CustomersGroup < ActiveRecord::Base
belongs_to :shop
end
And the Customers are added to a group through another join model.
create_table :customers_group_memberships do |t|
t.integer :customers_group_id
t.integer :customer_id
end
class CustomersGroupMembership < ActiveRecord::Base
has_many :customers
belongs_to :customers_group
end
Is this the correct way of doing such kind of a relationship or this is a recipe
for doom and am I missing something that would make this not to work.

No, this is not the usual way of doing it. Have a look at the "has and belongs to many" association (AKA: HABTM). It will create the shops_customers join table for you and maintain it without you having to do it by hand.
here's a link to the apidock on HABTM: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many

Related

how to create a record for a join table?

I have the following associations set up:
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :users_books
has_many :users, through: :user_books
end
and
class User < ActiveRecord::Base
has_many :users_books
has_many :books, through: :users_books
end
I created a join table migration as I ought to
class CreateUsersBooks < ActiveRecord::Migration[4.2]
def change
create_table :users_books do |t|
t.integer :user_id
t.integer :book_id
end
end
end
Now I need to create a method called check_out_book, that takes in a book and a due_date as arguments. When a user checks out a book, it should create a new UserBook record (or Checkout record or whatever you want to call you join table/model). That new UserBook record should have a attribute (and therefore table column) of returned? which should default to false. How would I go about creating this method and the migrations?
Your tablenames and your associations in Rails should always be singular_plural with the exception of the odd duckling "headless" join tables used by the (pretty useless) has_and_belongs_to_many association.
class CreateUserBooks < ActiveRecord::Migration[4.2]
def change
create_table :user_books do |t|
t.references :user
t.references :book
t.boolean :returned, default: false
end
end
end
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :user_books
has_many :users, through: :user_books
end
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
But you should really use a better more descriptive name that tells other programmers what this represents in the domain and not just a amalgamation of the two models it joins such as Loan or Checkout.
I would also use t.datetime :returned_at to create a datetime column that can record when the book is actually returned instead of just a boolean.
If you want to create a join record with any additional data except the foreign keys you need to create it explicitly instead of implicitly (such as by user.books.create()).
#book_user = Book.find(params[:id]).book_users.create(user: user, returned: true)
# or
#book_user = current_user.book_users.create(book: user, returned: true)
# or
#book_user = BookUser.new(user: current_user, book: book, returned: true)

How to set up an admin user in rails

I have a simple relationship
class School < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :schools
end
A user can be part of many schools but at the same time a user might be the admin of a number of schools. I set up a many-to-many relationship to represent this however I'm not sure how I would distinguish between admins and simple users.
I initially thought of setting a table which has a school_id and a user_id and every entry will represent the school id and the user id of any admins that the school has however I'm not sure how I would represent this in rails or if it's the best way to solve this problem? And if it is, how do I access the table without a model associated to it?
What I mean by what I said above:
school_id user_id
1 3
1 4
Which means that the school with id 1 has 2 admins (3 and 4)
What you are looking for is a more complex many_to_many relationship between school and user called has_many :through. This relationship allows you to have many to many relationship with access to the table that represents the relationship. If you use that relationship, your models should look something like this:
class User < ActiveRecord::Base
has_many :school_roles
has_many :schools, through: :school_roles
end
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
end
class School < ActiveRecord::Base
has_many :school_roles
has_many :users, through: :school_roles
end
And the migrations of those tables would look something like this:
class CreateSchoolRoles < ActiveRecord::Migration
def change
create_table :schools do |t|
t.string :name
t.timestamps null: false
end
create_table :users do |t|
t.string :name
t.timestamps null: false
end
create_table :school_roles do |t|
t.belongs_to :school, index: true
t.belongs_to :user, index: true
t.string :role
t.timestamps null: false
end
end
end
I would suggest to make the "role" field in the "school_roles" migration an integer and then use an enum in the model like so:
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
enum role: [ :admin, :user ]
end
which allows you to add more roles in the future, but it's your call
combining polymorphic association with has_many :through in my opinion is best option.
Let's say you create supporting model SchoolRole, which
belongs_to :user
belongs_to :school
belongs_to :rolable, polymorphic:true
This way:
class School ...
has_many :administrators, :as => :schoolroles
has_many :users, :through => :administators
#school.administrators= [..., ...]
It is quite agile.
#user=#school.administrators.build()
class User
has_many :roles, :as => :rolable
def admin?
admin=false
self.roles.each do |r|
if r.role_type == "administator"
admin=true
break
end
end
admin
end
....

Has many :through association not found

I have two models that can have tags added to them.
Player
Ticket
and I have a Tag model which belongs to both so I have two join models
tag_ticket
tag_player
I am getting a Could not find the association :tag_tickets in model Ticket error but my association is in there.
class Ticket < ActiveRecord::Base
has_many :tag_tickets
has_many :tags, :through => :tag_tickets
end
I'm just focusing on the Ticket model but the player model should look similar.
this is my migration for TagTicket
class CreateTagTickets < ActiveRecord::Migration
def change
create_table :tag_tickets do |t|
t.integer :ticket_id
t.integer :tag_id
t.timestamps
end
end
end
You need to specify the :tag_tickets join first like this:
class Ticket < ActiveRecord::Base
has_many :tag_tickets
has_many :tags, :through => :tag_tickets
end
You would also need to specify the joins in your TagTicket model:
class TagTicket < ActiveRecored::Base
belongs_to :ticket
belongs_to :tag
end
Alternatively, you can skip all this and use a habtm join (only recommended if the tag_tickets join is truly only used as a join and has no primary key for itself). In this case you would have no TagTicket model (just a tag_tickets table) and the Ticket model would look like this:
class Ticket < ActiveRecord::Base
has_and_belongs_to_many :tags
end

Setting up a polymorphic association

I am trying to add a "following" like functionality to my site but I am having trouble finding the right way to use a polymorphic association. A user needs to be able to follow 3 different classes, these 3 classes do not follow the user back. I have created a user following user in the past but this is proving to be more difficult.
My Migration was
class CreateRelationships < ActiveRecord::Migration
def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :relations_id
t.string :relations_type
t.timestamps
end
end
end
My Relationship model is
class Relationship < ActiveRecord::Base
attr_accessible :relations_id
belongs_to :relations, :polymorphic => true
has_many :followers, :class_name => "User"
end
In my User model
has_many :relationships, :foreign_key => "supporter_id", :dependent => :destroy
and in the other 3 models
has_many :relationships, :as => :relations
Am I missing something with setting up this association?
You basically have it right, except for a few minor errors:
attr_accessible :relations_id is redundant. Remove it from your Relationship model.
Both Relationship and User models call has_many to associate with each other. Relationship should call belongs_to because it contains the foreign key.
In your User model, set :foreign_key => "follower_id".
Here is how I would do it.
Have a Follow middle class with polymorphic association on the followable content side and has_many on the follower user side (user has many follows).
First, create a follows table:
class CreateFollows < ActiveRecord::Migration
def change
create_table :follows do |t|
t.integer :follower_id
t.references :followable, :polymorphic => true
t.timestamps
end
end
end
Replace Relationship model with a Follow model:
class Follow < ActiveRecord::Base
belongs_to :followable, :polymorphic => true
belongs_to :followers, :class_name => "User"
end
Include in User model:
has_many :follows, :foreign_key => :follower_id
Include in your three followable classes:
has_many :follows, :as => :followable
You can now do this:
TheContent.follows # => [Follow,...] # Useful for counting "N followers"
User.follows # => [Follow,...]
Follow.follower # => User
Follow.followable # => TheContent

In RoR, how do I create TWO one to one relationship between two tables?

In RoR3,
I have Users and Skills and each skill is created by a user. I wanted to record that, so I created a one to many relationship.
class User < ActiveRecord::Base
has_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :user
end
However, each user also has many skills in the sense that, user "Bob" created skill "Kung Fu", user "Charlie" created skill "Karate" and user "Bob" both created and is able to do both "Kung Fu" and "Karate"
How should I represent this with ActiveRecord? Should I just create a new table "user_skills" which has_many :skills? and belong_to :user?
There are two different associations here. The first is a one-to-many association. An user can be the creator of any number of skills. The second one is a many-to-many association, an user can have many skills and a skill can have many users.
The first one is a simple belongs_to <-> has_many declaration. For the second one, you either need a has_and_belongs_to_many declaration in both models, and a related join table, or a dedicated join model, and a has_many :through declaration. Let's try the first one:
Method 1: HABTM
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_and_belongs_to_many :users
end
This requires a join table called "skills_users" that has columns named user_id and skill_id
Method 2: Has many through (Join model)
The second one is similar, but adds a model that acts as the middleman. This has an added benefit that you can include additional columns in the join model, like for example a skill level.
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_many :user_skills
has_many :skills, :through => :user_skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_many :user_skills
has_many :users, :through => :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
Having those two models
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
has_and_belongs_to_many :users
end
You would have to create an extra migration (without the model)
rails generate migration CreateSkillsUsersJoin
which will give you
class CreateSkillsUsersJoin < ActiveRecord::Migration
def self.up
create_table :skills_users, id => false do |t|
t.references "user"
t.references "skill"
end
add_index :skills_users,["user_id","skill_id"]
end
def self.down
drop_table :skills_users
end
end
The methods self.up and self.down you will have yo add them
You'd be well served using a gem like acts_as_taggable_on which you'd be able to simply setup and use in your User model, something like:
acts_as_taggable_on :skills
Honestly, they've figured all this stuff out, as it's not as simple as what you're trying to do, OR I should rephrase that and say, what you are trying to do is overtly 'complex' and this gem allows you to just keep on, keeping on after it's set up.
Read the Readme.

Resources