I have a class Reservation in which I want to put a collection (HABTM) of Resources, through an intermidiate class of ReservedResources. This is not a classic HABTM as in ReservedResources I want to store the quantity of the resource reserved.
On the form I want to display the available resources dynamically, something like this:
<% #resources.each do |r| %>
<%= f.label "#{r.name}" %>
<%= f.number_field "reserved_resources#{r.id}".intern %><br>
<% end %>
The problem is that there is no method named "reserved_resourcesXX", where XX is the resource ID. I want to store the quantity of the resource per its ID on the params hash, but I can't manage to do that...
Is it possible? If so, how? I want to avoid JS for this...
Actually what you should have is something like:
class Reservation < ApplicationRecord
has_many :reserved_resources
has_many :resources, through: :reserved_resources
end
class Resource < ApplicationRecord
has_many :reserved_resources
has_many :reservations, through: :reserved_resources
end
class ReservedResource < ApplicationRecord
belongs_to :reservation
belongs_to :resource
end
Now assuming you have reserved_quantity attribute in reserved_resources table.
Example of use from rails console:
resource = Resource.create(name: 'resource 1')
reservation = Reservation.create(name: 'reservation 1')
resource.reserved_resources.create(reservation: reservation, resource_quantity: 20)
reservation = Reservation.create(name: 'reservation 2')
resource.reserved_resources.create(reservation: reservation, resource_quantity: 10)
Sanity check:
resource.reservations.count
=> 2
you can then get total quantity of reserved resources for a particular resource by doing:
resource.reserved_resources.sum(:resource_quantity)
=> 30
I'll just start off with an example which I guess will get you going.
Scenario
Imagine a store selling products. Users can reserve products to later buy them. They can also define how many of each product they want to reserve.
class User
has_many :reservations
has_many :products, through: :reservations
end
class Product
has_many :reservations
has_many :users, through: :reservations
end
class Reservation
belongs_to :user
belongs_to :product
end
Now when creating your reservations model you can simply add an extra field quantity, resulting in this reservations schema:
create_table "reservations" do |t|
t.integer "user_id",
t.integer "product_id",
t.integer "quantity",
t.datetime "created_at"
t.datetime "updated_at"
end
So you take a basic HABTM relationship and add the extra field. Thats exactly what HABTM is for, to add extra info when it is required.
Your usecase
Adapt this to your models and I assume the html forms will be kinda straightforward from there on.
Related
I'm having some difficulty expressing a linkage of a User to my Listing model.
I set up a Rails form when I associated a designer_id (added to listing_params as a private controller method) that would link a selected user to a Listing model when created:
#migration
add_column :listings, :designer_id, :integer
_form.html.erb
<%= collection_select :listing, :designer_id, #account.users, :id, :name, prompt: "Choose..." %>
Checking in the console, the form returned the correct user id as designer_id. Success!
What I need to do now is access the User name using a Listing method, but I'm getting stuck- the issue is primarily making the translation from the id procured to the User referenced:
#listing.rb
def designer
self.designer_id == User.find_by_id(params[:name])
if self.designer_id = nil
return "N/A"
else
return "#{User.name}"
end
Much appreciated!
in the migration if you are on at least rails 4 you can do
add_reference(:listings, :designer)
you may need to do
add_reference(:listings, :designer, :foreign_key => { to_table: 'users'}
other options I often use
add_reference(:listings, :designer, :foreign_key => { to_table: 'users'} index: true, limit: 8)
Migration aside you can do this in the models.
class Listing
belongs_to :designer, class_name: 'User', inverse_of: :listings
end
and in users
class User
has_many :listings, inverse_of: :designer, dependent: :destroy
end
Getting the users name would then be,
listing.designer.name
if you are doing this in a controller you will want to pre-load the association so you are not introducing an n+1 query to a list of listings.
Let's say I have a model Movie. Movies can have_many of each other through an intermediary model AssociatedMovie.
How can I specify the nature of the relationship between two Movies? For any given pair of Movies, the relationship may be prequel/sequel, or remake/original, or inspired/inspired by, or related/related, etc. Right now, I can't give the relationships names.
Here's my schema and associations:
create_table "movies", force: true do |t|
t.string "title"
end
create_table "associated_movies", force: true do |t|
t.integer "movie_a_id"
t.integer "movie_b_id"
end
class Movie < ActiveRecord::Base
has_many :movies, :through => :associated_movies
end
class AssociatedMovie < ActiveRecord::Base
has_many :movies
end
And here's the query for setting each Movie's associated Movies:
def movie_associated_movies
associated_movie_ids = AssociatedMovie.
where("movie_a_id = ? OR movie_b_id = ?", self.id, self.id).
map { |r| [r.movie_a_id, r.movie_b_id] }.
flatten - [self.id]
Movie.where(id: associated_movie_ids)
end
I think I'd probably have to add movie_a_type and movie_b_type attributes to AssociatedMovie. But I'm not sure how I could specify which Movie is attached to which type.
Anyone have any ideas?
You're already half-way there with has_many :through (using an intermediary model) - this allows you to add as many extra attributes as you like.
I think your problem is down to your relationships, which I'll explain below:
#app/models/movie.rb
class Movie < ActiveRecord::Base
has_many :associated_movies, foreign_key: :movie_a_id
has_many :movies, through: :associated_movies, foreign_key: :movie_b_id
end
#app/models/associated_movie.rb
class AssociatedMovie < ActiveRecord::Base
belongs_to :movie_a, class_name: "Movie"
belongs_to :movie_b, class_name: "Movie"
end
The above will give you access to:
#movie = Movie.find params[:id]
#movie.associated_movies #-> collection of records with movie_a and movie_b
#movie.movies #-> all the movie_b objects
--
Because you're using has_many :through, rather than has_and_belongs_to_many, you'll be at liberty to add as many attributes to your join model as you need:
To do this, you just have to add a migration:
$ rails g migration AddNewAttributes
#db/migrate/add_new_attributes_________.rb
class AddNewAttributes < ActiveRecord::Migration
def change
add_column :associated_movies, :relationship_id, :id
end
end
$ rake db:migrate
-
... I apologize if this is a little off-course; however I would actually add a separate model for your relationships (considering you have them predefined):
#app/models/relationship.rb
class Relationship < ActiveRecord::Base
#columns id | movie_a_type | movie_b_type | created_at | updated_at
has_many :associated_movies
end
#app/models/associated_movie.rb
class AssociatedMovie < ActiveRecord::Base
belongs_to :movie_a, class_name: "Movie"
belongs_to :movie_b, class_name: "Movie"
belongs_to :relationship
delegate :movie_a_type, :movie_b_type, to: :relationship
end
This may seem a little bloated (it is), but it will provide extensibility.
You'll have to add another table, but it will ultimately provide you with the ability to call the following:
#movie.associated_movies.each do |associated|
associated.movie_a #-> current movie
associated.movie_b #-> related movie
associated.movie_a_type #-> "Original"
associated.movie_b_type #-> "Sequel"
end
You'd then be able to pre-populate the Relationship model with the various relationships you'll have.
I can add to the answer as required.
My models are setup as follows:
class Contract <ActiveRecord::Base
has_many :contract_upgrades
has_many :upgrades, through: :contract_upgrades
end
My Upgrade model
class Upgrade < ActiveRecord::Base
has_many :contract_upgrades
has_many :contracts, through: :contract_upgrades
end
my ContractUpgrade model
# schema
t.integer :contract_id
t.integer :upgrade_id
t.integer :qty
class ContractUpgrade < ActiveRecord::Base
belongs_to :contract
belongs_to :upgrade
end
My contract form looks like this...
- #upgrades.available.each do |upgrade|
%tr{class: "#{upgrade.id}", id: 'upgrade-checkboxes'}
%td
.upgrade-checkboxes
= check_box_tag "contract[upgrade_ids][]", upgrade.id, #contract.upgrades.include?(upgrade), { upgrade_id: upgrade.id }
= label_tag "contract_upgrade_#{upgrade.id}", upgrade.name
%td
= number_to_currency("#{upgrade.price}")
- if upgrade.allow_each
here is where it gets a little tricky...
%td
%input{type: 'text', placeholder: 'Qty'} # displays text_field box
= number_field_tag # displays text_field box
When the upgrade check_box is checked the upgrade.id and contract.id save as it should and works perfectly. I need to be able to input a quantity on those upgrades that can have multiple. In my Contracts controller the upgrade_ids are whitelisted via {upgrade_ids: []}, and no matter what I've done whenever anything is in the qty field I get an unpermitted_parameter :qty...
UPDATE
I'd be able to figure out what/how to do what I essentially need to do if someone can get Rails Guides Associations: has_many_through example to work. In this example directly from the rails guides the join table has an appointment_date attribute.
I've got two different types of users here, Fans and Artists.
I have a Relationships model to allow Fans to follow Artists.
Creating the relationship is working fine, but I now need to check if a Fan is following an Artist.
I also have add_index :relationships, [:fan_id, :artist_id], unique: true in my database, so a Fan cannot follow an Artist multiple times and displays an error if they try to follow again.
Now when a Fan clicks the follow button I want an unfollow button to show. To display this I need to check if a Fan is following an Artist.
Here is my code:
### model/artist.rb ###
class Artist < ActiveRecord::Base
has_many :relationships
has_many :fans, through: :relationships
belongs_to :fan
end
### model/fan.rb ###
class Fan< ActiveRecord::Base
has_many :relationships
has_many :artists, through: :relationships
belongs_to :artist
def following?(artist)
Fan.includes(artist)
end
end
### relationship.rb ###
class Relationship < ActiveRecord::Base
belongs_to :fan
belongs_to :artist
end
### views/artists/show.html.erb ###
<% if current_fan.following?(#artist) %>
unfollow button
<% else %>
follow button
<% end %>
I'm 100 percent the error is in my "following?" method.
As Jordan Dedels said, this will work:
def following?(artist)
artists.include?(artist)
end
But it forces rails to either load the join models, or to use a join query.
If you know the structure of your associations, and you only want a boolean (true/false), then this is faster:
def following?(artist)
Relationship.exists? fan_id: id, artist_id: artist.id
end
Inside your Fan model, try:
def following?(artist)
artists.include?(artist)
end
I want my users to have many skills. I do have a users and skills database table.
I used has_many_and_belongs_to association in user.rb
has_many :skills
which I am not sure if its correct. And in skill.rb
has_and_belongs_to_many :users
I also created a migration like that:
def change
create_table :user_skills do |t|
t.belongs_to :users
t.belongs_to :skills
end
Is this correct?
So IF this is correct, how do I add new skills to my user? What is the general approach?
What I thought of,
In my users controller on update action I will be updating user's skill and update the user_skills table.
How is this done?
Also How do I iterate through my user_skills table for a specific user? (in view)
Any guidance, resource, tip will be great help for me as its the first time i do something like this in Rails.
Thanks
In Rails, most would prefer to use has_many :through over habtm associations. Here's a guide on how to use it: ActiveRecord guide.
A has_many through association for users and skills would look like this in your relevant models:
class User < ActiveRecord::Base
has_many :user_skills
has_many :skills, through: :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
class Skill < ActiveRecord::Base
has_many :user_skills
has_many :users, through: :user_skills
end
Your migration would look like:
def change
create_table :user_skills do |t|
t.references :user, index: true
t.references :skill, index: true
end
end
The indexes in the migration are for faster look-ups for using the reference_id. It's advisable to do that for all references.
To add new skills to your user, you can refer to this SO answer.
To update a user's skill, you could do this:
#skill = #user.skills.find(params[:skill_id])
#skill.update(skill_params)
To create a user's skill, you could do this:
#user.skills.create(skill_params)
To add a skill to user, you could do this in your update action:
#user.update(user_params)
#app/views/users/edit.html.erb
<%= f.select :skill_ids, Skill.all.collect {|x| [x.name, x.id]}, {}, :multiple => true %>
When working with has_many through, you won't need to go through the user_skills table to get a specific user. You would, however, might need to get a specific user from a skill. To do this:
#skill.users.find(user_id)
Hope that helps!
If you set user to have_and_belong_to_many :skills also then this will work.
To create a new skill for a user do
user.skills.create!{...}
or to associate an existing skill with a user do
user << skill
"In my users controller on update action I will be updating user's skill and update the user_skills table. How is this done?"
user = User.find params[:id]
skills = user.skills
You can then do what you like to users skills
"Also How do I iterate through my user_skills table for a specific user? (in view)"
user.skills.each do |skill|
...
end
for more on HABTM association see http://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many-association-reference
Forgive me If I get it wrong, try to fill in the gaps but I think you want something that looks like this.
controller
def index
#to fetch all skills associated to users (add where u.id=? to fetch for a single user)
#users = User.select("u.name, s.name").
from("users u, skills s, users_skills us").
where("u.id = us.user_id").
where("s.id = us.skill_id")
end
def new
#user = User.new
#skills = Skill.all
end
def create
#user = User.new(params[:user])
...............................
end
in the create form
<%= form_for #user do |f| %>
<%= f.collection_select(:skill_ids, #skills,:id,:name)%>
<%= f.submit "Save" %>
<% end %>
In order to use HABTM you need a join table named either users_skills or skills_users (not sure it matters). It should contain two integer columns named user_id and skill_id. You should create indices for them as well. In your User model you want has_and_belongs_to_many :skills and in your Skill model you want has_and_belongs_to_many :users.
You need has_and_belongs_to_many on both sides of the realtionship.
class User
has_and_belongs_to_many :skills
class Skill
has_and_belongs_to_many :users
Alternatively (and better, in my opinion) would be to use has_many :through:
class User
has_many :user_skills
has_many :skills, through: :user_skills
class Skill
has_many :user_skills
has_many :users, through: :user_skills