form_for calls model method without submit - ruby-on-rails

I have an index page for admin part of my project
<% #reviews.each do |review| %>
<p><%= review.header %></p>
<p><%= review.body %></p>
<%= form_for [:admin, review] do |f| %>
<%= f.hidden_field :approve %>
<%= f.submit "Approve" %>
<% end %>
<%= form_for [:admin, review] do |f| %>
<%= f.hidden_field :reject %>
<%= f.submit "Reject" %>
<% end %>
<% end %>
where :approve and :reject are public instance methods in Review model.
For some reason, when I load this index page, it automatically calls review.reject method which sets corresponding is_rejected field to true. Same behavior applies to form_for with :approve if I remove form_for with :reject bit.
index action from corresponding controller is very simple
def index
#reviews = Review.all
end
I realize this must be normal behavior, but what I would expect is to call reject method only when I submit corresponding form. Is there a way to fix it? Thank you.
UPDATE
Just for the future reference (including my own): it is easier to use button_to helper for things like that
<%= button_to "Approve", { :action => "update", :id => review.id, :review => { :approve => true } }, :method => :put %>
<%= button_to "Reject", { :action => "update", :id => review.id, :review => { :reject => true } }, :method => :put %>

The form builder is calling approve and reject on your model because it's trying to determine what to set the value for the hidden fields to. One way around this would be to not use f.hidden_field and just create a hidden field that's not tied to your model. You can use hidden_field_tag instead.

Related

Rails route can't expand model object to get ID

I have a form in one of my views that looks like this:
<% form_for #user, url_for(:controller => :users, :action => :admin_edit, :id => #user.id) do |f| %>
<%= f.select 'Confirmed', [['Yes', true], ['No', false]] %>
<%= f.submit 'Update' %>
<% end %>
The first line of it is raising a TypeError: no implicit conversion of Symbol into Integer. I think this is because the route (below) needs an ID, but isn't finding the ID it needs.
The users#admin_edit method is currently an empty definition. The route that points to it is:
post 'admin/users/:id/edit', to: 'users#admin_edit'
The #user object in question is set using
#user = User.find(params[:id])
so the :id attribute is definitely set; I've verified this with the console.
Given that I've passed the ID explicitly to url_for, why can the route not expand the #user object to get its ID out? Or can it, and the issue is somewhere else entirely?
Try this:
<%= form_for #user, url: {:action => :admin_edit} do |f| %>
<%= f.select 'Confirmed', [['Yes', true], ['No', false]] %>
<%= f.submit 'Update' %>
<% end %>
If you have set your routes as:
post 'admin/users/:id/edit', to: 'users#admin_edit'
Then your form could be as follows:
<%= form_for #user, url: {:controller => ::users, :action => :admin_edit, :id => #user.id}, method: :post do |f| %>
<%= f.select 'Confirmed', [['Yes', true], ['No', false]] %>
<%= f.submit 'Update' %>
<% end %>
Then you can get the id in your UsersController could look as follows:
def admin_edit
#user = User.find_by_id(params[:id])
end

Creating a form that works for both creating and updating a model

I'm creating a form where a user can input their favorite food. I want the form to work for both an existing favorite food, and a new favorite food.
I thought I could just switch what object the form is being created for like this:
<% if #user.favorite_food %>
<%= form_for #user.favorite_food, :html => { class: :form } do |f| %>
<% else %>
<%= form_for :favorite_food, :url => :favorite_food, :html => { class: :form } do |f| %>
<% end %>
However I get an error syntax error, unexpected keyword_else, expecting keyword_end.
I agree with Mayank comment your form should be like this
<% if #user.favorite_food %>
<%= form_for #user.favorite_food, :html => { class: :form } do |f| %>
<% end %>
<% else %>
<%= form_for :favorite_food, :url => :favorite_food, :html => { class: :form } do |f| %>
<% end %>
<% end %>
You would better use a nested form for this and let the UsersController take care of the update:
<%= form_for #user, :html => { class: :form } do |f| %>
<%= f.fields_for :favourite_food do |food_form| %>
<% # your favorite_food inputs go here %>
<% end %>
<% end %>
In your User class you have to have accepts_nested_attributes_for:
class User
accepts_nested_attributes_for :favorite_food
...
end
In your UserController you need to make sure to permit the favorite_food_attribures on your strong parametes:
params.require(:person).permit(favorite_food_attribures: []) #do not delete the existing parameters that are already there.
You could check this answer for more info.
Hope this can help you.

Passing model id through rails route in form

I'm calling a custom action with simple_form. I'm having trouble passing the :id parameter to the action.
routes
post '/posts/:id/admin_vote' => 'posts#admin_vote', as: 'admin_vote'
form
<%= simple_form_for :post, url: admin_vote_path(:post_id), :html => {:class => 'form-inline admin-vote-form'} do |f| %>
<%= f.select :vote, 1..20 %>
<%= f.submit 'Vote', :class => 'btn btn-primary btn-xs' %>
<% end %>
partial render
<%= render 'layouts/admin_vote', :locals => { :post => post, :post_id => post.id } %>
For some reason the action receives params[:id] = 'post_id' instead of the actual id.
You're providing :post_id symbol to the admin_vote_path, so it uses that. Change it to:
admin_vote_path(params[:post_id])
or a different parameter depending on the context of your form.
it seems to me your form should be
<%= simple_form_for post, url: admin_vote_path, :html => {:class => 'form-inline admin-vote-form'} do |f| %>
<%= f.select :vote, 1..20 %>
<%= f.submit 'Vote', :class => 'btn btn-primary btn-xs' %>
<% end %>
you should build form based on variable post

How to fragment cache "like" buttons ? (Rails 3.2, memcached)

My Rails app has Post and Member models. Within posts/:id/show contains a "like" button which Members can click, or "Unlike" if #member has already "liked" this #post already.
(This button will link to a post action that does some ajax and makes the "like" button change into a "unlike" button)
Whats the best practice for caching the button? (below code obviously doesn't cache the button html).
Should I add :touch => true to member.rb, and then make a cache key for the button e.g. <% cache ['V1', #post, #member, 'like_button'] ? (seems redundant?)
post.rb
has_many :likes
like.rb
belongs_to :member
belongs_to :post
member.rb
has_many :likes
*posts/show.html.erb *
<% cache ['V1', #post, 'show'] do %>
<h1>#post.title</h1>
<div class="content">#post.content</div>
<% end %>
<%= render 'like_button', :post=> #post, :member => #member %>
** posts/_like_button.html.erb **
<% if member.liked_post?(post) %>
<%= link_to unlike_post_path(post), :method => :post, :remote => true, :class => 'btn' %>
<% else %>
<%= link_to like_post_path(post), :method => :post, :remote => true, :class => 'btn' %>
<% end %>
You can do something along these lines:
<% cache ['V1', #post, #member.liked_post?(#post), 'show'] do %>
<h1>#post.title</h1>
<div class="content">#post.content</div>
<%= render 'like_button', :post=> #post, :member => #member %>
<% end %>
This gives your 2 different cached versions of the fragment - one each for the 'liked' and 'not liked' states. This is better than 1 version per user.
YOu run the risk here of someone adding code to the like_button partial that uses more of the #member parameter, and that isn't part of the cache key, so you'll get incorrect results.
For this case, I'd change the like_button partial to take the same parameter as the cache call - #member.liked_post(#post) -- to make it clear that this is the only value used inside the partial code.
<%= render 'like_button', :post=> #post, :liked => #member.liked(#post) %>
With the new partial:
<% if liked %>
<%= link_to unlike_post_path(post), :method => :post, :remote => true, :class => 'btn' %>
<% else %>
<%= link_to like_post_path(post), :method => :post, :remote => true, :class => 'btn' %>
<% end %>

Difference between :model and #model in form_for?

What is the difference between using form_for the following way:
<% form_for #user do |f| %>
<%= f.label :name %>:
<%= f.text_field :name, :size => 40 %>
...
<% end %>
and:
<% form_for :user, :url => {:action => 'create'} do |f| %>
<%= f.label :name %>:
<%= f.text_field :name, :size => 40 %>
...
<% end %>
Does using #user just automatically use CRUD methods for the URL actions?
If you just give a model instance like #user without specifying an action (as in your first example), Rails automatically uses the appropriate CRUD action for your form:
If #user is a new, unsaved User object, the form will point to your create action.
If #user is an existing User loaded from the database, the update action will be used instead.
This has the advantage that you can reuse the same form for both your edit and new views without changing the :url parameter for your forms.
As usual, the API docs provide more information.
If you give form_for a symbol without an instance variable it looks for an instance variable with the same name.
The documentation says:
For example, if #post is an existing
record you want to edit
<% form_for #post do |f| %>
...
<% end %>
is equivalent to something like:
<% form_for :post, #post, :url => post_path(#post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
...
<% end %>

Resources