I have a view that is iterating through a series of posts, and is running a count on the votes for each. I know how to do eager loading generally and how to use cache counters generally. But I can't figure out how to use the cache counter that comes with acts_as_votable (or I'm doing something else wrong.)
View:
<span class="votes">
<% if current_user.voted_for? link %>
<%= link_to like_link_path(link), class: "likes active", id: "link-#{link.id}", remote: true, method: :put do %>
<i class="fa fa-heart"></i> <%= link.cached_votes_total.to_s %>
<% end %>
<% else %>
<%= link_to like_link_path(link), class: "likes", id: "link-#{link.id}", remote: true, method: :put do %>
<i class="fa fa-heart-o"></i> <%= link.cached_votes_total.to_s %>
<% end %>
<% end %>
</span>
Terminal:
Link Load (0.4ms) SELECT "links".* FROM "links" ORDER BY "links"."score" DESC LIMIT 10 OFFSET 0
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (1)
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
(0.2ms) SELECT COUNT(*) FROM "votes" WHERE "votes"."voter_id" = $1 AND "votes"."voter_type" = $2 AND "votes"."votable_id" = $3 AND "votes"."votable_type" = $4 AND "votes"."vote_scope" IS NULL [["voter_id", 1], ["voter_type", "User"], ["votable_id", 1], ["votable_type", "Link"]]
(0.2ms) SELECT COUNT(*) FROM "votes" WHERE "votes"."voter_id" = $1 AND "votes"."voter_type" = $2 AND "votes"."votable_id" = $3 AND "votes"."votable_type" = $4 AND "votes"."vote_scope" IS NULL [["voter_id", 1], ["voter_type", "User"], ["votable_id", 2], ["votable_type", "Link"]]
Rendered home/index.html.erb within layouts/application (24.6ms)
Completed 200 OK in 60ms (Views: 56.9ms | ActiveRecord: 1.6ms)
Thanks!
It looked nothing abnormal to me. The voted_for? method is alias of voted_on?. Keep on reading the code you will find this method calls a votes.size > 0, which will trigger following queries to database. (I guess you have 2 links on your page, right?) So it has nothing to do with the cached counts fields.
(0.2ms) SELECT COUNT(*) FROM "votes" WHERE "votes"."voter_id" = $1 AND "votes"."voter_type" = $2 AND "votes"."votable_id" = $3 AND "votes"."votable_type" = $4 AND "votes"."vote_scope" IS NULL [["voter_id", 1], ["voter_type", "User"], ["votable_id", 1], ["votable_type", "Link"]]
(0.2ms) SELECT COUNT(*) FROM "votes" WHERE "votes"."voter_id" = $1 AND "votes"."voter_type" = $2 AND "votes"."votable_id" = $3 AND "votes"."votable_type" = $4 AND "votes"."vote_scope" IS NULL [["voter_id", 1], ["voter_type", "User"], ["votable_id", 2], ["votable_type", "Link"]]
P.S. The code is here: github: voter.rb.
May be that is not the best way, but it could help to preload user's votes for links in single database hit:
Somewhere in backend (e.g. User model) you may define method like
# votable - records, which were voted by user, links in the case
def preloaded_votes(votable)
#preloaded_votes ||= Vote.where(voter: self, votable: votable).group(:votable_id).count
end
this method will return hash like {votable_id1 => count1, votable_id2 => count2 }, where votable_idN means id of an element from votable argument for the method.
Change current call current_user.voted_for? link to preloaded counts current_user.preloaded_votes.key?(link.id)
This solution adds one more database hit, but allow to avoid N+1 queries. Also, additional SQL does not contain any tables joins. One possible way to improve here - add special index for this kind of queries.
Related
Just added a new comment feature to my rails app and stumbled upon this issue. I'm getting a NoMethodError in Pics#show which comes from my _comment.html.haml partial.
I can see the comment thread on my show page but after I post the comment I'm getting this. Can go back to uncommented pics, but can't go to the ones I've already commented on.
I've been staring at the code for a while now and I need a fresh pair of eyes.
Started GET "/pics/14" for 127.0.0.1 at 2018-12-19 23:44:51 +0000
(0.5ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
↳ /Users/alexfurtuna/.rvm/gems/ruby-2.4.0/gems/activerecord-5.2.2/lib/active_record/log_subscriber.rb:98
Processing by PicsController#show as HTML
Parameters: {"id"=>"14"}
Pic Load (0.4ms) SELECT "pics".* FROM "pics" WHERE "pics"."id" = $1 LIMIT $2 [["id", 14], ["LIMIT", 1]]
↳ app/controllers/pics_controller.rb:53
CACHE Pic Load (0.0ms) SELECT "pics".* FROM "pics" WHERE "pics"."id" = $1 LIMIT $2 [["id", 14], ["LIMIT", 1]]
↳ app/controllers/pics_controller.rb:9
Rendering pics/show.html.haml within layouts/application
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/views/pics/show.html.haml:14
(0.7ms) SELECT COUNT(*) FROM "votes" WHERE "votes"."votable_id" = $1 AND "votes"."votable_type" = $2 AND "votes"."vote_flag" = $3 [["votable_id", 14], ["votable_type", "Pic"], ["vote_flag", true]]
↳ app/views/pics/show.html.haml:20
Comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."commentable_id" = $1 AND "comments"."commentable_type" = $2 [["commentable_id", 14], ["commentable_type", "Pic"]]
↳ app/views/pics/show.html.haml:28
Rendered collection of comments/_comment.html.haml [1 times] (153.0ms)
Rendered pics/show.html.haml within layouts/application (220.1ms)
Completed 500 Internal Server Error in 267ms (ActiveRecord: 13.0ms)
ActionView::Template::Error (undefined method `comment_comments_path' for #<#. <Class:0x007fa535ee08b8>:0x007fa535ee9d50>
Did you mean? pic_comment_comments_path):
2: = comment.body
3: %small
4: Submitted #{time_ago_in_words(comment.created_at)} ago
5: = form_for [comment, Comment.new] do |f|
6: = f.text_area :body, placeholder: "Add a Reply"
7: %br/
8: = f.submit "Reply"
app/views/comments/_comment.html.haml:5:in `_app_views_comments__comment_html_haml___2866288612795974586_70173756400620'
app/views/pics/show.html.haml:28:in `_app_views_pics_show_html_haml___2738689311384334047_70173775563600'
And this is my _comment partial.
%li
= comment.body
%small
Submitted #{time_ago_in_words(comment.created_at)} ago
= form_for [comment, Comment.new] do |f|
= f.text_area :body, placeholder: "Add a Reply"
%br/
= f.submit "Reply"
%ul
= render partial: 'comments/comment', collection: comment.comments
Everything is fixed now and working properly. Had to pass the #pic variable. Thanks
Acts-as-Taggable-on is failing to correctly print out only the name column, and not the entire hash of information on the object itself. How can I setup the show page to print out each tag separately for the model? The code that I have so far is below.
show.html.erb
<%= #user.user_profile.tags.each do |tag| %>
<%= tag.name %>
<% end %>
results shown on show page
#<ActsAsTaggableOn::Tag::ActiveRecord_Associations_CollectionProxy:0x2b7534d0> games [#<ActsAsTaggableOn::Tag id: 1, name: "games", created_at: "2017-08-25 15:54:30", updated_at: "2017-08-25 15:54:30", taggings_count: 1>] 1
console output
Rendering users/show.html.erb within layouts/application
UserProfile Load (8.0ms) SELECT "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = $1 LIMIT $2 [["user_id", 5], ["LIMIT", 1]]
ActsAsTaggableOn::Tag Load (15.0ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND "taggings"."context" = $3 [["taggable_id", 2], ["taggable_type", "UserProfile"], ["context", "tags"]]
ActsAsTaggableOn::Tagging Load (2.0ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 [["taggable_id", 2], ["taggable_type", "UserProfile"]]
ActsAsTaggableOn::Tag Load (25.0ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL) [["taggable_id", 2], ["taggable_type", "UserProfile"]]
user_profile.rb
class UserProfile < ApplicationRecord
acts_as_taggable
acts_as_taggable_on :abilities
end
Try
<% #user.user_profile.tags.each do |tag| %>
<%= tag.name %>
<% end %>
(without '=' after the '<%' in the first line)
I want to implement a way of clicking a button and running a method without having to switch to the view of the specifid item/object found by id. I'm using PostgrSQL.
In my PaymentsController I have a method called withdraw, which looks something like this:
def withdraw
#payment = Payment.find(params[:id])
#payment.withdrawn = true
#payment.amount_interest = withdraw.amount * 1.1
#payment.save
head :ok
end
And the route:
patch '/withdraw/:id', to: 'payments#withdraw', as: :withdraw
each statement with link_to:
<% #payments.each do |withdraw| %>
<tr>
<td><%= withdraw.amount %></td>
<td><%= link_to 'Withdraw', withdraw_path(withdraw), method: :patch, remote: true %></td>
</tr>
<% end %>
EDIT: Upon clicking "Withdraw" I get an unending database query that looks like this:
Started PATCH "/withdraw/1" for 127.0.0.1 at 2016-11-11 21:20:34 +0200
Processing by PaymentsController#withdraw as JS
Parameters: {"id"=>"1"}
Started PATCH "/withdraw/1" for 127.0.0.1 at 2016-11-11 21:20:34 +0200
Processing by PaymentsController#withdraw as JS
Member Load (40.0ms) SELECT "members".* FROM "members" WHERE "members"."id" = $1 ORDER BY "members"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Parameters: {"id"=>"1"}
Member Load (1.4ms) SELECT "members".* FROM "members" WHERE "members"."id" = $1 ORDER BY "members"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Payment Load (0.0ms) SELECT "payments".* FROM "payments" WHERE "payments"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Payment Load (2.0ms) SELECT "payments".* FROM "payments" WHERE "payments"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Payment Load (1.1ms) SELECT "payments".* FROM "payments" WHERE "payments"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Payment Load (0.0ms) SELECT "payments".* FROM "payments" WHERE "payments"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
My Payments model has the following:
account:string amount:decimal paid:boolean withdrawn:boolean amount_interest:decimal
belongs_to :member
Any assistance will be greatly appreciated.
The issue was a bug in my withdraw method.
changed:
#payment.amount_interest = withdraw.amount * 1.1
to:
#payment.amount_interest = #payment.amount * 1.1
I'm running into an issue where I rander a partial using AJAX and for some reason the line_items renders to the amount of products in it. So if there's 2 products, it loops through all line_items 2 times, causing it to be 4 elements listed instead of 2. So the loop keeps going by the amount of products in the line_items.
In my create.js.erb I have:
$('#shopping-cart').html("<%= escape_javascript render(#cart) %>");
and my _cart:
<div id="shopping-cart">
<div id="shopping-cart-header">
</div>
<div id="shopping-cart-items">
<table>
<%= render cart.line_items %>
<tr class="total-line">
<td colspan="2">Total</td>
<td class="total-cell"><%= number_to_currency(cart.total_price, unit: '') %></td>
</tr>
</table>
<%= button_to 'Empty cart', cart, method: :delete %>
</div>
<div id="shopping-cart-footer">
<%= link_to 'New order', new_order_path, class: 'btn btn-main btn-lg', data: { no_turbolink: true } %>
</div>
</div>
and the issues is with render cart.line_items I guess:
<% #cart.line_items.each do |item| %>
<% if line_item == #current_item %>
<tr id="current-item">
<% else %>
<tr>
<% end %>
<td><%= item.quantity %> ×</td>
<td>
<p><%= item.product.name %></p>
<% if item.line_item_attributes.exists? %>
<% item.line_item_attributes.each do |attribute| %>
<i><%= attribute.product_attribute.name %></i>
<% end %>
<% end %>
</td>
<td class="item-price"><%= number_to_currency(item.total_price, unit: '') %></td>
</tr>
<% end %>
if I've now added 2 products they're shown twice like this:
1 × red shirt 490.00
1 × blue shirt 89.00
1 × red shirt 490.00
1 × blue shirt 89.00
Total 579.00
Any one know what's going on? My logs are:
Started POST "/line_items" for ::1 at 2016-01-25 08:53:41 +0100
Processing by LineItemsController#create as JS
Parameters: {"utf8"=>"✓", "line_item"=>{"product_id"=>"5", "instruction"=>""}, "commit"=>"Legg til"}
Cart Load (0.4ms) SELECT "carts".* FROM "carts" WHERE "carts"."id" = $1 LIMIT $2 [["id", 92], ["LIMIT", 1]]
Product Load (0.3ms) SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
(0.3ms) SELECT COUNT(*) FROM "line_items" WHERE "line_items"."cart_id" = $1 AND "line_items"."product_id" = $2 [["cart_id", 92], ["product_id", 5]]
LineItem Load (0.4ms) SELECT "line_items".* FROM "line_items" WHERE "line_items"."cart_id" = $1 AND "line_items"."product_id" = $2 [["cart_id", 92], ["product_id", 5]]
LineItemAttribute Load (0.4ms) SELECT "line_item_attributes".* FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 [["line_item_id", 132]]
LineItem Load (0.4ms) SELECT "line_items".* FROM "line_items" WHERE "line_items"."cart_id" = $1 AND "line_items"."id" = $2 LIMIT $3 [["cart_id", 92], ["id", 132], ["LIMIT", 1]]
(0.2ms) BEGIN
Product Load (0.4ms) SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
SQL (11.6ms) UPDATE "line_items" SET "quantity" = $1, "updated_at" = $2 WHERE "line_items"."id" = $3 [["quantity", 2], ["updated_at", 2016-01-25 07:53:41 UTC], ["id", 132]]
(0.8ms) COMMIT
LineItem Exists (1.0ms) SELECT 1 AS one FROM "line_items" WHERE "line_items"."cart_id" = $1 LIMIT $2 [["cart_id", 92], ["LIMIT", 1]]
LineItem Load (0.7ms) SELECT "line_items".* FROM "line_items" WHERE "line_items"."cart_id" = $1 [["cart_id", 92]]
Product Load (0.5ms) SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
LineItemAttribute Exists (0.4ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 131], ["LIMIT", 1]]
LineItemAttribute Exists (1.0ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 131], ["LIMIT", 1]]
Product Load (0.4ms) SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
LineItemAttribute Exists (0.4ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 132], ["LIMIT", 1]]
LineItemAttribute Exists (0.3ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 132], ["LIMIT", 1]]
LineItemAttribute Exists (0.4ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 131], ["LIMIT", 1]]
LineItemAttribute Exists (0.3ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 131], ["LIMIT", 1]]
LineItemAttribute Exists (0.3ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 132], ["LIMIT", 1]]
LineItemAttribute Exists (0.3ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 132], ["LIMIT", 1]]
Rendered line_items/_line_item.html.erb (22.9ms)
LineItemAttribute Exists (0.5ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 131], ["LIMIT", 1]]
LineItemAttribute Exists (0.4ms) SELECT 1 AS one FROM "line_item_attributes" WHERE "line_item_attributes"."line_item_id" = $1 LIMIT $2 [["line_item_id", 132], ["LIMIT", 1]]
Rendered carts/_cart.html.erb (74.4ms)
Rendered line_items/create.js.erb (78.1ms)
It seems that _line_items are rendered, and then _cart (which again contains line_items) are rendered - maybe thats an issue?
You're correct. When you call render cart.line_items, you're rendering a collection. To quote the Rails documentation,
When you pass a collection to a partial via the :collection option, the partial will be inserted once for each member in the collection:
<h1>Products</h1>
<%= render partial: "product", collection: #products %>
(this is the same as your render cart.line_items)
And then in the partial you will have access to a product (or line_item in your case) local variable, like this:
<p>Product Name: <%= product.name %></p>
So: Rewrite your partial view to represent a single line_item with a corresponding line_item variable instead of a collection.
I am trying to do a marking system for moderators, in which they can select each item as "editor pick".
I've added a :editor_pick column to photos table(boolean)
and added a form in the partial for each item to have a checkbox in it:
<% if controller_name == "photos" %>
<%= simple_form_for [#user, #photo] do |f| %>
<%= f.input :editor_pick, label: "Portada" %>
<%= f.submit "Pick" %>
<% end %>
<% end %>
after that, I required the new param in strong parameters in photos controller
It's redirecting me correctly after the update, but it's not saving the new value.
When I reload the page, the boolean is still false.
My controller actions:
def by_zone
#photo = Photo.find_by(params[:id])
#user = #photo.user
render :index
end
def update
#photo = Photo.friendly.find(params[:id])
if #photo.update_attributes(photo_params)
redirect_to [current_user, #photo], notice: 'El spot se ha actualizado.'
else
render 'edit'
end
end
Edit
def photo_params
params.require(:photo).permit(:editor_pick,:url,:remote_photo_url,:thumbnail_cache ,:order,:string_tags,:tag_list, :sponsored, :photo, :terms, :title,:description,:category_id,:zone_id, :crop_x, :crop_y, :crop_w, :crop_h, sponsors_attributes: [:name, :description, :web, :facebook, :twitter, :sponsored_avatar])
end
Params Edit
Started PATCH "/users/enrique-isasi-12/photos/rustic-plastic-table" for 127.0.0.1 at 2015-01-14 16:43:55 +0100
Processing by PhotosController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"wB2SmOT0zR0dwIIguRhzFdctY51LX333CzGYelxS4Hs=", "photo"=>{"editor_pick"=>"1"}, "commit"=>"Pick", "user_id"=>"enrique-isasi-12", "id"=>"rustic-plastic-table"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 5 ORDER BY "users"."id" ASC LIMIT 1
Notification Load (0.3ms) SELECT "notifications".* FROM "notifications" WHERE "notifications"."user_id" = $1 AND "notifications"."viewed_at" IS NULL ORDER BY "notifications"."created_at" DESC LIMIT 30 [["user_id", 5]]
Notification Load (0.2ms) SELECT "notifications".* FROM "notifications" WHERE "notifications"."user_id" = $1 AND (viewed_at IS NOT NULL) ORDER BY "notifications"."created_at" DESC LIMIT 30 [["user_id", 5]]
Photo Load (0.4ms) SELECT "photos".* FROM "photos" WHERE "photos"."slug" = 'rustic-plastic-table' ORDER BY "photos"."id" ASC LIMIT 1
(0.4ms) BEGIN
Category Load (0.3ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = $1 ORDER BY "categories"."id" ASC LIMIT 1 [["id", 2]]
Zone Load (0.3ms) SELECT "zones".* FROM "zones" WHERE "zones"."id" = $1 ORDER BY "zones"."id" ASC LIMIT 1 [["id", 6]]
ActsAsTaggableOn::Tag Load (0.2ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL) [["taggable_id", 26], ["taggable_type", "Photo"]]
SQL (0.6ms) UPDATE "photos" SET "editor_pick" = $1, "updated_at" = $2 WHERE "photos"."id" = 26 [["editor_pick", true], ["updated_at", Wed, 14 Jan 2015 15:43:55 UTC +00:00]]
false ActsAsTaggableOn::Tag Load (0.2ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL) [["taggable_id", 26], ["taggable_type", "Photo"]]
(6.1ms) COMMIT
Redirected to http://localhost:3000/users/enrique-isasi-4/photos/rustic-plastic-table
Completed 302 Found in 31ms (ActiveRecord: 9.7ms)