Action method is :id with link_to? - ruby-on-rails

When I try to subscribe to a product by clicking the link:
<%= link_to "Subscribe", :controller => "products", :action => "subscribe_product", :id => product.id, :method => :post %>
I get this error and notice the parameters are wrong.
ActiveRecord::RecordNotFound in ProductsController#show
Couldn't find Product with id=subscribe_product
{"id"=>"subscribe_product", "method"=>"post"}
My subscribe_product method in my ProductsController is:
def subscribe_product
#product = Product.find(params[:id])
#product.subscriptions.create(:subscriber_id => current_user.id)
end
My route:
resources :products do
post :subscribe_product, :on => :collection
end
These are the associations:
class User
has_many :products
has_many :subscriptions, :foreign_key => :subscriber_id
class Product
belongs_to :user
has_many :subscriptions, :as => :subscribable
class Subscriptions
belongs_to :subscriber, :class_name => "User"
belongs_to :subscribable, :polymorphic => true
Users subscribe in another controller:
PagesController
def index
#product_history = current_user.products
end
end
pages/index.html.erb
<% for product in #product_history %>
<%= product.name %>
<%= product.price %>
<%= link_to "Subscribe", :controller => "products", :action => "subscribe_product", :id => product.id, :method => :post %>
<% end %>
So why is my action method being seen as the ID instead?

Try :
resources :products do
post :subscribe_product, :on => :member
end
It will generate routes like :
subscribe_product_product POST /product/:id/subscribe_product(.:format) {:action=>"subscribe_product", :controller=>"products"}
and use path like in view :
subscribe_products_path(product.id)

Since you're passing an id, the subscribe_product route should be a member route. Try this, and let me know what you get:
resources :products do
member do
post 'subscribe_product'
end
end
In the controller (to get around non-mass-assignable attributes):
def subscribe_product
#product = Product.find(params[:id])
subscription = Subscription.new
subscription.subscriber_id = current_user.id
#product.subscriptions << subscription
end

Please try this. Change your route to :
resources :products do
post :subscribe
end
Then change your link like :
<%= link_to "Subscribe", subscribe_products_path(:id => product.id), :method => :post %>

Related

Rails: create and destroy belongs_to association through link_to actions

I am working on an admin interface where I have images and heroes. The hero table consists in only two columns: id and image_id. I would like to be able to add and remove images to the hero table.
I have a select_to_hero and select_from hero action and view which display either all images not already connected or all existing heroes and both work, but the add_to_hero and remove_from_hero actions, which I use to create a new or destroy an existing association do not work.
Hero.rb Model
class Hero < ActiveRecord::Base
attr_accessible :image_id
belongs_to :image
end
Image.rb Model
class Image < ActiveRecord::Base
attr_accessible :alt, :author, :copyright, :file_name, :title
has_one :hero
mount_uploader :file_name, ImageUploader
end
Select_from_hero.html.erb
<% #heroes.each do |hero| %>
<%= link_to(image_tag(hero.image.file_name.url(:thumb), {:action => 'remove_from_hero', :id => hero, :hero => #hero}) %>
<% end %>
Select_to_hero.html.erb
<% #images.each do |image| %>
<%= link_to(image_tag(image.file_name.url(:thumb), {:action => 'add_to_hero', :id => image, :hero => #hero}) %>
<% end %>
images_controller.rb
def add_to_hero
#hero.image << Image.find(params[:id]) unless #hero.image.include?(Image.find(params[:id]))
if #hero.save
..
else
render :action => 'select_to_hero'
end
end
def remove_from_hero
#hero.image.delete(Image.find(params[:id]))
if #hero.save
..
else
render :action => 'select_from_hero'
end
end
With this setting I get:
NoMethodError in Admin::ImagesController#add_to_hero
undefined method `image' for nil:NilClass
and
NoMethodError in Admin::ImagesController#remove_from_hero
undefined method `image' for nil:NilClass
But I can query an existing association:
> Hero.find(2).image
Hero Load (0.3ms) SELECT `heroes`.* FROM `heroes` WHERE `heroes`.`id` = ? LIMIT 1 [["id", 2]]
Image Load (0.3ms) SELECT `images`.* FROM `images` WHERE `images`.`id` = 1 LIMIT 1
=> #<Image id: 1, file_name: "red.jpg", title: "blu", alt: "yellow", author: "John", copyright: "Jane", created_at: "2019-01-29 19:50:25", updated_at: "2019-01-29 19:50:25">
How can I get this working?
Update
Routes
namespace :admin do
resources :heroes
match '/images/select_to_hero', :to => 'images#select_to_hero', :as => :select_to_hero
match '/images/select_from_hero', :to => 'images#select_from_hero', :as => :select_from_hero
resources :images
match '/images/add_to_hero/:id', :to => 'images#add_to_hero', :as => :add_to_hero
match '/images/remove_from_hero/:id', :to => 'images#remove_from_hero', :as => :remove_from_hero
...
end
I had to move the select_to_hero and select_from_hero routes above resources :images or a call would have triggered the show action.
follow up your last comment about your add_to_action, I would like to suggest solution below
Select_to_hero.html.erb, change :hero and send only the id of hero like this below
<% #images.each do |image| %>
<%= link_to(image_tag(image.file_name.url(:thumb), {:action => 'add_to_hero', :id => image, :hero_id => #hero.id}) %>
<% end %>
images_controller.rb, find the hero first from the hero_id
def add_to_hero
#hero = Hero.find(params[:hero_id])
#hero.image << Image.find(params[:id]) unless #hero.image.include?(Image.find(params[:id]))
if #hero.save
..
else
render :action => 'select_to_hero'
end
end
Thanks to the always helpful #vasilisa I figured out a way to solve my problem. I had to use:
<% #images.each do |image| %>
<%= link_to(image_tag(image.file_name.url(:thumb)), {:action => 'add_to_hero', :id => image, :image_id => image}) %>
<% end %>
in my view and:
def add_to_hero
#hero = Hero.new(:image_id => params[:id])
if #hero.save
...
else
render :action => 'select_to_hero'
end
end
in my controller action.
This works, since in my case I only needed to declare an image as a hero by creating a new entry into Hero or to remove a hero declaration by destroying an entry in Hero.
Thanks all for their advice.

param is missing or the value is empty error with nested model form

I hope I am not asking an obvious question/ wont get down voted to hell for this.I am working on rails app and I am getting a "param is missing or the value is empty" error.
I have an event and questions that have already been created and I am using a nested form to allow the user to answer all the questions at once.
I am using rails 4
Models
class Event < ActiveRecord::Base
belongs_to :user
has_many :questions
accepts_nested_attributes_for :questions, allow_destroy: true
end
class Question < ActiveRecord::Base
belongs_to :user
belongs_to :event
has_many :answers
accepts_nested_attributes_for :answers, allow_destroy: true
end
class Answer < ActiveRecord::Base
belongs_to :user
belongs_to :question
end
Routes.rb
Rails.application.routes.draw do
root 'home#index'
devise_for :users, path_names: {sign_in: "login", sign_out: "logout"}, controllers: {omniauth_callbacks: "omniauth_callbacks"}
resources :answers
resources :users, only: [:new, :create]
resources :questions do
resources :answers #-> domain.com/questions/1/answers/new
end
resources :events, only: [:index, :new, :show, :update] do
patch ":id", action: :index
collection do
get :favorite
get "question/:id", action: :question
end
end
get 'users/new', to: 'users#new'
post 'users/new', to: 'users#create'
get 'events/favorite', to: 'events#favorite', via:[:get], as: 'favorite'
post 'events/:id' => 'events#update'
get 'answers/new' => 'answers#new'
get 'events/question' => 'events#question'
end
answers controller
class AnswersController < ApplicationController
def new
#event = Event.find(params[:id])
#answer = Answer.new
end
def show
end
def create
#answer = Answer.new(answer_params)
#answer.save
redirect_to events_path, notice: "Answered Questions"
end
private
def answer_params
params.require(:answer).permit(:response, :question, :event, :user, :event_id, :question_id, :user_id)
end
end
This is where my issue lies. Originally I had a very generic nested from a la http://railscasts.com/episodes/196-nested-model-form-revised but I switched the form_for down to the #answer because that is whats being created and switched to a button_to because the submit button was not writing the answer to the DB.(I believe it was trying to trigger something with #event )
<h1>New answers</h1>
<%= fields_for #event do |f| %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :questions do |b| %>
<p>
<%= b.text_field :poll%><br />
<%= form_for #answer do |x| %>
<%= x.text_field :response %>
<% end %>
</p>
<% end %>
<%= button_to "New", action: "create"%>
<% end %>
<%= link_to 'Back', answers_path %>
Please let me know if you need anymore code or have any questions
Thanks!
UPDATE
I have reworked my code based off this blog post http://iroller.ru/blog/2013/10/14/nested-model-form-in-rails-4/
now I am running the update through the events controller or at least I'm trying to.
The code is as follows, the error im getting now is
undefined local variable or method `event_params' for #
Thanks guys and girls sorry for the dumb questions
Models
class Event < ActiveRecord::Base
belongs_to :user
has_many :questions
accepts_nested_attributes_for :questions
end
class Question < ActiveRecord::Base
belongs_to :user
belongs_to :event
has_many :answers
accepts_nested_attributes_for :answers
end
class Answer < ActiveRecord::Base
belongs_to :user
belongs_to :question
end
Routes.rb
Rails.application.routes.draw do
root 'home#index'
devise_for :users, path_names: {sign_in: "login", sign_out: "logout"}, controllers: {omniauth_callbacks: "omniauth_callbacks"}
resources :answers
resources :users, only: [:new, :create]
resources :questions do
resources :answers #-> domain.com/questions/1/answers/new
end
resources :events, only: [:index, :new, :show, :update] do
patch ":id", action: :index
collection do
get :favorite
get "question/:id", action: :question
end
end
get 'users/new', to: 'users#new'
post 'users/new', to: 'users#create'
get 'events/favorite', to: 'events#favorite', via:[:get], as: 'favorite'
post 'events/:id' => 'events#update'
get 'answers/new' => 'answers#new'
get 'events/question' => 'events#question'
end
methods from events_controller
def question
#event = Event.find(params[:id])
end
def update
#event = Event.find(params[:id])
if #event.update(event_params)
redirect_to events_path, notice: "Answers saved"
else
redirect_to events_question_path, notice: "Answers not saved"
end
questions.erb
<%= simple_form_for(#event) do |f| %>
<%= f.error_notification %>
<%= f.object.name %>
<%= f.simple_fields_for :questions, f.object.questions do |q| %>
<%= q.object.poll%>
<%= q.simple_fields_for :answers, q.object.answers.build do |a|%>
<%= a.text_field :response %>
<% end %>
<%end %>
<%= f.button :submit%>
<% end %>

How to delete a join table association between two models?

I have the following setup of models and controllers:
Models:
class Company < ActiveRecord::Base
has_many :follow_companies, dependent: :destroy
has_many :followers, through: :follow_companies, source: :user
end
#join table
class FollowCompany < ActiveRecord::Base
attr_accessible :company_id
belongs_to :user
belongs_to :company
end
class User < ActiveRecord::Base
#a user can follow many companies
has_many :follow_companies, dependent: :destroy
has_many :followed_companies, through: :follow_companies, source: :company
end
Controllers:
class FollowCompaniesController < ApplicationController
def create
company = Company.find params[:follow_company][:company_id]
current_user.follow_companies.create! company_id:company.id
redirect_to company
end
def destroy
company = Company.find params[:id]
current_user.follow_companies.find_by(company_id: company.id).destroy
redirect_to company
end
end
The join table as well as companies and users is a resource:
resources :users
resources :companies
resources :follow_companies, only: [:create, :destroy]
Now I'd like to have buttons in my frontend for users to UNfollow a company assuming, they already follow that company:
The following view is part of the Company show action and not the FollowCompany show action
View:
<%= follow_company = current_user.follow_companies.find_by_company_id(#company.id) %>
<%= form_for(follow_company, :html => { :method => :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn pull-right" %>
<% end %>
When browsing to companies/show however, I get an error in the form_for line above:
ActionController::RoutingError at /companies/10
No route matches {:action=>"destroy", :controller=>"follow_companies", :format=>nil, :id=>#<FollowCompany user_id: 2, company_id: 10, created_at: "2013-03-21 23:34:36", updated_at: "2013-03-21 23:34:36">}
Request parameters
{"action"=>"show", "controller"=>"companies", "id"=>"10"}
Why can't rails find the route?
Pretty sure you need to pull :method => :delete out of the html args:
<%= form_for(follow_company, :method => :delete) do |f| %>
Not sure if that's the only problem but that's what caught my eye.
Something like this seems a bit more elegant too (automagically creates a form):
= button_to "Unfollow", follow_company_path(follow_company), :method => 'delete'
An alternative way to achieve this without writing a form is below. If you want you can do this with a single link.
<%= link_to "Unfollow", follow_compony_path(follow_company), :method => :delete %>

public_activity model

I have a guidelines model and a comments model and I'm trying to get them to both be trackable in my activities feed. Guidelines is working, comments is not.
The 2 main issues are:
I'm unsure how to add 'Comment' as a second trackable_type in the activities_controller.rb and I'm unsure how to sort my view in comment/_create.rb - it should say that a comment was added to x guideline (and link that guideline show page).
guidelines.rb
include PublicActivity::Model
tracked owner: ->(controller, model){controller && controller.current_user}
attr_accessible :content, :hospital, :title, :user_id, :guideline_id, :specialty, :updated_by, :current_user, :subtitle, :slug, :activities, :comment
belongs_to :user
has_many :favourite_guidelines
has_many :comments, :dependent => :destroy
comments.rb
include PublicActivity::Model
tracked owner: ->(controller, model){controller && controller.current_user}
belongs_to :guideline
belongs_to :commenter, class_name: 'User'
activities_controller.rb
def index
#activities = PublicActivity::Activity
.order("created_at desc")
.where(trackable_type: 'Guideline' 'Comment')
views/public_activity/comment/_create.html.erb
added a comment
<% if activity.trackable %>
to the guideline <%= link_to activity.trackable.body, activity.trackable %>
<% else %>
which can no longer be viewed
<% end %>
views/public_activity/guideline/_create.html.erb
added a guideline
<% if activity.trackable %>
titled <%= link_to activity.trackable.title, activity.trackable %>
<% else %>
which can no longer be viewed
<% end %>
my routes.rb is
Guidelines::Application.routes.draw do
get "activities/index"
# get "user/index"
ActiveAdmin.routes(self)
devise_for :admin_user, ActiveAdmin::Devise.config
get "guidelines/topic"
get "guidelines/topichospital"
get "guidelines/topicspecialty"
get "guidelines/favourite"
get "profiles/show"
get "guidelines/show"
devise_for :users
devise_scope :user do
get 'signup', to: 'devise/registrations#new', as: :register
get 'login', to: 'devise/sessions#new', as: :login
get 'logout', to: 'devise/sessions#destroy', as: :logout
get 'edit', to: 'devise/registrations#edit', as: :edit
put 'users' => 'devise/registrations#update', :as => 'user_registration'
get 'about', to: 'about#about', as: :about
end
resources :guidelines
get 'guidelines', to: 'guidelines#index', as: :guidelines
get 'favourites', to: "favourites#show", as: :favourites
get 'topics', to: 'guidelines#list', as: :topics
get 'hospitals', to: 'guidelines#listhospital', as: :hospitals
get 'specialties', to: 'guidelines#listspecialty', as: :specialties
resources :activities
root :to => 'guidelines#index'
resources :guidelines do
resources :comments
end
If I understood properly your question this cast explains it very well:
http://railscasts.com/episodes/406-public-activity in case you haven't seen it yet.
In my case I have Post model (instead of your Guidelines) and Comment and I just added
include PublicActivity::Model
tracked owner: Proc.new{ |controller, model| controller.current_user }
to both Post and Comment model and everything worked well without adding anything to activities_controller
public_activity/comment/_create.html.erb
said "<%= activity.trackable.body.truncate(100) %>" on <%= link_to activity.trackable.commentable.title, project_post_path(activity.trackable.commentable.project, activity.trackable.commentable.id) %> post.
public_activity/post/_create.html.erb
added a new <%= link_to activity.trackable_type.downcase, project_post_path(activity.trackable.project, activity.trackable.id) %> to <%= link_to activity.trackable.project.title, project_path(activity.trackable.project) %> project.
activities index.html.erb
<% #activities.each do |activity| %>
<%= link_to activity.owner.full_name, activity.owner if activity.owner %>
<%= render_activity(activity) %>
<% end %>

Rails3 nested-routing issues

I would like to create a mechanism for a User to keep track of other, favorite Users, similar to SO's favorite questions. I'm using the Rails 3.0 beta.
To do so, I have a User-Favorite HABTM relationship, which works as expected:
class User < ActiveRecord::Base
has_and_belongs_to_many :favorites, :class_name => "User", :join_table => "favorites", :association_foreign_key => "favorite_id", :foreign_key => "user_id"
end
The Favorites Controller only needs 3 of the 7 RESTful methods to manage a User's favorites:
class FavoritesController < ApplicationController
# GET /favorites
# GET /favorites.xml
def index
#user = User.find(params[:user_id])
#favorites = #user.favorites.joins(:profile).order("last_name,first_name")
...
end
def create
#favorite = User.find(params[:id])
current_user.favorites << #favorite
...
end
def destroy
#favorite = User.find(params[:id])
current_user.favorites.delete(#favorite)
...
end
end
The Routes.rb file contains the routing instruction:
resources :users, :except => :destroy do
resources :favorites, :only => [:index,:create,:destroy]
end
that generates these user-favorite routes:
GET /users/:user_id/favorites(.:format) {:controller=>"favorites", :action=>"index"}
user_favorites POST /users/:user_id/favorites(.:format) {:controller=>"favorites", :action=>"create"}
user_favorite DELETE /users/:user_id/favorites/:id(.:format) {:controller=>"favorites", :action=>"destroy"}
In the User's Show View, the User (#user) can be toggled as a favorite using image links, which works as expected:
<% if [test if user is a favorite] %>
# http://localhost:3000/favorites/destroy/:id?post=true
<%= link_to image_tag("favorite.png", :border => 0), :controller => :favorites, :action => :destroy, :post=>true, :id => #user %>
<% else %>
# http://localhost:3000/favorites/create/:id?post=true
<%= link_to image_tag("not-favorite.png", :border => 0), :controller => :favorites, :action => :create, :post=>true, :id => #user %>
<% end %>
However, in the current_user's favorite Index View, the link_to each favorite user:
# http://localhost:3010/users/4/favorites/3?post=true
<%= link_to image_tag("favorite.png", :border => 0), :controller => :favorites, :action => :destroy, :id => favorite, :post=>true %>
generates an error that reads:
No route matches "/users/4/favorites/3"
Questions:
Have I correctly specified my routing? Seem like the create and destroy routes would only need the id of the favorite, as the 'owner' of the favorite is always current_user.
If I'm simply referencing the Controller/Action in the Show view, do I even need the create/destroy routes?
Why doesn't the link_to in the Index View work correctly?
Are there any improvements that can be made to the over-all approach?
Your routing looks fine.
I think there is something wrong with your link_to, though. For one thing, the RESTful way is not to specify URLs with :controller and :action parameters. The correct way is using the generated URL methods, such as user_favorite_path. Also, you need to specify the :method parameter when targeting the destroy action. This is how I think the link_to should look like:
<%= link_to image_tag("favorite.png", :border => 0), user_favorite_path(#user, #favorite), :method => :delete %>
I believe the reason it says no route matches that URL is because you didn't specify the :method as :delete.
in your rake routes output, the paramater needed is :user_id not :id, so you need to send that in your link_to call.

Resources