My code is rather similar in concept to the railstutorial by Michael Hartl - the following users part, however it is different enough to ask this question.
The problem before all the details:
When I remove the remote: true from _like, everything works smoothly,
When _like.html.erb has remote: true I'm getting:
No route matches {:action=>"destroy", :controller=>"likes", :artwork_id=>1} missing required keys: [:id]
And I don't understand why.
The code snippets:
views/artworks
show.html.erb:
<div id="like_link">
<% if #artwork.liked_users.include?(current_user) then %>
<%= render 'unlike' %>
<% else %>
<%= render 'like' %>
<% end%>
</div>
_unlike.html.erb:
<%= link_to 'UNLike', like_path(artwork_id: #artwork.id), method: :delete, remote: true %>
_like.html.erb:
<%= link_to 'Like', likes_path(artwork_id: #artwork.id), method: :post, remote: true %>
views/likes
create.js.erb:
$("#like_link").html("<%= escape_javascript(render("artworks/unlike")) %>")
$("#likes").html('<%= #artwork.likes_count %>')
destroy.js.erb:
$("#like_link").html("<%= escape_javascript(render("artworks/like")) %>")
$("#likes").html('<%= #artwork.likes_count %>')
and the likes controller - controllers/likes_controller.rb
class LikesController < ApplicationController
respond_to :html, :js
def create
#artwork = Artwork.find(params[:artwork_id])
current_user.like!(#artwork)
respond_with #artwork
end
def destroy
#artwork = Artwork.find(params[:artwork_id])
current_user.unlike!(#artwork)
respond_with #artwork
end
end
routes.rb:
resources :artworks
resources :likes, only: [:create, :destroy]
here you create routes of
resources :likes, only: [:create, :destroy]
According RAILS convention it create routes for likes controller there it use :id such as "likes/:id" not likes/artwork_id so please create likes routes manually or follow convention.
Here is the problematic line:
# _like.html.erb
<%= link_to 'Like', likes_path(artwork_id: #artwork.id), method: :post, remote: true %>
The problem here is likes_path(artwork_id: #artwork.id), method: :post. If you check the output of rake routes you should see the following two lines for your likes resources:
likes POST /likes(.:format) likes#create
like DELETE /likes/:id(.:format) likes#destroy
As you can see the likes_path does not take in a parameter except the format, so your likes_path(artwork_id: #artwork.id) cannot be mapped to any path.
You should update your route definition with the following:
# app/config/routes.rb
resources :artworks do
resources :likes, only: [:create, :destroy]
end
With this route definition, your link_to calls will be:
# _like.html.erb
<%= link_to 'Like', artwork_likes_path(artwork_id: #artwork.id), method: :post, remote: true %>
# _unlike.html.erb
# Replace this_like_id with like_id attribute for this like being destroyed.
<%= link_to 'UNLike', artwork_likes_path(artwork_id: #artwork.id, id: this_like_id), method: :delete, remote: true %>
you do not have o pass artwork id
<%= link_to 'Like', likes_path(#artwork), method: :post, remote: true %>
<%= link_to 'UNLike', like_path(#artwork), method: :delete, remote: true %>
and now you can access this in controller by
params[:id]
Related
UPDATE - I'VE NOW SOLVED THIS PROBLEM - I created a partial for the each Course item and rendered them from the main listing view. Thanks for all your help, I'm not really sure why it worked but it did END OF UPDATE
Apologies if this looks like a repeat posting but I've tried applying solutions to similar questions and they haven't worked, I'm stuck! Any suggestions welcomed, thank you.
Problem
I have a 'Courses' model which belongs to a 'Listings' model. The courses are created and deleted on a page belonging to Listing i.e. "/listing/2/courses"
Error Message
No route matches [DELETE] "/listings/2/courses"
Courses Controller def destroy detail
class CoursesController < ApplicationController
before_action :authenticate_user!, except: [:show]
before_action :set_listing
before_action :set_course, except: [:index, :new, :create]
def destroy
#course = #listing.courses.find(params[:id])
#course.destroy
flash[:notice] = "Course deleted!"
redirect_back(fallback_location: request.referer)
end
private
def set_course
#listing = Listing.find(params[:listing_id])
#course = Course.find(params[:id])
end
def set_listing
#listing = Listing.find(params[:listing_id])
end
def course_params
params.require(:course).permit(:name, :curriculum_type, :summary, :address, :course_places, :start_date, :finish_date, :price)
end
end
listing/listingID/courses page detail
<%= #listing.courses.each do |f| %>
<div class="jumbotron">
<ul>
<li>Name = <%= f.name %></li>
<li>Type of course = <%= f.curriculum_type %></li>
<li>Number of places = <%= f.course_places %></li>
<li>Start Date = <%= f.start_date %></li>
<li>Finish Date = <%= f.finish_date %></li>
<li>Price (£) = <%= f.price %></li>
<%= link_to "Delete Course", listing_courses_path(#listing, #course), method: :delete %>
</ul>
</div>
<% end %>
Routes.rb detail
resources :users, only: [:show]
resources :listings, except: [:edit] do
member do
get 'listing'
get 'pricing'
get 'description'
get 'photo_upload'
get 'amenities'
get 'location'
get 'courses'
end
resources :courses, except: [:edit] do
member do
get 'listing'
get 'pricing'
get 'description'
get 'photo_upload'
get 'amenities'
get 'location'
end
end
end
<%= link_to listing_course_path(#listing,f), :method => :delete, :data => { :confirm => 'Are you sure?' } %>
or try
<%= link_to listing_course_path(#listing,id: f.try(:id)), :method => :delete, :data => { :confirm => 'Are you sure?' } %>
route.rb
resources :listings do
resources :courses
end
By default, destroy method expects an ID as it's a member route. You are using listing/listingID/courses route without an ID. For that you need to define listing and/or courses as singular resources (read this) like:
resource :courses do
:
end
as described in this answer or make destroy a collection route like so:
resources :courses, except: [:edit] do
delete :destroy, on: :collection
:
rest...
end
Try and see if this works.
By the way, this looks a bit redundant as you are iterating over each #listing.courses and calling courses#destroy where you are destroying all the courses of #listing anyhow. So, why do #listing.courses.each in the first place. You should have either used a listing#destroy_courses method or remove #listing.courses.each iteration.
Update path in link_to to listing_course_path(#listing, #course) from listing_courses_path(#listing, #course)
<%= link_to "Delete Course", listing_course_path(#listing, f), method: :delete %>
I'm building my second-ever basic Ruby on Rails application and having fun doing it, but have gotten stuck at precisely the same place that gave me trouble (and was never solved) on my last effort: the PUT or PATCH request.
My application has two models: entries and users. A logged-in user should be able to edit only those entries that were originally created by that user.
CONTROLLER
class EntriesController < ApplicationController
# authenticate user (Devise)
before_action :authenticate_user!, :except => [:index, :show]
# set entry upon page load
before_action :set_entry, :only => [:show, :edit, :update, :destroy]
# GET request - display all entries
def index
#all_entries = Entry.all
end
# GET request - display an individual entry
def show
# nothing required here because entry identified with before_action :set_entry on line 2 above
end
# GET request - access form to create a new entry
def new
#entry = Entry.new
#user = User.find(current_user[:id])
end
# GET request - access form to update an existing entry
def edit
if #entry[:user_id] != current_user[:id]
redirect_to root_path
else
redirect_to edit_entry_path
end
end
# POST request - make a new entry/save new data into db
def create
user = current_user[:id]
Entry.create({
entry_title: params[:entry][:entry_title],
book_title: params[:entry][:book_title],
text: params[:entry][:text],
img_url: params[:entry][:img_url],
tag: params[:entry][:tag],
created_at: params[:entry][:created_at],
user_id: user
})
redirect_to entries_path
end
# PUT request - save changes to an existing entry
def update
if #entry.update(entry_params)
redirect_to entry_path
else
render :new
end
end
# DELETE request - delete an existing entry from db
def destroy
#entry.destroy
redirect_to entries_path
end
private
def set_entry
#entry = Entry.find(params[:id])
end
def entry_params
params.require(:entry).permit(:email, :text, :tag)
end
end
VIEW (show.html.erb - shows a single entry and includes links allowing the logged-in user who originally authored the entry to edit or delete it)
<h3>Selected Entry</h3>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-6">
<div>Entry title: <%= #entry.entry_title %></div>
<div>Book title: <%= #entry.book_title %></div>
<div>Text: <%= #entry.text %></div>
</div>
<div class="col-md-4">
<div><%= #entry.created_at.strftime("%b %d, %Y") %></div>
<div>Submitted by: <i><%= #entry.user.email %></i></div>
<div>File under: <i><%= #entry.tag %></i></div>
<% if current_user %>
<%= link_to 'Edit', #entry, :method => 'update' %>
<%= link_to 'Delete', #entry, :method => 'delete' %>
<% end %>
</div>
</div>
ROUTES.RB - At first my routes were the commented-out lines, but then I had a thought that was either madness or sudden realization - should only the GET routes lead with "get"? So that's the non-commented-out attempt you see. Somehow the app works (except for the issue at hand) both ways.
In researching I've come across routes defined using a much more elaborate syntax than that I'm using here. I've been unable to figure out whether a given way of doing things is different convention, outdated, or just inadequate to the task.
Rails.application.routes.draw do
devise_for :users
resources :entries
# root 'entries#index'
# get '/entries' => 'entries#index'
# get '/users' => 'users#index'
# get '/entries/:id' => 'entries#show'
# get '/entries/:id' => 'entries#update'
# get '/entries/new' => 'entries#new'
# get '/entries/:id/edit' => 'entries#edit'
# get '/users/:id' => 'users#show'
# get '/about' => 'pages#index'
root 'entries#index'
get '/entries' => 'entries#index'
get '/entries/new' => 'entries#new'
post '/entries' => 'entries#create'
get '/entries/:id' => 'entries#show'
get '/entries/:id/edit' => 'entries#edit'
put '/entries/:id' => 'entries#update'
delete '/entries/:id' => 'entries#destroy'
get '/users' => 'users#index'
get '/users/:id' => 'users#show'
get '/about' => 'pages#index'
end
Thanks in advance for any insight. If additional context is needed I'm happy to provide.
Edited to add:
PARTIAL (_form.html.erb)
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-6" id="form-container">
<%= form_for #entry do |form| %>
<br>
<%= form.text_field :entry_title, :size => 59, :placeholder => "Entry Title"%>
<br><br>
<%= form.text_field :book_title, :size => 59, :placeholder => "Book Title"%>
<br><br>
<%= form.text_field :img_url, :size => 59, :placeholder => "Image URL"%>
<br><br>
<%= form.text_area :text, :placeholder => "Text" %>
<br><br>
<%= form.text_field :tag, :placeholder => "Tag" %>
<br><br>
<%= form.submit %>
<% end %>
</div>
<div class="col-md-4"></div>
</div>
To edit a record you
first, should use a GET request to get the edit form
second, should submit that form using a PUT/PATCH request
To get to the edit form you should link to the edit path for your entry
<%= link_to 'Edit', edit_entry_path(#entry) %>
The Rails form helpers will automatically set the form to submit with the proper method, PUT OR PATCH.
:method in link_to helpers refers to HTML verb (get, post, etc), while controllers methods naming convention is action.
link_to
You need something as
<%= link_to 'Edit', #entry, :method => 'put' %>
or
<%= link_to 'Edit', #entry, :action => 'update' %>
At a glance you are trying to post with the edit link. Remember new/edit are get methods to render form, so just just delete method part in your links. Like from
<%= link_to 'Edit', #entry, :method => 'update' %>
to
<%= link_to 'Edit', edit_entry_path(#entry) %>
I'm building my second-ever basic Ruby on Rails application
Congrats! You need at least 3 more before it all starts to make sense
To add to the existing answers, you'll be best looking at the resources directive to clean the routes up:
#config/routes.rb
root 'entries#index'
devise_for :users
resources :entries
resources :pages, only: [:index], path_names: { index: "about" }
resources :users, only: [:index,:show]
--
A logged-in user should be able to edit only those entries that were originally created by that user.
This is known as authorization.
Authentication = is user logged in?
Authorization = can user do this?
Although people confuse Devise with being able to handle authorization, it only handles authentication. Whilst you have a simple implementation of this in your controller, you should check out either the CanCanCan or Pundit gems:
#Gemfile
gem "cancancan"
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :manage, Post, user_id: user.id
end
end
#app/controllers/entries_controller.rb
class EntriesController < ApplicationController
def edit
authorize! :edit, #entry
end
end
--
Finally, to answer your question directly, you're calling the update method (which doesn't exist) to access the edit view:
<% if current_user %>
<%= link_to 'Edit', #entry, :method => 'update' %>
<%= link_to 'Delete', #entry, :method => 'delete' %>
<% end %>
You should read up about http verbs - this is what the "method" option invokes with the link. As mentioned above, you don't need to set the method for edit as it uses GET. Update uses put/patch, which I can explain later.
A much better way to achieve what you want would be the following:
<%= link_to "Edit", edit_entry_path(#entry) if can? :edit, #entry %>
<%= link_to "Delete", #entry, method: :delete, if can? :destroy, #entry %>
The above uses the CanCanCan authorization method can?
I am busy going through PBP - Agile Web Development with Rails and implementing the locale switcher.
However when I try switch between english and spanish I get a error:
No route matches [POST] "/en"
My controller is as follows:
class StoreController < ApplicationController
skip_before_filter :authorize
def index
if params[:set_locale]
redirect_to store_path(locale: params[:set_locale])
else
#products = Product.order(:title)
#cart = current_cart
end
end
end
and an extract of the application.hmtl.erb that is being used;
<div id="banner">
<%= form_tag store_path, class: 'locale' do %>
<%= select_tag 'set_locale', options_for_select(LANGUAGES, I18n.locale.to_s), onchange: 'this.form.submit()' %>
<%= submit_tag 'submit' %>
<%= javascript_tag "$('.locale input').hide()" %>
<% end %>
<%= image_tag("logo.png") %>
<%= #page_title || t('.title') %>
</div>
the routing folder is as follows:
scope'(:locale)' do
resources :users
resources :orders
resources :line_items
resources :carts
resources :products do
get :who_bought, on: :member
end
root to: 'store#index', as: 'store'
end
Cant figure out what I did wrong. If I enter /en or /es in the url it works correctly. However choosing it in the drop down that is created I get the error mentioned
Found the issue, the form_tag was expecting a POST so I changed
<%= form_tag store_path, class: 'locale' do %>
to
<%= form_tag store_path, class: 'locale', :method => :get do %>
and it worked
You are missing an slash in the scope as guides states:
# config/routes.rb
scope "/:locale" do
resources :books
end
http://guides.rubyonrails.org/i18n.html#setting-the-locale-from-the-url-params
I am trying to finish my forum application. I want arrange user Friendships. I am using devise as authentication system. I want some suggestions.
Shall i nest the resources in User and Friendships. This is the way Railscasts used:
devise_for :users
resources :users, only: [:index, :show]
resources :friendships, only: [:create, :destroy]
This is how i used:
devise_for :users
resources :users, only: [:index, :show] do
resources :friendships, only: [:create, :destroy]
end
My real problem is that . I want to use friendships in a way that , signed_in user can check the users list , and add a friend if he is not in the friendslist. Now i can add a friend multiple times. And also ,user can add himself as a friend.
How can i fix this links with if/else statements:
showing a user profile works:
<section>
<h1>
<%= #user.username %>
</h1>
<%= link_to "Arkadaşlarıma Ekle", friendships_path(:friend_id => #user), :method => :post,class: "btn btn-large btn-primary" %>
</section>
and herei I cant find the way to show the friend's profile. :
<% for friendship in #friendships %>
<%= link_to friendship.friend.username, '#' %>
(<%= link_to 'Sil', friendship, :method => :delete %>)
<% end %>
..
rake routes:
I can understand that , I have to use correct if/elses but i am lost in nesting my resources and routing. . Thanks for explanations..
These are my edits:
In my users_controller:
def user_is_friend?(other_user)
current_user.friendships.find_by_friend_id(other_user.id)
end
<% unless user_is_friend?(#user) %>
<%= button_to "Add friend", friendships_path(:friend_id => user), :method => :post %>
<% end %>
Is it correct?
I would recommend you, in your user index view, when you are looping with a variable user (for #users do |user|) to add first a condition:
<% unless current_user == user %>
--> this will allow you to process all users except the current_user (i.e. yourself)
Then, you can define this:
<% user_is_friend = current_user.friendships.find_by_friend_id(user.id) %>
This will be TRUE if the user is already in your friend's list
And then, a last piece:
<% if !user_is_friend %>
<%= button_to "Add friend", friendships_path(:friend_id => user), :method => :post %>
<% end %>
Not sure why I'm getting
No route matches {:action=>"edit", :controller=>"documents"}
in a link_to in one of my partials
relevant routes.rb
resources :documents, only: [:create, :destroy, :edit, :update] do
post 'sort' => 'documents#sort', on: :collection
end
I recently just added the edit and update actions and thus my current issues
rake routes =
sort_documents POST /documents/sort(.:format) documents#sort
documents POST /documents(.:format) documents#create
edit_document GET /documents/:id/edit(.:format) documents#edit
document PUT /documents/:id(.:format) documents#update
DELETE /documents/:id(.:format) documents#destroy
The partial with the problem route is just
<%= document.title %>
<%= document.position %>
<%= link_to 'link_to_test', edit_document_path %>
<%= link_to 'Delete', document, method: :delete, remote: true %>
my documents_controller.rb has
edit defined
def edit
#document = current_user.documents.find(params[:id])
end
The error occurs because you forgot to specify the object or its id in the edit_document_path. Try this:
<%= link_to 'link_to_test', edit_document_path(document) %>