Rails, How to delete an association in has_many through - ruby-on-rails

I Have a scenario, which I am not able to figure out,
Profile Product
customer1 Iphone
customer2 Iphone
Now customer1 sells his iphone, so i will have to delete the association between customer1 and iphone but not the profile or product. I have an intermediate table for many to many relationship called product_profiles with product_id and profile_id as fields. Hence i need to delete id's from product_profiles table.
Product model
has_many :product_profiles
has_many :profiles, :through => :product_profiles
Profile model
has_many :product_profiles
has_many :products, :through => :product_profiles
Product controller
def destroy
if ProductProfile.where(:product_id => params[:id]).where(:profile_id => ?).destroy //how to get the profile_id from view when clicked on delete button
redirect_to admin_products_path
flash[:success] = "Product Successfully Deleted!"
else
flash[:error] = "Sorry! Could not complete the request, please try again!"
redirect_to :action => 'index'
end
end
View file
<% #products.each do |b| %>
<tr>
<td><%= #profile.name %></td>
<td>
<% b.sub_categories.each do |p| %>
<%= p.name + ',' %>
<% end %>
</td>
<td><%= b.name %></td>
<td>
<%= link_to "<span class='glyphicon glyphicon-edit'></span>".html_safe, edit_admin_product_path(b) %>
</td>
<td>
<%= link_to "<span class='glyphicon glyphicon-trash'></span>".html_safe, admin_product_path(b), :method => :delete, :title => "Delete Brand", "data-confirm" => "Do you really want to delete?" %>
</td>
</tr>
<% end %>

product = Product.find_by_id(product_id)
product.product_profiles.delete(product_profile_id)
Delete is not going to destroy your objects only the association between them.
Source:
http://neyric.com/2007/07/08/how-to-delete-a-many-to-many-association-with-rails/

You can pass parameters using the second argument of the path helper.
<%= link_to "<span class='glyphicon glyphicon-trash'></span>".html_safe, admin_product_path(b, :profile_id => #profile.id), :method => :delete, :title => "Delete Brand", "data-confirm" => "Do you really want to delete?" %>
In your controller you would then be able to access params[:profile_id]

Related

Removing ActiveStorage attachments in Rails 5

I have an app with products - each product has things like notes/FAQs/attachments.
I can delete the notes & FAQs successfully, but not the Active Storage attachments.
Could somebody please assist? I've tried using a separate method in the Products controller but that didn't work, so my current line is using an Uploads controller.
The current error I am getting is:
NameError in UploadsController#destroy
uninitialized constant Upload
Uploads controller:
class UploadsController < ApplicationController
load_and_authorize_resource :nested => :product
def destroy
#product = Product.find(params[:id])
#upload = #product.ActiveStorage::Attachment.find(params[:id])
#upload.purge
redirect_back(fallback_location: products_path)
end
end
Products view:
<% #product.uploads.each do |upload| %>
<% if can? :destroy, upload %>
<td><%= link_to t('X', :default => t("X")),
product_upload_path(#product, upload),
:method => :delete,
:data => { :confirm => t('.confirm', :default => 'Are you sure you want to delete this attachment?') },
:id =>'delete-faq' %></td>
<% end %>
<% if upload.variable? %>
<span><%= image_tag upload.variant(resize: "100x100"), class: "other-image" %></span>
<% elsif upload.previewable? %>
<span><%= link_to image_tag(upload.preview(resize: "100x100"), class: "other-image"), rails_blob_path(upload), target: "_blank" %></span>
<% else %>
<span><%= link_to image_tag("paper.jpg", size: "100x100", class: "other-image"), rails_blob_path(upload), target: "_blank" %></span>
<% end %>
<% end %>
routes:
resources :products do
resources :notes
resources :faqs
resources :uploads
end
I know this is not the most RESTful solution but I got it working, so am adding this as an answer in case it helps anybody else tearing their hair out.
I have checked the logs and it does remove both the attached image and the blob.
In my case, a product has_many uploads.
routes.rb:
resources :products do
resources :uploads do
match '/remove', to: 'products#remove', via: 'delete'
end
end
products controller (yes, I know, putting it here is not ideal)
def remove
#product = Product.find(params[:product_id])
#upload = #product.uploads.find(params[:upload_id])
#upload.purge
redirect_to product_path(#product), notice: "Upload was successfully removed."
end
products show view:
<% #product.uploads.each do |upload| %>
<%= link_to "X", product_upload_remove_path(#product, upload),
:method => :delete,
:data => { :confirm => t('.confirm', :default => 'Are you sure you want to delete this upload?') },
:id =>'delete-faq', :class => 'delete' %></td>
<% end %>
Hopefully this will help others or give a foundation for a more RESTful solution.

Rails select tag using belongs_to association

I have the following models:
Product:
class Product < ActiveRecord::Base
belongs_to :user
...
end
User
class User < ActiveRecord::Base
has_many :products
end
In my products view I need a drop down list of users, and when a user is selected, it should be associated with that specific product.
This is my view:
<%= form_tag (update_user_products_path) do %>
<%#products.each do |product| %>
<div class = "prod">
<img src='<%= product.cover_img %>' class='product_image_prods'></img>
<div class= "title"><small><b><%= link_to truncate(product.title, :length =>30), recommendations_path(:product_id => product.id, :rating_set_id => params[:rating_set_id]), :target => '_blank' %></b></small></div>
<br/>
<div><em>Current Rating: <%= product.rating %> </em></div>
<%= hidden_field_tag :product_id, product.id %>
<%= select_tag "user_id", options_for_select(User.all.collect {|u| [ u.name, u.id ] })%>
</div>
<% end %>
<%= submit_tag %>
<% end %>
I am confused as to if I should use form_tag and updated the Product.user_id, or should use nested_attributes for the user model in my product from?
UPDATE:
Products Controller Action:
def update_user
#product = Product.find(params["product_id"])
#product.update_attribute(:user_id, params[:user_id])
redirect_to :back, :flash => { :notice => "Updated users" }
end
Also updated my view. How do I update all product records with the correct user_id. Currently, when I submit my from, it updates just 1 product record.
All you have to do is figure which user was associated with which product.
For example (hooking the tag with product.id)
<%= form_tag(update_user_path, :id => "update_user_form") do %>
<%#products.each do |product| %>
<%= select_tag "user_id:#{product.id}", options_for_select(User.all.collect {|u| [ u.name, u.id ] })%>
<% end %>
<% end %>
When you submit you will get the the key with the product id and the selected user's id.
For example lets say product.id = 1 and user.id = 2
params will include
key => user_id:1 value=> 2
so taking the key and splitting it on : will give you the product_id then getting the value of that hash entry will give you the selected user.id for that product.

How can I pass the parameters to controller correctly?

From show view: I'd like to pass the shown message's id to discard action and trash the message.
From index view: I'd like to pass the checked messages' ids to discard action and trash them all at once.
But I only can trash one record at once even if I check multiple and submit from index view.
How can I archive both 1 and 2 with the same action????
Routes
match 'messages/discard(/:id)' => 'messages#discard', :via => :post , :as => :discard_messages
index view
<%= form_tag(:action => discard, :via => 'post') do %>
<% #messages.each do |m| %>
<tr>
<td><%= check_box_tag "id",m.id %></td>
<td><%= m.last_message.id %></td>
<td><%= 'unread' if m.is_unread?(current_user) %></td>
<td><%= m.last_message.created_at.to_s(:jp) %></td>
<td><%= m.last_sender.username %></td>
<td><%= link_to m.subject, show_messages_path(:id => m, :breadcrumb => #box) %></td>
</tr>
<% end %>
<%= submit_tag "discard", :class => 'btn' %>
<% end %>
show view
<%= link_to 'Discard', discard_messages_path(#messages), :class => 'btn', :method => 'post' %>
controller
def discard
conversation = Conversation.find_all_by_id(params[:id])
if conversation
current_user.trash(conversation)
flash[:notice] = "Message sent to trash."
else
conversations = Conversation.find(params[:conversations])
conversations.each { |c| current_user.trash(c) }
flash[:notice] = "Messages sent to trash."
end
redirect_to :back
end
use the [] naming in your html, which rails will then make available as an array in the params
index.html.erb
<td><%= check_box_tag "message_id[]", m.id %></td>
controller
# ...
else
conversations = Conversation.where("id IN (?)", params[:message_id][])
# ...
to simplify things further I would remove the conditional in your action and create two separate actions
routes
resource :messages do
member do
post 'discard' # /messages/:id/discard
end
collection do
post 'discard_all' # /messages/discard_all?message_id[]=1&message_id[]=22
end
end

Creating a link to subscribe to a product?

I need help on figuring how to make a link for my Product that enables users to subscribe to it. I first have my Subscription model:
class Subscription < ActiveRecord::Base
attr_accessible :subscribable_id
belongs_to :subscriber, :class_name => "User"
belongs_to :subscribable, :polymorphic => true
end
Then my Product model:
class Product < ActiveRecord::Base
attr_accessible :name, :price
belongs_to :user
has_many :subscriptions, :as => :subscribable
end
My plan is to make my view, similar to the DELETE method a link to click to subscribe to a product. Here is my routes, controller and then view:
resources :products do
post :subscribe_product, :on => :collection
end
ProductsController:
def subscribe_product
#product = Product.find(params[:id])
# Not sure what goes here next?
# Something like: user.subscriptions.create(:subscribable => product)
end
View:
<table>
<% for product in #products %>
<tbody>
<tr>
<td><%= product.name %></td>
<td><%= product.price %></td>
<td><%= link_to 'Delete', product, :confirm => 'Are you sure?', :method => :delete %></td>
<td><%= link_to 'Subscribe', :controller => "products", :action => "subscribe_product", :id => product.id %></td>
</tr>
</tbody>
<% end %>
</table>
Right now this gives a strange error:
ActiveRecord::RecordNotFound in ProductsController#show
Couldn't find Product with id=subscribe_product
Theirs 2 things,
Creating the method to subscribe.
Making the link correct.
How would I do these two?
By default link_to uses GET, so your router thinks you are trying to go to ProductsController#show with the first param being the ID
http://yoursite.com/products/subscribe_product/5
This is a get request to the products controller with an id param of subscribe_product.
If you pass :method => :post to your link_to helper, it will issue a post request, which is what your router is expecting.
<%= link_to 'Subscribe', :controller => "products", :action => "subscribe_product", :id => product.id, :method => :post %>
Without posting your user model, I can't know for sure, but the method will look like this:
#product.subscriptions.create(:user_id => user.id)
# user.id would be current_user.id, or whatever you are storing the current user as
Your subscribe_product path uses POST, so you'll want to change your link to use that method:
<%= link_to 'Subscribe', {:controller => "products", :action => "subscribe_product", :id => product.id}, :method => :post %>
Your action will probably look something like this:
#product.subscriptions << Subscription.new(:user_id => current_user.id)

Simple quantity function problems, shopping cart

I just picked up Agile Web Development with Rails 3rd Ed., and I'm going thru the Depot Application chapters, I'm attempting to create a simple Edit quantity function, and delete function. I've had luck with the delete function but no luck with the edit quantity function.
I'm going to provide a lot of information, so please don't feel overwhelmed. I've found this to be a challenging problem.
To add_to_cart.html.erb
<div class="cart-title">Your cart</div>
<table>
<% for item in #cart.items %>
<tr>
<td><% form_for #cart.items, :url => {:action => "cart_update", :id => "#{item.getinventoryid}"} do |f| %>
<%= f.text_field :quantity, :size => '3' %>
<%= f.hidden_field :id, :value => "#{item.getinventoryid}" %>
<%= f.submit 'cart_update' %>
<% end %></td>
<td><%=h item.quantity %> ×</td>
<td><%=h item.title %></li></td>
<td><%=h item.description %></td>
<td class="item-price"><%= number_to_currency(item.price) %></td>
<td><%= link_to 'remove', {:controller => 'inventories', :action => 'remove_cart_item', :id => "#{item.getinventoryid}"} %></td>
</tr>
<% end %>
<tr class="total-line">
<td colspan="4">Total</td>
<td class="total-cell"><%= number_to_currency(#cart.total_price) %></td>
</tr>
</table>
<%= button_to "Checkout", :action => 'checkout' %>
<%= button_to 'Empty cart', :action => 'empty_cart' %>
inventories_controller:
def cart_update
#inventory = Inventory.find(params[:id])
#cart = find_cart
#cart.increment_inventory_quantity(params[:inventory])
end
def remove_cart_item
inventory = Inventory.find(params[:id])
#cart = find_cart
#cart.remove_inventory(inventory)
redirect_to_index("The item was removed")
end
Cart.rb model
attr_accessor :items
def increment_inventory_quantity(id, quantity)
inventory_to_increment = #items.select{|item| item.inventory == inventory}
# We do this because select will return an array
unless product_to_increment.empty?
inventory_to_increment = inventory_to_increment.first
else
# error handling here
end
inventory_to_increment.quantity = quantity
end
def remove_inventory(inventory)
#items.delete_if {|item| item.inventory == inventory }
end
cart_item.rb model
attr_accessor :inventory, :quantity
def getinventoryid
#inventory.id
end
This produces strange results:
Notice the quantity 16 appears in both items from my loop (#Fail). When I submit the form a ArgumentError in InventoriesController#cart_update wrong number of arguments (1 for 2) error is returned. Parameters being passed:
{"commit"=>"cart_update",
"_method"=>"put",
"authenticity_token"=>"sH1tWXTJPltpSq5XaAkww7259IR5ZiflnqSFB2Zb0IY=",
"id"=>"50",
"cart_item"=>{"quantity"=>"16",
"id"=>"50"}}
You are getting the wrong number of arguments error because you are passing one argument to #cart.increment_inventory_quantity in the controller method. That method requires two arguments.
# In your controller:
def cart_update
#inventory = Inventory.find(params[:id])
#cart = find_cart
#cart.increment_inventory_quantity(params[:inventory]) # you are passing one thing
end
# Then in your model:
def increment_inventory_quantity(id, quantity) # the method wants two things
# ...
Possibly you intended to do something like this:
def cart_update
#inventory = Inventory.find(params[:cart_item][:id])
#cart = find_cart
#cart.increment_inventory_quantity(#inventory.id, params[:cart_item][:quantity])
end
Are you sure it's form_for( #cart.items ) and not form_for( item )?

Resources