How to fragment cache "like" buttons ? (Rails 3.2, memcached) - ruby-on-rails

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 %>

Related

Ajax not rendering the partial in rails application

Ajax partial is not rendering in my rails application.
I am having an Article model. I am trying to add new articles using Ajax
Please find the code below.
cart.html.erb
<div id="articles">
<%= render 'showarticles' %>
</div>
cart.js.erb
$('#articles').html('<%= escape_javascript(render('static_pages/showarticles')) %>');
$('.form-control').val('')
routes.rb
match 'cart/:id', to: 'static_pages#cart', via: [:get, :post]
_showarticles.html.erb
<%= form_for #article, :remote => true do |f| %>
<%= f.text_area :name, :class => "form-control" %>
<% end %>
<% #art.each do |a| %>
<%= a.name %>
<% end %>
static_pages_controller.rb
def cart
#art = Article.all
#article = Article.new
respond_to do |format|
format.html
format.js
end
end
Thanks!
I was able to fix the issue by adding this line to my routes.rb file:
match 'cart', to: 'static_pages#cart', via: [:get, :post]
Ok,
so this
<%= form_for #article, :remote => true do |f| %>
<%= f.text_area :name, :class => "form-control" %>
<% end %>
<% #art.each do |a| %>
<%= a.name %>
<% end %>
Will go to the articles create method. Which is why your cart.js.erb isn't called. Either set the url <%= form_for #article, :url => url_for(:controller => 'static_pages, :action => 'cart', :id => #cart.id), :remote => true do |f| %> or preferably handle it in the create action of your articles controller

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

form_for calls model method without submit

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.

Rails3 display form_for form gives routing error if on other erb page

I have a'Remove' button in a show erb of the trackers_controller.show:
<%= form_for :user_tracker, :url => user_tracker_path,:method => :delete do |f| %>
<%= f.hidden_field :tracker_id, :value => #tracker.id %>
<%= f.submit :save, :value => 'Remove' %>
<% end %>
This works fine and calls user_trackers_controller.destroy
The User models looks like:
has_many :user_trackers
has_many :trackers, :through => :user_trackers
If I put the exact same button in another erb I get this error:
No route matches {:action=>"show", :controller=>"user_trackers"}
I have a few different combinations like :html => {:method => :delete }
If I try it this way
<% current_user.user_trackers.each do |user_tracker| %>
<%= user_tracker.tracker %>
<%= form_for user_tracker, :method => :delete do |f| %>
<%= f.submit :delete, :value => 'Remove' %>
<% end %>
I get the same routing error
Here are the routes:
user_trackers GET /user_trackers(.:format) user_trackers#index
POST /user_trackers(.:format) user_trackers#create
new_user_tracker GET /user_trackers/new(.:format) user_trackers#new
edit_user_tracker GET /user_trackers/:id/edit(.:format) user_trackers#edit
user_tracker GET /user_trackers/:id(.:format) user_trackers#show
PUT /user_trackers/:id(.:format) user_trackers#update
DELETE /user_trackers/:id(.:format) user_trackers#destroy
I do not understand, why will it not pick up that this is a destroy when in an unrelated erb?
Edit:
This is one seems to work but the html generates ids and classes like edit_user_tracker_7 but also the javascript to make it a delete so it seems I still have something wrong:
<%= form_for user_tracker, :url => user_tracker_path(user_tracker), :method => :delete do |f| %>
<%= f.hidden_field :tracker_id, :value => user_tracker.tracker_id %>
<%= f.submit :delete, :value => 'Remove from my portfolio' %>
<% end %>
in first line it should be
:url => user_tracker_path(#user)
Its not a bug, he just have to know who to remove :) so he needs id (in REST).
Also :method should be in :html
:html => { :method => :delete }
or
html: { method: "delete" }
with 1.9+ notation.
full form_for
user_tracker_path(#user), :html => {:method => :delete} do |f| %>
ofc if in your case #user is current_user then you have to swap it :)
Sorry for typos & english i'm not native :)
Cheers!

Rails tries to execute Update action instead of the one I want

I want to develop an ajax functionality for commenting posts in my website.
I've done this before, but I don't know why I'm having problems this time. Rails executes Update action from posts_controller, instead of the action called "save_comment".
This is the relevant line of my routes file:
map.connect "/posts/save_comment", :controller => 'posts', :action => 'save_comment'
This is the view's code:
<%= javascript_include_tag "prototype" %>
<% if logged_in? %>
<% remote_form_for :post, PostComment.new, :url => {:action => 'save_comment',:post_id=>inside_list.id}, :html => { :method => :put} do |f| %>
<p>
<%= f.label 'Comment' %><br />
<%= f.text_area :comment, :style=>'height:100px;' %>
</p>
<p>
<%= f.submit 'Publish' %>
</p>
<% end %>
<% end %>
The save_comment action looks like this:
def save_comment
comment = PostComment.new
comment.user_id = current_user.id
comment.post_id = params[:post_id]
comment.comment = params[:post][:comment]
comment.save
post = Post.find(params[:post_id])
render :update do |page|
page.replace_html 'dComments', :partial => 'post_comments/inside_list', :object => post
end
end
BTW: Is there a neater way for doing this?
You need to define the route method. Also you're not defining the post parameter.
map.connect "/posts/:post_id/save_comment", :controller => 'posts', :action => 'save_comment', :method => :post
Following convention you should make the route method => :post, rather than :put. Put requests are generally used for updating existing records, post for creating new. Also how about named routes?
#routes.rb
map.save_comment "/posts/:post_id/save_comment", :controller => 'posts', :action => 'save_comment', :method => :post
#view
<% remote_form_for :post, PostComment.new, :url => save_comment_path(inside_list.id) do |f| %>
Also, guessing here but do you have this defined:
map.resources :posts
if you do then add the new method
map.resources :posts, :member => {:save_comment => :post}

Resources