I have this scenario:
class Order < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :orders
end
class CreateJoinTableOrdersProducts < ActiveRecord::Migration
def change
create_join_table :orders, :products do |t|
t.index [:order_id, :product_id]
t.index [:product_id, :order_id]
t.decimal :price, precision: 8, scale: 2
t.integer :quantity, default: 1
end
end
end
Now, I need to add some records using the command line:
This works fine:
Order.first.products << Product.first
But, I need to add some more fields, like: price, quantity...
How can I do this?
Rails has the association.create and .build methods which allows you to create associated models on the fly:
Order.first.products.create(price: 999)
But as #test has pointed out you need to use has_many_through instead of has_and_belongs_to_many when the join table contains its own data.
has_and_belongs_to_many is used for a straight many-to-many relationship. There is no straightforward way to get the additional data such as price or quantity in a HABM relationship which is just built on a join table with the IDs of the joined models. The rails guides explain this pretty well.
class Order < ActiveRecord::Base
has_many :row_items
has_many :products, though: :row_items
end
class Product < ActiveRecord::Base
has_many :row_items
has_many :orders, though: :row_items
end
class RowItem < ActiveRecord::Base
belongs_to :product
belongs_to :order
end
You would also need to create the correct table:
rails g migration CreateRowItem product:belongs_to order:belongs_to price:decimal ...
You can then create associated records via:
Order.first.row_items.create(price: 999, product: product)
Related
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)
I've discovered a nice way to generate join tables for my HABTM relationships in my Rails app.
rails g migration CreateJoinTable table1 table2
This generates an ActiveRecord::Migration that employs the method create_join_table
I'm wondering what this wonderful mysterious method does. I guess it makes a table (probably without an id field) that has a column for table1 foreign key and a column for table2 foreign key, but does the table have any other features?. My habit for join tables has always been to add a unique index across both those columns so that a relationship between a record in table1 and a record in table2 cannot be entered twice.
My question boils down to: If I use create_join_table do I need to keep adding that unique index, or does this method do that for me (I think it should)?
The documentation I usually look at doesn't go into this sort of detail.
Called without any block, create_join_table just creates a table with two foreign keys referring to the two joined tables.
However, you can actually pass a block when you call the method to do any additional operations (say, adding indexes for example). From the Rails doc:
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end
Have a look at create_join_table documentation.
You can check the create_join_table code at the bottom (click on Source: show).
SchemaStatements#create_join_table() only creates join table without any fancy indexes etc,... So if you wish to use uniqueness constraint on two fields you have to do something like this:
class CreateJoinTable < ActiveRecord::Migration
def change
create_join_table :posts, :users do |t|
t.integer :post_id, index: true
t.integer :user_id, index: true
t.index [:post_id, :user_id], name: 'post_user_un', unique: true
end
end
end
Please also note that create_join_table by default does NOT create id field.
It turns out it doesn't do any more than the basics I described in the question. I found this out simply by running the migration and seeing what ends up in db/schema.rb
For those interested, to get the unique index do this:
class CreateJoinTable < ActiveRecord::Migration
def change
create_join_table :posts, :users
add_index :posts_users, [:post_id, :user_id], unique: true, name: 'index_posts_users'
end
end
Also be aware of how you define the dependent destroy for this join table.
If you later move away from HABTM and define the relationships using through: and get it wrong you might run into the 'to_sym' error I reported here.
Make sure you have defined the destroy like this:
class Proposal < ActiveRecord::Base
has_many :assignments
has_many :products, through: :assignments, dependent: :destroy # <- HERE
end
class Product < ActiveRecord::Base
has_many :assignments
has_many :proposals, through: :assignments, dependent: :destroy # <- HERE
end
class Assignment < ActiveRecord::Base
belongs_to :product
belongs_to :proposal
end
not this:
class Proposal < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :products, through: :assignments
end
class Product < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :proposals, through: :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :product
belongs_to :proposal
end
I have a HABTM relation in my application like this:
class Book < ActiveRecord::Base
has_and_belongs_to_many :authors
end
class Author < ActiveRecord::Base
has_and_belongs_to_many :books
end
In rails console I can access the records of Book and Author like this:
Book.all
Book.first
b = Book.first
b.title = "Title2"
b.save
...
But I don't know how to access the join table.
How can I access and see the records in the join table books_authors?
Is is possible to change the join table rows?
If you want to access the join table records, you'll have to recreate this with a has-many-through relationship. There's a great guide to doing this, and the differences between has-many-through and has-and-belongs-to-many, here: http://railscasts.com/episodes/47-two-many-to-many.
You'll need to create a new migration like the following to create the join table:
class Authorships < ActiveRecord::Migration
def change
create_table :authorships do |t|
t.belongs_to :book, index: true
t.belongs_to :author, index: true
t.timestamps null: false
end
add_foreign_key :authorships, :books
add_foreign_key :authorships, :authors
end
end
where 'Authorships' can be any name you deem suitable for the join table (or 'BookAuthors' if you'd want to stick with that).
As a quick example, your models could look like the following:
class Book < ActiveRecord::Base
has_many :authorships
has_many :authors, through: :authorships
end
class Author < ActiveRecord::Base
has_many :authorships
has_many :books, through: :authorships
end
class Authorship < ActiveRecord::Base
belongs_to :book
belongs_to :author
end
You can add extra columns to the join table and access them as needed, along with authorship_ids, and Author.first.books / Book.first.authors once they're added.
Hope that's useful!
I'm new to RoR and working on my first project. The basic concept behind the idea is to connect "Users" that have chosen a set a "Skills" with other Users that that have submitted a "Help Request" that deals specifically with those chosen skills. An app that connects Skilled Users with Users that need help if you will. My question has to do with the relationship between the Users, Skills, and Help_Request Models. It feels like a "has_many :through association" or maybe "polymorphic association" might be in order for this kind of three way relationship? Really not sure?
Any thoughts or suggestions would be greatly appreciated.
A polymorphic association is when a model should belong to another model. Let's say when you a comment model. You can comment on a post and a comment itself. That's when you would use a polymorphic.
In your case a simple has_many through would do.
It should look like this
class User < ActiveRecord::Base
has_many :skills
has_many :help_requests, through: :skills
end
class Skill < ActiveRecord::Base
belongs_to :user
belongs_to :helpRequest
end
class HelpRequest < ActiveRecord::Base
has_many :skills
has_many :users, through :skills
end
For more information the docs
I'm afraid ShivamD's answer will not suffice. That would require duplicate Skills, and queries the relevant User.help_requests via relationship, where instead you need an intersection.
Not just that, Users who create HelpRequests will already have a has_many relationship. That makes sense.
I won't include schema for the HABTM I'm about to suggest (see here), but I will cover the models. Essentially what you want:
skill = Skill.create(name: "My Skill")
user.skills << skill
help_request.skills << skill
user.matched_help_requests
#> [help_request]
Which can be achieved as follows:
class User
has_and_belongs_to_many :skills
def matched_help_requests
HelpRequest.joins(:skills).where("skills.id IN(?)", skills.pluck(:id))
end
end
class HelpRequest
has_and_belongs_to_many :skills
end
class Skill
has_and_belongs_to_many :users
has_and_belongs_to_many :help_requests
end
EDIT: here's the schmea for the HABTM:
rails g migration add_skills_join_tables
within migration
def change
create_table :skills_users, id: false do |t|
t.references :skill
t.references :user
end
create_table :help_requests_skills, id: false do |t|
t.references :skill
t.references :help_request
end
add_index :skills_users, [:skill_id, :user_id]
add_index :help_request_skills, [:skill_id, :help_request_id]
end
One hay is to create a has_and_belongs_to_manyrelation between the Users and Helprequests and Skills, so you end up with this:
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
end
class Help_request < ActiveRecord::Base
has_and_belongs_to_many :skills
end
class Skills < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :help_requests
end
Then you need to create the tables
rails g migration add_skills_users_table
rails g migration add_help_requests_skills_table
In the end run rake db:migrate
You can then search for it using User.first.skills
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