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! :)
Related
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
I have an app where users can ask questions and bookmark certain questions. I'm done with the users, questions, and answers, so I've added a BookmarkController & Bookmarks model. At first, I considered using associations, but my app has a few associations already so I'm (or I've attempted at) using query parameters such as user_id and question_id to fetch bookmarks.
The structure is a bit like StackOverflow. A user navigates to a single question view and bookmarks it on that page. This creates a new bookmark model containing the user_id of current_user and the question_id. The user can go to his profile to view all the questions he bookmarked, fetched using his user_id. (Answers cannot be bookmarked. Only questions.)
I've been getting a 'param is missing or the value is empty: bookmark' error, although I have followed similar steps I did for my QuestionsController. It would be great if someone could help me out in identifying what's wrong/bad about my code!
rake routes (first part omitted)
bookmark_question PUT /questions/:id/bookmark(.:format) questions#bookmark
questions GET /questions(.:format) questions#index
POST /questions(.:format) questions#create
new_question GET /questions/new(.:format) questions#new
edit_question GET /questions/:id/edit(.:format) questions#edit
question GET /questions/:id(.:format) questions#show
PATCH /questions/:id(.:format) questions#update
PUT /questions/:id(.:format) questions#update
DELETE /questions/:id(.:format) questions#destroy
route.rb (excerpt)
# Questions
get '/questions/:id' => 'bookmarks#create'
show.html.erb (questions#show)
<% if current_user %>
<%= link_to "Bookmark", :controller => 'bookmarks', :action => 'create' %>
<% end %>
BookmarksController
class BookmarksController < ApplicationController
def new
#bookmark = Bookmark.new
end
def create
#question = Question.find(params[:id]) # when I delete this line, I get a new error - "undefined local variable 'params'"
#bookmark = Bookmark.new(bookmark_params)
#bookmark.user_id = current_user.id
#bookmark.question_id = #question.id
#bookmark.save
redirect_to #question
end
def destroy
end
private
def bookmark_params
params.require(:bookmark).permit(:user_id, :question_id)
end
end
Bookmark model
class Bookmark < ApplicationRecord
validates :user_id, presence: true
validates :question_id, presence: true
end
QuestionsController
(at the moment, contains no reference to Bookmarks. I thought so because I did the routing, but this might be where I'm going wrong)
class QuestionsController < ApplicationController
def index
#questions = Question.all
end
def show
#question = Question.find(params[:id])
#answers = Answer.all
# Delete only appears when no answers
#deletable = (current_user== User.find(#question.user_id)) && (#question.answers.all.size==0)
end
def new
#question = Question.new
end
def create
if logged_in?
#question = Question.new(question_params)
#question.user_id = current_user.id
#question.save
redirect_to #question
else
redirect_to login_path
end
end
def destroy
#question = Question.find(params[:id])
#question.destroy
redirect_to root_path
end
private
def question_params
params.require(:question).permit(:picture_url, :country, :educational_level, :topic)
end
end
profile index.html.erb (just for ref)
<% if (#bookmarks.count == 0) %>
///
<% else %>
<%= #bookmarks.each do |bookmark| %>
<!-- Show bookmark content here like Question.find(bookmark.question_id) etc -->
<% end %>
<% end %>
I have looked a the previous qns that have the same error as me. But they were all using associations. I hope to not use associations as the bookmark model only needs to keep a record of the user id and qn id.
UPDATE
So, referring to the answers given, I updated my erb to:
<% if logged_in? %>
<%= link_to "Bookmark", :controller => 'bookmarks', :action => 'create', bookmark: {user_id: current_user.id, question_id: #question.id} %>
<% end %>
hence specifying the controller and action (and the params) that need to be directed. But rails sends an error:
No route matches {:action=>"create", :bookmark=>{:user_id=>2, :question_id=>4}, :controller=>"bookmarks", :id=>"4"}
So I assume it was a routing problem. As Pavan suggested, I did consider nesting my resources, but the nesting is already one level deep, as such:
resources :questions do
resources :answers
end
And I reckon doing something like:
resources :questions do
resources :bookmarks # or resources :bookmarks, only: create
resources :answers
end
won't work. (And it didn't :( )
I'm not so sure how to get this routing problem fixed (tried Googling). Thanks.
param is missing or the value is empty: bookmark
The reason for the error is bookmark_params expects a :bookmark key to be present in the params hash, which in your case is missing since you are not passing any.
Change link_to like below:
<% if current_user %>
<%= link_to "Bookmark", :controller => 'bookmarks', :action => 'create', bookmark: {user_id: current_user.id, question_id: #question.id} %>
<% end %>
Also, the route get '/questions/:id' => 'bookmarks#create' isn't right and would conflict with this route question GET /questions/:id(.:format) questions#show. I would instead recommend building nested routes
resources :users do
resources :questions do
resources :bookmarks, only: [:create]
end
end
Update:
Along with the above, you should change #question = Question.find(params[:id]) to #question = Question.find(params[:bookmark][:question_id])
'param is missing or the value is empty: bookmark, this error means that, there is no bookmark key present in your params object, but you defined your bookmark_params to have one:
def bookmark_params
params.require(:bookmark).permit(:user_id, :question_id)
end
That's why it's throwing the above error message.
You should make sure you send the user_id and question_id key/value pairs under the bookmark key. Something like this:
bookmark: { user_id: 1, question_id: 2}.
So, your code should look something like this (adding the bookmark to params):
<%= link_to "Bookmark", :controller => 'bookmarks', :action => 'create', bookmark: {user_id: current_user.id, question_id: #question.id} %>
The next comment button should be directed to http://localhost:3000/articles/14/comments/70.
What it currently executes is: http://localhost:3000/articles/14/comments/56/next_comment
How does one fix this?
#button
= link_to "next comment", next_comment_article_comment_path(#article, #comment)
#controller
class CommentsController < ApplicationController
def next_comment
#comment ||= #scope.next(#comment)
end
def scope
#scope ||= #article.comments
end
end
UPDATE
#routes.rb
resources :articles do
resources :comments do
member do
get 'next_comment'
end
end
end
Run rake routes and check if there is a route to next_comment_article_comment_path(#article, #comment)
And try whith: article_comment_next_comment_path(#article, #comment)
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.
I would like to perform this action with the click of a link.
def index
#books = Book.all
end
def update_read_books
#books.each do |book|
book.update_attribute(:read, true)
end
end
How can I update mark all books as read?
Rails has an update_all method. See link.
def mark_as_read
Book.update_all(read: true)
redirect_to books_path
end
Setup up route to give you /books/mark_as_read
resources :books do
get :mark_as_read, on: :collection
end
Then in your view:
= link_to "Mark all as Read", mark_as_read_books_path
If you really want to be Restful, you can make your route a put/patch method. Don't forget to change your link to the method you choose.
If you want this to be an Ajax request, you can add a remote: true to the link:
= link_to "Mark all as Read", mark_as_read_books_path, remote: true
That will make it async. Then you need to handle that response in the controller.
def mark_as_read
Book.update_all(read: true)
respond_to do |format|
format.html { redirect_to books_path }
format.js
end
end
... and add a template inside /views/books/update_all.js.erb and add some jQuery to remove the notification. For example:
$('#notification_count').hide();
First of all define your method outside index method.
def index
#books = Book.all
end
def update_read_books
Book.update_all(read: true)
end
define route:
resources :books do
put :update_read_books, on: :collection
end
Then in your view:
= form_for update_read_books ,:remote => true do |f|
= f.submit "All Read"
Try with this. Hope it will help.