Fix inconsistent route names (Rails) - ruby-on-rails

Routes
resources :favorites, only: [ :index, :create, :destroy ] , param: :listing_id
Rake routes
favorites GET /favorites(.:format) favorites#index
favorites POST /favorites(.:format) favorites#create
favorite DELETE /favorites/:listing_id(.:format) favorites#destroy
Notice the (s) in favorites, why is it not all favorite or favorites?
I create one favorite and destroy one favorite, so I think it should be singular in both.
I need
favorite POST /favorites/:listing_id(.:format) favorites#create
I tried this in my routes:
resources :favorites, only: [ :index, :destroy
] , param: :listing_id
post 'favorites/:listing_id' => 'favorite#create', as: :favorite
but get this error:
ArgumentError: Invalid route name, already in use: 'favorite' You may
have defined two routes with the same name using the :as option, or
you may be overriding a route already defined by a resource with the
same naming. For the latter, you can restrict the routes created with
resources as explained here:
http://guides.rubyonrails.org/routing.html#restricting-the-routes-created
How do I modify this one?
How do I keep it consistent as I need create path and destroy path consistent in my view for a number of reasons.
My controller
class FavoritesController < ApplicationController
before_action :load_listing, only: [:create, :destroy]
def index
#favorites = current_user.favorites.map{|i| i.id} || []
#listings = ListingsQuery::Search.call(:favorited_ids=> current_user.favorites.map{|i| i.id} )
respond_to do |format|
format.html {}
format.js {}
end
end
def create
if current_user.favorite!(#listing)
format.js {}
end
end
def destroy
if current_user.unfavorite!(#listing)
format.js {}
end
end
private
def load_listing
#listing_id = favorite_params[:listing_id]
#listing = Listing.find(#listing_id)
end
def favorite_params
params.permit(:listing_id)
end
end
view
<% if listing.is_favorited == true %>
<%= link_to favorite_path(:listing_id => listing.listing_id), method: :delete, remote: true do%>
<i id='i-<%= listing.listing_id %>' class=" fa fa-heart"></i>
<% end %>
<% else %>
<%= link_to favorite_path(:listing_id => listing.listing_id), method: :post, remote: true do %>
<i id='i-<%= listing.listing_id %>' class="fa fa-heart-o"></i>
<% end %>
<% end %>
create.js
(function(){
$("#i-<%= #listing_id %>").removeClass('fa-heart-o');
$("#i-<%= #listing_id %>").addClass('fa-heart');
$("#i-<%= #listing_id %>").parent().attr("data-method",'delete');
})();
Why is this...
resources :favorite do
collection do
post "for_lisiting/:listing_id", action: :create_for_listing
delete "for_listing/:listing_id", action: :delete_for_listing
end
end
preferred over this..
match 'favorite' => 'favorites#create', via: :post
match 'favorite' => 'favorites#destroy', via: :delete
It seems to me, but maybe I am wrong. that
/favorite/for_lisiting/:listing_id(.:format)
is unnecessarily long compared to
/favorite/:listing_id(.:format)
however, I am a novice, so value your reasoning.

When using rails resource helper, it creates some REST endpoints. You have listings that can be favorited, you are mixing both resources. Your resource is the Listing and favorite/unfavorite/favorites are actions on the resource.
Try something like this:
resources :listings do
member do
post :favorite, action: :create_favorite
delete :favorite, action: :delete_favorite
get :favorites
end
end
That will give you two route: /listings/:id/favorite (both for create -POST- and delete -DELETE-) and /listings/:id/favorites (GET). Both create and delete will be the same favorite_listing_path(listing) (or similar, check rake routes).
Now, on your ListingsController, define those actions:
class ListingsController < ApplicationController
def create_favorite
Listing.find(:id).favorites.create(user: current_user)
redirect_to :something, notice: 'Favorited'
end
def delete_favorite
Listing.find(id).favorites.where(user: current_user).destroy_all
redirect_to :something, notice: 'Unfavorited'
end
def favorites
#favorites = Listing.find(id).favorites
end
# of course, you could add a before_action to DRY it, I just wanted to be explicit on which is the actual resource
end

Related

Rails 4: Reload page with ajax after submitting remote form to a different controller

I've made a comments section for my 'posts' views and I've got remote: true working on the form so when you hit enter and submit the 'new comment' form to the database, it updates in the background fine (the comment is created, page doesn't redirect or change) but I can't get it to load the comments on the page. You have to refresh the page to see them.
I could do redirect_to :back in the comments controller after saving but that takes user to top of the page rather than staying put to see the comment appear.
I've tried render 'posts#show' after saving the comment in the comment controller create action but that tries to send you to /comments/posts/:slug/. If it actually rendered the posts show action I think this would work.
Comments controller:
class CommentsController < ApplicationController
before_action :find_commentable
def show
end
def new
#comment = Comment.new
end
def create
#comment = #commentable.comments.new comment_params
#comment.author = current_user if current_user
#comment.save
end
private
def comment_params
params.require(:comment).permit(:body, :author_id, :post_id)
end
def find_commentable
#commentable = Comment.find(params[:comment_id]) if params[:comment_id]
#commentable = Post.find_by_slug(params[:post_id]) if params[:post_id]
end
end
Comment section on post show view:
%ul#post-comments
= render 'comment_feed'
= form_for [#post, Comment.new], remote: true do |f|
= f.text_field :body, class: 'js-new-comment-field', placeholder: "Write a comment..."
posts/show.js.erb:
$("#post-comments").html("<%= escape_javascript render("comment_feed") %>");
Routes.rb:
resources :posts do
collection do
match 'search' => 'posts#search', via: [:get, :post], as: :search # For ransack search
end
resources :comments
end
resources :comments do
resources :comments # Replies on comments
end
Got it working!
This helped loads:
https://gemfile.wordpress.com/2014/03/06/rails-blog-comments-with-ajax/
I added this second match line to routes.rb and it's refreshing the page with the new comment now:
resources :posts do
collection do
match 'search' => 'posts#search', via: [:get, :post], as: :search
match '/comments/posts/:slug' => 'posts#show', via: [:get, :post], as: :comment
end
resources :comments
end
It's not ajax though: demo

How to call a method from controller using link_to?

I want my app to add a book to current_user when he clicks the link. My code seems to be ok, there are no errors but after user clicks the link nothing happens.
book.rb:
has_many :book_users
has_many :users, through: :book_users
user.rb:
has_many :book_users
has_many :books, through: :book_users
book_user.rb:
belongs_to :book
belongs_to :user
books_controller.rb:
before_action :is_admin?, except: [:book_params, :add_to_books_i_read, :index]
before_filter :authenticate_user!
expose(:book, attributes: :book_params)
expose(:books)
def create
if book.save
redirect_to(book)
else
render :new
end
end
def update
if book.save
redirect_to(book)
else
render :edit
end
end
def add_to_books_i_read(book_id)
current_user.books << Book.find(book_id)
end
In my index view I have
ul
-books.each do |book|
li
= link_to "#{book.name}", book_path(book)
= link_to " Add to my books", {:controller => "books", method: :add_to_books_i_read, book_id: :book.id}
So, what am I doing wrong? Why my method add_to_books_i_read does nothing? The table in database book_users doesn't record anything after clicking this link_to, but it works well itself (I checked via console). What can I do? How to make users add books through the method and how to call this method correctly? Every help would be appreciated, thank you!
= link_to " Add to my books", {:controller => "books", action: :add_to_books_i_read, book_id: :book.id}
The first, method in the link_to is the symbol of HTTP verb so you can not pass your function in the controller
To define and use new action on you controller you need to define route for that see here
Also try to organize your routes as (note member section)
resources :books do
member do
get :add_to_books_i_read
end
end
and see Prefix Verb column in rake routes output.
From #Ioannis Tziligkakis, I edited a bit.
Try:
# config/routes.rb
resources :books do
member do
get :add_to_books_i_read
end
end
# app/controller/books_controller.rb
def add_to_books_i_read
current_user.books << Book.find(params[:id])
# redirect or other logic
end
# app/views/books/index.html.haml
ul
- books.each do |book|
li
= link_to "#{book.name}", book_path(book)
= link_to "Add to my books", add_to_books_i_read_book_path(book.id)
Try this:
# config/routes.rb
resources :books do
# just not about method:
# use get for request, search
# use put for update
# use post for create
# use delete for destroy
put :add_to_books_i_read, on: :member
end
# app/controller/books_controller.rb
def add_to_books_i_read
#book = Book.find(params[:id])
current_user.books << #book
respond_to do |format|
format.js
end
end
# app/views/books/index.html.haml
ul
- books.each do |book|
li{:id => "book_#{book.id}"}
= render "links", book: book
# add field: app/views/books/_links.html.haml
= link_to "#{book.name}", book_path(book)
= link_to "Add to my books", add_to_books_i_read_book_path(book), method: :put, remote: true
# add field: app/views/books/add_to_books_i_read.js.erb
$("#book_<%= #book.id %>").html("<%= j render 'books/links', book: #book %>")
Since add_to_books_i_read is a controller action method you should update your routes to include this action for books resources in order to play nicely with Rails way of doing thing in a resourceful way. You also get to use a URL helper for this path that will look like this:
/books/:id/add_to_books_i_read
Your code could look like this:
# config/routes.rb
resources :books do
get :add_to_books_i_read, on: :member
end
# app/controller/books_controller.rb
def add_to_books_i_read
current_user.books << Book.find(params[:id])
# redirect or other logic
end
# app/views/books/index.html.haml
ul
- books.each do |book|
li
= link_to "#{book.name}", book_path(book)
= link_to "Add to my books", add_to_books_i_read_book_path(book)
Oh God. With your advise help I've finally got it! So,
Controller:
def add_to_books_i_read
current_user.books << Book.find(params[:id])
redirect_to(:back) #this line
end
In routes
resources :books do
member do
get :add_to_books_i_read
end
end
index view
-books.each do |book|
li
= link_to "#{book.name}", book_path(book)
= link_to " Add to my books", add_to_books_i_read_book_path(book)
Thank you all for your help! I'm really really grateful! :)

Rails button_to No route matches {:action=>"complete", :controller=>"dashboard", :name=>"Order"}

Please help i have tried my best. I really need your help.
So im trying to make a mark order as complete. Now it all works up to the button to mark order as complete. I ran a migration to add.
class AddCompleteToOrder < ActiveRecord::Migration
def change
add_column :orders, :complete, :boolean, default: false
end
end
Then i added a method the order.rb of
def complete!
update(complete: true)
end
Then routes.rb
resources :orders do
post "complete", to: "orders#complete", on: :member
end
Then this is the button
= button_to "Mark as complete", { action: "complete", id: #order.id }
But i dont have a #order.id
but a order does have a #order.name so i changed it to
= button_to "Mark as complete", { action: "complete", name: #order.name }
But then i get the error:
ActionController::UrlGenerationError in Dashboard#dadmin
Showing /Users/jacksharville/Desktop/dancer/app/views/dashboard/dadmin.html.haml where line #87 raised:
No route matches {:action=>"complete", :controller=>"dashboard", :name=>"Order"}
Extracted source (around line #87):
85
86
87
= link_to "Back to Dashboard", :back, :class => 'btn-danger btn'
= button_to "Mark as complete", { action: "complete", name: #order.name }
So clearly im doing the routes.rb wrong but i cant fix it. Please help. Any help greatly appreciated.
routes.rb (full file)
Rails.application.routes.draw do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
get 'home/index'
root 'home#index'
#pages
get '/why' => 'pages#why'
get '/trak' => 'pages#trak'
get '/contact' => 'pages#contact'
get '/mydms' => 'pages#mydms'
get '/air' => 'pages#air'
get '/ocean' => 'pages#ocean'
get '/road' => 'pages#road'
get '/courier' => 'pages#courier'
get 'fulfilment' => 'pages#fulfilment'
get 'express' => 'pages#express'
resources :dashboard
get 'dadmin' => 'dashboard#dadmin'
get 'myorders' => 'dashboard#myorders'
get 'label' => 'dashboard#label'
resources "contacts", only: [:new, :create]
devise_for :users
as :user do
get 'users/edit' => 'devise/registrations#edit', :as => 'edit_user_registration'
put 'users' => 'devise/registrations#update', :as => 'user_registration'
end
resources "orders"
get "/confirm" => "confirmations#show"
get 'dconfirmation' => 'orders#confirmation'
resources :orders do
post "complete", to: "orders#complete", on: :member
end
end
orders_controller.rb
class OrdersController < ApplicationController
before_filter :authenticate_user!
def new
#order = Order.new
end
def create
#order = current_user.orders.new(order_params)
#order.email = current_user.email
#order.name = current_user.name
#order.address_line_1 = current_user.address_line_1
#order.address_line_2 = current_user.address_line_2
#order.postcode = current_user.postcode
#order.city = current_user.city
#order.country = current_user.country
if #order.save
redirect_to dconfirmation_path
end
end
def order_params
params.require(:order).
permit(
:email,
:delivery_name,
:company_name,
:delivery_address1,
:delivery_address2,
:delivery_address3,
:delivery_city,
:delivery_postcode,
:delivery_country,
:phone,
:package_contents,
:description_content,
:restricted_items,
:terms_conditions,
:insurance,
:contents_value,
:cf_reference,
:reference_number
)
end
def show
#user = User.find(params[:id])
end
def confirmation
end
def complete!
order = Order.find(params[:id])
order.complete!
# handle response
end
end
dashboard_controller.rb
class DashboardController < ApplicationController
before_filter :authenticate_user!
def index
end
def admindashboard
(current_user.nil?) ? redirect_to(root_path) : (redirect_to(root_path) unless current_user.admin?)
end
def adminuser
(current_user.nil?) ? redirect_to(root_path) : (redirect_to(root_path) unless current_user.admin?)
end
def dadmin
(current_user.nil?) ? redirect_to(root_path) : (redirect_to(root_path) unless current_user.admin?)
# #order = Order.all
#order = Order.order("name").page(params[:page]).per(1)
end
def myorders
#order = current_user.orders.order("name").page(params[:page]).per(1)
end
def show
#user = User.find(params[:id])
end
def label
#order = current_user.orders.order("name").page(params[:page]).per(1)
end
def complete!
order = Order.find(params[:id])
order.complete!
# handle response
end
end
You can specify this additional option in the button_to tag :controller => "order", but the proper way to use button_to tag is to not create a separate hash for options. Instead use it as a button_to("Mark as complete", controller: "order", action: "complete", name: #order.name)
try this
button_to "Mark as complete", { controller: "orders", action: "complete", name: #order.name }, {method: :post}
This link can help: Routing Error - No route matches when using button_to with custom action
rake routes give me:
complete_order POST /order/:id/complete(.:format) orders#complete
You need to move the complete action from DashboardsController to OrdersController, which is where it is defined in the routes file.
Also, you can probably use:
= button_to "Mark as complete", complete_order_path(#order)
The controller action is looking for params[:order_id] so it can find the right order. I am not sure how orders are defined in your view, but you will need to pass an order object to the path.
If by any chance you defined a to_param method in Order class, which defines order's name rather than id as params, you will need to update the complete action to look for an order by name.
But my guess is that it is the default id. So passing the order object to the path should work.

Controller method #show getting called

I have a link on my #index view:
<%= link_to 'Export Calendar (ICS)', { controller: :tickets, action: :ics_export, format: :ics }, class: "class-needed right" %>
routes.rb that pertains to this:
resources :tickets
get 'tickets/calendar' => 'tickets#ics_export'
post 'tickets' => 'tickets#index'
patch 'tickets/:id/close' => 'tickets#close', as: 'close_ticket'
post 'tickets/:id' => 'ticket_comments#create'
My TicketsController that pertains:
before_action :set_ticket, only: [:show, :edit, :destroy, :update, :close]
def show
#ticket_comment = TicketComment.new
end
def ics_export
tickets = Ticket.all
respond_to do |format|
format.html
format.ics do
cal = Icalendar::Calendar.new
tickets.each do |ticket|
event = Icalendar::Event.new
event.dtstart = ticket.start
event.description = ticket.summary
cal.add_event(event)
end
cal.publish
render :text => cal.to_ical
end
end
end
private
def set_ticket
#ticket = Ticket.find(params[:id])
end
And when I click the link, it takes me to /tickets/calendar.ics which is correct but I get the following error:
ActiveRecord::RecordNotFound in TicketsController#show
Couldn't find Ticket with 'id'=calendar
Extracted source (around line #83):
private
def set_ticket
#ticket = Ticket.find(params[:id])
end
The #ticket = Ticket.find(params[:id]) is highlighted. Which make sense that it is failing to call a ticket with an id of calendar.
Request has parameters:
{"id"=>"calendar",
"format"=>"ics"}
How do I fix this error? Why is it calling the show action?
There is a footnote in the canonical Rails Routing from the Outside In to the effect:
Rails routes are matched in the order they are specified, so if you have a resources :photos above a get 'photos/poll' the show action's route for the resources line will be matched before the get line. To fix this, move the get line above the resources line so that it is matched first.
As commented, the fix is to specify get 'tickets/calendar' => ... ahead of resources :tickets. If the order of routes is in question, you can run rake routes, which, to the best of my knowledge, should render your routes in the order they are checked.

No route matches [GET] "/book/list" part II: The Route Revenge of Rails

I am trying to follow this rails tutorial but I am having a lot of issues due to version mismatches. I followed the instructions exactly and I really understand everything up until now but I can't seem to resolve this routing issue on my own. I read a good deal about routes independently but can't figure out what to do. This question has been posted once before on stackoverflow but that solution does not work for me because of version issues
Error Message:
No route matches [GET] "/book/list"
When trying to access http://localhost:3000/book/list.
Code
route.rb
Rails.application.routes.draw do
resources: :books
end
views>book>list.rhtml
<% if #books.blank? %>
<p>There are not any books currently in the system.</p>
<% else %>
<p>These are the current books in our system</p>
<ul id="books">
<% #books.each do |c| %>
<li><%= link_to c.title, {:action => 'show', :id => c.id} -%></li>
<% end %>
</ul>
<% end %>
<p><%= link_to "Add new Book", {:action => 'new' }%></p>
models>book.rb
class Book < ActiveRecord::Base
belongs_to :subject
validates_presence_of :title
validates_numericality_of :price, :message=>"Error Message"
end
models>subject.rb
class Subject < ActiveRecord::Base
has_many :books
end
controllers>book_controller.rb
class BookController < ApplicationController
def list
#books = Book.find(:all)
end
def show
#book = Book.find(params[:id])
end
def new
#book = Book.new
#subjects = Subject.find(:all)
end
def create
#book = Book.new(params[:book])
if #book.save
redirect_to :action => 'list'
else
#subjects = Subject.find(:all)
render :action => 'new'
end
end
def edit
#book = Book.find(params[:id])
#subjects = Subject.find(:all)
end
def update
#book = Book.find(params[:id])
if #book.update_attributes(params[:book])
redirect_to :action => 'show', :id => #book
else
#subjects = Subject.find(:all)
render :action => 'edit'
end
end
def delete
Book.find(params[:id]).destroy
redirect_to :action => 'list'
end
def show_subjects
#subject = Subject.find(params[:id])
end
end
There are a couple of issues I'm seeing. For one, your controller should be BooksController and not BookController (you'll also need to ensure it's in a file called books_controller.rb). Two, when you do resources :books, Rails will create the following routes
GET /books -> index
GET /books/:id -> show
GET /books/:id/edit -> edit
PUT /books/:id -> update
GET /books/new -> new
POST /books -> create
DELETE /books/:id -> destroy
As you can see, list is not one of the routes created, which is why you're getting that error message.
Extending #BartJedrocha's answer, first of all with your current routes i.e.
resources: :books
your application should not work and give you a Syntax error syntax error, unexpected ':', expecting keyword_end (SyntaxError).
As resources is a method invocation with argument :books.
So your route should be
resources :books ## Notice no : after "resources"
You have defined resources :books in your routes.So your controller Class name should be Plural ie., BooksController not BookController.So is the error.
Change your controller class name to BooksController and your filename to books_controller.rb
OR
update your routes to
resource :book #not singular
Note: I prefer the first way,because it suites the Rails convention
Update
You have to update your routes to like this
resources :books do
collection do
get 'list'
end
end
This will enable Rails to recognize the path /books/list with GET, and route to the list action of BooksController.

Resources