Rails 3, changing a specific value in a row - ruby-on-rails

This question seems ridiculously easy, but I seem to be stuck.
Lets say we have a table "Books"
Each Book, has a name, description, and a status.
Lets say I want to create link in the show view, that when clicked, solely changes the status (to "read") for example.
So far, I've tried adding a block in the controller, that says:
def read
#book = Book.find(params[:id])
#book.status = "Read"
#book.update_attributes(params[:book])
respond_to do |format|
format.html { redirect_to :back}
format.xml { render :xml => #book }
end
end
Then I've added a link to the view that is like:
<%= link_to "Read", read_book_path(#book), :method => :put %>
This isn't working at all. I have added it to my routes, but it doesn't seem to matter.
Any help would be great! Thanks!
-Elliot
EDIT: Forgot to add I'm getting a NoMethodError: undefined method `read_book_path'

You need to define a method called read:
def read
#book = Book.find(params[:id])
#book.status = "Read"
#book.save
end
Then you want <%= link_to "Read", read_book_path(#book), :remote => true %>
and in routes.rb:
resources :books do
member do
get 'read'
end
end
Once that works, mess around with changing the method to :put

If all you're trying to do is set the status attribute on a book and not update any other parameters, then calling update_attributes is unnecessary.
change:
#book.update_attributes(params[:book])
to:
#book.save!

Related

Two controllers, one model -- why is my app looking for a second model?

I have an Order model.
Customers get a handful of consumer-friendly views that let them create and view their own orders, all backed by an Orders controller.
Admins get the full range of views to create, edit, view, delete and manage orders, backed by a Purchases controller.
As far as I can tell, the Purchases controller should only be speaking to the Order model, but the following error message makes me think it's looking for a non-existant Purchase model:
ActiveRecord::StatementInvalid in PurchasesController#new
NameError in PurchasesController#new
uninitialized constant Purchase
Rails.root: /Users/steven/Dropbox/testivate
Is that what the error means? If it does, how do I stop the Purchases controller from trying to find a Purchase model?
My code...
app/controllers/purchases_controller.rb:
class PurchasesController < ApplicationController
def new
#purchase = Order.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #purchase }
end
end
def create
#purchase = Order.new(params[:purchase])
respond_to do |format|
if #purchase.save
format.html { redirect_to purchase_path(#purchase), notice: 'Purchase was successfully created.' }
format.json { render json: #purchase, status: :created, location: #purchase }
else
format.html { render action: "new" }
format.json { render json: #purchase.errors, status: :unprocessable_entity }
end
end
end
end
/config/routes.rb:
Testivate::Application.routes.draw do
resources :orders
resources :purchases
end
/app/views/purchases/new.html.haml:
.textbox
%p#notice= notice
%h1 New Purchase
= render 'form'
= link_to 'List Purchases', purchases_path
/app/views/purchases/_form.html.haml:
= simple_form_for #purchase do |f|
= f.error_notification
= f.input :name
= f.button :submit
Update: so I've just realised that 'transaction' is a reserved word in Rails so I've changed that. But is there anything else I need to fix?
*Update 2: When I completely comment out the #new view and the _form, I still get the error, so I think the problem is in my controller or routes or somewhere other than with my use of simple_form.*
Update:
This is half of the answer. For the other half see the comments on the question.
Original answer:
Part of the problem here is that by default form_for (which simple_form_for is built atop) assumes certain things about what paths to use for a given record, which it derives from the class of that record. In your case, since #purchase is actually an instance of the Order class, those assumptions are going to screw things up for you.
If you look at the form generated by simple_form_for, you'll see that it gives the form both an id and a class of new_order, and that the fields in the form have names like order[name]. To get rails to use purchase instead of order in the form, you have to pass it an :as option.
From the docs for form_for:
If you have an object that needs to be represented as a different parameter, like a Person that acts as a Client:
And the example they give:
<%= form_for(#person, :as => :client) do |f| %>
...
<% end %>
In your case, you'd actually want to tell it to treat #purchase like a "purchase":
= simple_form_for #purchase, :as => :purchase do |f|
= f.error_notification
= f.input :name
= f.button :submit
That will get the form ids, etc. right, so that you can use e.g. params[:purchase] in your create action as you have above. But this is not enough, because the action (URL) for the form will still end up being /orders. To change that, add an :url option:
= simple_form_for #purchase, :as => :purchase, :url => purchases_path(#purchase) do |f|
= f.error_notification
= f.input :name
= f.button :submit
This will also solve the other question you posted.

How can I call an action in application_controller.rb, without having a corresponding view file?

I am trying click on a link to call this action.
<% link_to "popup", :action => 'user_logs_out', :controller => 'application'%>
In my application controller I have this action:
def user_logs_out
gon.display_sign_out_popup = true
end
How can I call this action? It always shows template error. Since the action simply needs to set a boolean value, I don't feel a need to have view file for it.
If you really want an action to render nothing, you can use render :nothing => true.
def user_logs_out
display_sign_out_popup = true
render :nothing => true
end
However, there is almost certainly no reason why you would want to do this.
You have to put
def user_logs_out
display_sign_out_popup = true
respond_to do |format|
format.js
end
end
You also must put :remote => true in your link_to
...and if you are trying to learn authentication from scratch I would recommend Hartl's free tutorial: http://ruby.railstutorial.org/book/ruby-on-rails-tutorial?version=3.2#cha-modeling_users

Rendering new form for a nested Ruby on Rails Resource

In my Ruby on Rails application, each group has_many :expenses. I have nested my routes, so expenses are entered only as child entities of their parent groups. Here's an excerpt from routes.rb.
resources :groups do
resources :expenses
end
I cannot figure out how to render the 'new' action in the case of an expense not saving when it is submitted through /groups/:group_id/expenses/new. In my expenses_controller.rb, here is how the create action is defined:
def create
#expense = Expense.new(params[:expense])
#expense.group_id = params[:group_id]
if #expense.save
redirect_to group_expense_path(#expense.group.id, #expense.id)
else
render 'new'
end
end
Everything works fine if I satisty expense validation and #expense.save winds up working. However, when it fails and the code tries to render 'new' I get:
undefined method `expenses_path' for #<#<Class:0x007fd408b1fd58>:0x007fd408f21ca8>
So, I am assuming I have something about my nested routing wrong. How do I return the user to the new form but still display to him/her through the flash[] params the errors with the data they originally attempted to submit?
The problem is that #group is not initialized
So in your controller just do
#expense = Expense.new(params[:expense])
#group = Group.find(params[:group_id])
#expense.group_id = #group.id
Looks like you need to explicitly specify the url for form_for in your view.
Something likeā€¦
<%= form_for #expense, :url => group_expenses_path(#group.id) do |f| %>
...
<% end %>
In your <%= form_for %> you have used #group for url, because expenses belongs_to groups. But inside your create action in the controller you have not defined what is #group, so first you should define it as:
#expense = Expense.new(params[:expense])
#group = Group.find(params[:group_id])
#expense.group_id = #group.id
Also I would suggest to use respond_to in your controller:
respond_to do |format|
if #expense.save
format.html { redirect_to group_expense_path(#group.id, #expense.id), :notice => "Any msg you want" }
else
format.html { render :action => "new" }
end
end
All of these are in your create action inside the controller.
Also for different rendering methods look up: http://guides.rubyonrails.org/layouts_and_rendering.html
Hope this helps!

How to stay at same url when validation fails

I'm using Ruby on Rails 2.3.8 and I've got a registration form in which I receive a parameter as follows: /registration/4, which 4 is the id of a user who recommended the user that is about to register in the website.
The problem is that if the validation fails when the user submits the registation (the form renders to the controller users, action create_particular) the site will redirect to /users/create_particular, and therefore I lose the parameter with value 4 that I had before. Besides, I want the user to stay at the same url, which is /registration/4
How can I do that?
Then you should rewrite your create method. You should use redirect_to :back instead of render :action
UPD
def new
#word = Word.new(params[:word])
#word.valid? if params[:word]
end
def create
#word = Word.new(params[:word])
if #word.save
redirect_to #word
else
redirect_to new_word_path(:word => params[:word] )
end
end
Looks quite dirty, but this is just a scratch
UPD 2
This is really not the best solution, but it works
# routes.rb
match 'words/new' => 'words#create', :via => :post, :as => :create_word
# words_controller
def new
#word = Word.new
end
def create
#word = Word.new(params[:word])
respond_to do |format|
if #word.save
format.html { redirect_to(#word, :notice => 'Word was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
# views/words/new.html.erb
<%= form_for(#word, :url => create_word_path) do |f| %>
...
<% end %>
Submit to the current URI (e.g. action=""). When the submission is valid, redirect. POST->Redirect->GET is a good habit.
From the top of my head:
Edit your controller (registrations_controller.rb file). Create method by default contains following piece of code:
if #registration.save
format.html { }
format.xml { }
else
format.html { }
format.xml { }
end
Add redirect_to (:back) between brackets to else format.html{}
Ok I solved the problem by doing the following:
1) I created two routes with the same path, but with different conditions method (one it's post and the other one is set to get)
2) I changed the form in order to post to the POST action defined above
3) I added render => :my_action when the validation fails
So that's pretty much it.
Thanks anyway for all your help.
Hidden field. That user ID param has a name by which you extract it in your controller, right? So just put that value in a hidden field of the same name, then it will survive a round-trip.
For example:
<%= hidden_field_tag :referring_user_id, params[:referring_user_id] %>

Passing data via instance variable from the controller to the view, what am I doing wrong?

I'm trying to write my first custom controller action and am running into a NoMethodError.
The scenario: PostsController, with the standard 7 actions scaffolding generates.
I want the index page to show a single post instead of displaying a list of them. Each post has a "visible" checkbox. I created a named_scope called visible in the Post model with:
named_scope :visible, :conditions => ["visible =?", true]
and applied it along with .last to the index action in my controller:
def index
#post = Post.visible.last
(format stuff)
end
Edited my index view to remove the iterating over an array part so it just shows #post.title, etc. So now my index just displays the last record in the Posts table with True on visible.
I then want to create an all posts archive page, that shows all of them regardless of visibility, so I created a new controller action:
(edited for clarity)
def archives
render :layout => 'posts'
#posts = Post.find(:all)
respond_to do |format|
format.html
format.xml { render :xml => #posts }
end
end
created a new named route:
map.archives 'posts/archives', :controller => 'posts', :action => 'archives'
and a view named archives.html.erb underneath views/posts.
Archives.html.erb looks exactly like the standard index.html.erb template that scaffolding creates, with a
<% for post in #posts %>
<tr>
<td><%=h post.title %></td>
...
</tr>
<% end %>
When I view this page in my browser, I get an error of
NoMethodError in Posts#archives
You have a nil object when you didn't expect it, you might have expected an isntance of Array, the error occurred while evaluating nil.each,
pointing to the line in the view (app/views/posts/archives.html.erb) that says
<% for post in #posts %>
so my understanding is that the data from the instance variable in the controller action archives isn't passing into the view, so instead of an array of posts, I've got nil. I'm looking for a reason why, and what it is I'm doing wrong.
(Of course, if I put
<% #posts = Post.find(:all) %>
before the
<% for post in #posts %>
in the view, then it works just fine, but I know this is not what I should do.
All help, links to tutorials to straighten me out, etc is much appreciated.
(I'm doubtful it matters with something this simple, but I'm using Rails 2.2.2 and Ruby 1.8.6.)
This is correct:
respond_to do |format|
format.html { render :layout => 'posts' }
format.xml { render :xml => #posts }
end
end
#posts was not recognized by the view because it was instantiated after you rendered the view.
'render' should be the last executed line in an action.

Resources