Rails 3: Join Table with additional attributes - ruby-on-rails

I have a simple cart system that I have been working on for a little while for an application and am needing a little help in trying to figure out how to update a particular attribute in a join table (Between Order and Products).
Here is the code:
def add_product_to_cart
#product = Product.by_client(current_client).first
#order = current_order
unless #order.products.exists? :id => #product.id
#order.products << #product
end
end
I am trying to update a particular attribute when I update the #order.products...
This is what I am trying to do:
#order.products << #product --> When this happens I need to update a :price attribute..
Anyway of doing this?

class Order
has_many :products
def price
products.sum(:price)
end
end
Just off the top of my head. Here's the sum reference:
http://ar.rubyonrails.org/classes/ActiveRecord/Calculations/ClassMethods.html#M000296

Desire to put attributes into join table may be a sign of missing model. You can promote join table into model, say OrderItem, by adding primary key to it. HABTM associations in Order and Product then become has_many through associations. The new model would be a good place for setting up callback which populates price attribute. It can also unlock additional benefits, like time-stamping items and making them act_as_list, etc.

Related

Multiple id in a single row

I have a Business Model and a Category Model.
Business belongs_to multiple category. What I am trying to do is building the relation without help of a third join table.
In business table there is a string column which will hold the comma separated category_id.
So I am wondering is it possible to build the relation like that. Any wise comment and idea will be appreciated.
I think it would be easier to do it with just a method:
class Business < ActiveRecord::Base
def categories
#categories ||= Category.where(id: category_ids.split(','))
end
def category_ids=(ids)
# this is needed to reset the memoization, when you change the category ids
#categories = nil
super
end
end

<< won't work if adding duplicate item

Ticket has_many :products and Product belongs_to :ticket
This code:
def prepare
#ticket = Ticket.last
if #ticket.status != "open"
#ticket = Ticket.create!
end
#ticket.products<<(Product.find(params[:id]))
respond_to :js
end
will not add a new product to #ticket.products, if any instance of the same Product (with the same id) already exists in the #ticket. I want to be able to add two identical products to one ticket - a customer should be able to order two identical beers, shouldn't it?
I digged here in edgeguides, but seems as if only avoiding duplication was covered, not enabling it.
It sounds more like a many-to-many relationship, which can be managed by a has_many :through association:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Consider a shopping cart example, where a customer can order more than one of the same product through a line item, which has a quantity.
You can just try to add not-yet added products, using arel gem:
#ticket.products << Product.where(Product.arel_table[:id].eq(params[:id])
.and(Product.arel_table[:ticket_id].not_eq(#ticket.id)))
or for ruby-on-rails-4 you can use where.not negation:
#ticket.products << Product.where(id: params[:id]).where.not(ticket_id: #ticket.id)

Deleting a record in a has_and_belongs_to_many table - Rails

I've been looking, and can't find a good answer for how to delete records in a HABTM table. I assume a lot of people have this same requirement.
Simply, I have Students, Classes, and Classes_Students
I want a student to be able to drop a class, or delete the HABTM record that has signed that student up for that class.
There must be a simple answer to this. Does anyone know what it is?
The reason why .destroy or .delete does not work on this situation is due to the missing primary key in the middle table. However, our parent objects have this really cool method called {other_obj}_ids. It is a collection of ids on the left table object, of the right table object. This information is of course populated from our middle table.
So with that in mind, we have 2 object classes (Student, and Classes). Active record magic can generally figure out the middle table if you are not doing anything fancy, but it is recommended to use has_many :through.
class Student < ActiveRecord::Base
has_and_belongs_to_many :classes
end
class Classes < ActiveRecord::Base
has_and_belongs_to_many :students
end
What we can now do in terms of the middle table with this setup...
student = Student.find_by(1)
student.classes # List of class objects this student currently has.
student.class_ids # array of class object ids this student currently has
# how to remove a course from the middle table pragmatically
course = Course.find_by({:name => 'Math 101'})
# if this is actually a real course...
unless course.nil?
# check to see if the student actually has the course...
if student.class_ids.include?(course.id)
# update the list of ids in the array. This triggers a database update
student.class_ids = student.class_ids - [course.id]
end
end
I know this is a little late to answer this, but I just went through this exact situation tonight and wanted to share the solution here.
Now, if you want this deleted by the form, since you can now see how it is handled pragmatically, simply make sure the form input is nested such that it has something to the effect of:
What kind of trouble are you having? Do you have the appropriate :dependent=>:destroy and :inverse_of=>[foo] on your relations?
Let's say a class had a course title. You can do:
student.classes.find_by_course_title("Science").delete
So the proper answer here is to do something like this in your view:
<%= link_to 'Remove', cycle_cycles_group_path(#cycle, cycle), method: :delete %><br />
cycle is from a block the above code is within.
#cycle is an instance variable from the join models controller.
cycle_cycles_group_path is the nested join table "cycles_groups" under the model "Cycle" in the routes.rb file:
resources :cycles do
resources :cycles_groups do
end
end
and the join model controller looks like this:
def destroy
#cycles_group = CyclesGroup.find(params[:id])
#cycle = #cycles_group.cycle
#cycles_group.destroy
puts "cycle: #{#cycle}"
respond_to do |format|
format.html {redirect_to cycle_path(#cycle), notice: 'Training Week was successfully removed!'}
end
end

How to pass object id?

I have a relationship between two models
Category Model
class Category < ActiveRecord::Base
belongs_to :product
end
Product Model
class Product < ActiveRecord::Base
has_many :categories
end
I have category_id in products table but When I create a new product in my products table
category_id is null. I am new to rails can anyone help please?
First off, a thought - most of the time, products have many categories, but each category also contains many products. Perhaps your association should be a many-to-many? On to your actual question.
If I understand correctly, your question is really about how to create categories and products that are related to each other in the database, i.e. how to set the value of product_id when building a new category.
For clarity in case you need it, product_id would only be set for a category. The category, after all, belongs to that product, so it has to hold its owner's ID.
So, let's say you want to build a new category that belongs to an existing product - you can do this:
# in your view, where you link from products/show.html.erb to category/new.html.erb
<%= link_to "Build new category for this product", new_category_url(:id => #product.id) %>
# you must have #product defined, and you also must have
# 'resources :categories' in your routes file
# in your controller action categories/new, set the new category's product id:
def new
#category = Category.new(:product_id => params[:id])
end
# include a hidden field to contain the product_id in your new form
<%= form_for #category do |f| %>
<%= f.hidden_field :product_id %>
... other fields, labels, etc.
<% end %>
# save the record as you normally would (analogous to the code in your comment to #Chowlett).
#category = Category.new(params[:category])
if #category.save
redirect_to :action => "list", :notice => "Category saved successfully."
else
render :action => "new"
end
The above code allows you to build a product, then each category one-by-one. So, we are building your product first, then including a link from the product/show page to your category/new form, passing in the ID of the product you want that category to be part of.
If you want to build a product and some categories at the same time, it is a bit more complicated. For more information on this, take a look at http://railscasts.com/episodes/196-nested-model-form-part-1 (this is the first of a three-part series) and https://github.com/ryanb/nested_form. I don't suggest this course of action unless you are very comfortable with the above basics. I once got mired in this code for a week when I was new to Rails!
First off, you have the _id field in the wrong table. If Category belongs_to :product, then your categories table needs a field product_id.
Look at it this way: each Product can have many Categories - so what single value would you expect to find in a category_id field?
If you still have problems after correcting that, let me know.
Edit: Once you've got your tables set up, you still need to tell Rails what the link should be. You've got a few options. Assuming you've got a Category in hand, your best bet is new_prod = my_cat.create_product(). Alternatively, you can use new_prod = Product.create(:category => my_cat).
Later on, you can associate the models together like this:
my_prod.category = my_cat
my_prod.save

nested form & habtm

I am trying to save to a join table in a habtm relationship, but I am having problems.
From my view, I pass in a group id with:
<%= link_to "Create New User", new_user_url(:group => 1) %>
# User model (user.rb)
class User < ActiveRecord::Base
has_and_belongs_to_many :user_groups
accepts_nested_attributes_for :user_groups
end
# UserGroups model (user_groups.rb)
class UserGroup < ActiveRecord::Base
has_and_belongs_to_many :users
end
# users_controller.rb
def new
#user = User.new(:user_group_ids => params[:group])
end
in the new user view, i have access to the User.user_groups object, however when i submit the form, not only does it not save into my join table (user_groups_users), but the object is no longer there. all the other objects & attributes of my User object are persistent except for the user group.
i just started learning rails, so maybe i am missing something conceptually here, but i have been really struggling with this.
Instead of using accepts_nested_attributes_for, have you considered just adding the user to the group in your controller? That way you don't need to pass user_group_id back and forth.
In users_controller.rb:
def create
#user = User.new params[:user]
#user.user_groups << UserGroup.find(group_id_you_wanted)
end
This way you'll also stop people from doctoring the form and adding themselves to whichever group they wanted.
What does your create method look like in users_controller.rb?
If you're using the fields_for construct in your view, for example:
<% user_form.fields_for :user_groups do |user_groups_form| %>
You should be able to just pass the params[:user] (or whatever it is) to User.new() and it will handle the nested attributes.
Expanding on #jimworm 's answer:
groups_hash = params[:user].delete(:groups_attributes)
group_ids = groups_hash.values.select{|h|h["_destroy"]=="false"}.collect{|h|h["group_id"]}
That way, you've yanked the hash out of the params hash and collected the ids only. Now you can save the user separately, like:
#user.update_attributes(params[:user])
and add/remove his group ids separately in one line:
# The next line will add or remove items associated with those IDs as needed
# (part of the habtm parcel)
#user.group_ids = group_ids

Resources