I was trying to add fragment caching to speed up website performance.
Now, I tested it in development mode, so I change this
#environments/developments.rb
config.action_controller.perform_caching = true
And in erb
<% #projects.each do |project| %>
<% cache project do %>
<%= link_to "#{project.name}", category_project_path(#category, project) %>
<br>
<% end %>
<% end %>
However, it seems not work. Every time I refresh the page, it showed query again in terminal.
I thought it would only query once in first time, or I've misunderstood the concept of cache?
Processing by CategoriesController#show as HTML
Parameters: {"id"=>"3306"}
Category Load (0.4ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1 [["id", 3306]]
Project Load (0.5ms) SELECT "projects".* FROM "projects" WHERE "projects"."category_id" IN (3306)
(0.4ms) SELECT DISTINCT COUNT(DISTINCT "projects"."id") FROM "projects" WHERE "projects"."category_id" = $1 [["category_id", 3306]]
Project Load (1.0ms) SELECT DISTINCT "projects".* FROM "projects" WHERE "projects"."category_id" = $1 LIMIT 10 OFFSET 0 [["category_id", 3306]]
Cache digest for app/views/categories/show.html.erb: 9b54e2d9c7ce230e3f6a333f00d549da
Read fragment views/projects/3670-20160715055333331671000/9b54e2d9c7ce230e3f6a333f00d549da (0.2ms)
Cache digest for app/views/categories/show.html.erb: 9b54e2d9c7ce230e3f6a333f00d549da
Read fragment views/projects/3677-20160715055334439274000/9b54e2d9c7ce230e3f6a333f00d549da (0.2ms)
Cache digest for app/views/categories/show.html.erb: 9b54e2d9c7ce230e3f6a333f00d549da
Read fragment views/projects/3678-20160715055334446172000/9b54e2d9c7ce230e3f6a333f00d549da (0.2ms)
Cache digest for app/views/categories/show.html.erb: 9b54e2d9c7ce230e3f6a333f00d549da
Read fragment views/projects/3689-20160715055334536421000/9b54e2d9c7ce230e3f6a333f00d549da (0.1ms)
Rendered categories/show.html.erb within layouts/application (8.7ms)
Completed 200 OK in 31ms (Views: 26.0ms | ActiveRecord: 2.3ms)
According to your logs it looks like data is product is populated from the cache and no query is fired for product.
views/projects/3670-20160715055333331671000/9b54e2d9c7ce230e3f6a333f00d549da
The number in the middle is the product_id followed by the timestamp value in the updated_at attribute of the product record. Rails uses the timestamp value to make sure it is not serving stale data. If the value of updated_at has changed, a new key will be generated(fires query to database). Then Rails will write a new cache to that key, and the old cache written to the old key will never be used again.
Hope this help!
Related
I'm experiencing some unusual behaviour with my Rails 4 Application. Every single-time I click on a link_to inside my views, my controllers actions are being called twice. For example:
In my root_url I have this standard call for users_profile:
<%= link_to('User Profile', users_profile_path, :class => "logout-button") %>
When I click this link, my console shows the following output:
Started GET "/users/profile" for 127.0.0.1 at 2013-11-25 20:45:53 -0200
Processing by Users::SessionsController#profile as HTML
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = 45 ORDER BY "users"."id" ASC LIMIT 1
InvestorProfile Load (0.5ms) SELECT "investor_profiles".* FROM "investor_profiles" WHERE "investor_profiles"."user_id" = $1 ORDER BY "investor_profiles"."id" ASC LIMIT 1 [["user_id", 45]]
EmployeeProfile Load (0.5ms) SELECT "employee_profiles".* FROM "employee_profiles" WHERE "employee_profiles"."user_id" = $1 ORDER BY "employee_profiles"."id" ASC LIMIT 1 [["user_id", 45]]
Rendered users/sessions/_investor_setup.html.erb (3.9ms)
Rendered users/sessions/profile.html.erb within layouts/application (5.2ms)
Completed 200 OK in 19ms (Views: 11.2ms | ActiveRecord: 2.5ms)
Started GET "/users/profile" for 127.0.0.1 at 2013-11-25 20:45:53 -0200
Processing by Users::SessionsController#profile as HTML
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 45 ORDER BY "users"."id" ASC LIMIT 1
InvestorProfile Load (0.3ms) SELECT "investor_profiles".* FROM "investor_profiles" WHERE "investor_profiles"."user_id" = $1 ORDER BY "investor_profiles"."id" ASC LIMIT 1 [["user_id", 45]]
EmployeeProfile Load (0.2ms) SELECT "employee_profiles".* FROM "employee_profiles" WHERE "employee_profiles"."user_id" = $1 ORDER BY "employee_profiles"."id" ASC LIMIT 1 [["user_id", 45]]
Rendered users/sessions/_investor_setup.html.erb (3.3ms)
Rendered users/sessions/profile.html.erb within layouts/application (4.1ms)
Completed 200 OK in 12ms (Views: 7.5ms | ActiveRecord: 1.2ms)
People often have this behaviour when there's a remote (JS for example) calling the method, but this is not my case. the weirdest part is that, if I put the direct URL to the users_profile_path on my browser. I only get one request on my rails console:
Started GET "/users/profile" for 127.0.0.1 at 2013-11-25 20:48:17 -0200
Processing by Users::SessionsController#profile as HTML
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 45 ORDER BY "users"."id" ASC LIMIT 1
InvestorProfile Load (0.3ms) SELECT "investor_profiles".* FROM "investor_profiles" WHERE "investor_profiles"."user_id" = $1 ORDER BY "investor_profiles"."id" ASC LIMIT 1 [["user_id", 45]]
EmployeeProfile Load (0.2ms) SELECT "employee_profiles".* FROM "employee_profiles" WHERE "employee_profiles"."user_id" = $1 ORDER BY "employee_profiles"."id" ASC LIMIT 1 [["user_id", 45]]
Rendered users/sessions/_investor_setup.html.erb (3.4ms)
Rendered users/sessions/profile.html.erb within layouts/application (4.2ms)
Completed 200 OK in 12ms (Views: 7.7ms | ActiveRecord: 1.1ms)
I'm getting this same result for every link inside my application, not only this one.
Rails 5/6
When I was clicking on a link, the whole controller was being called twice. I tried the accepted answer, but it does not work for me, so I just set turbolinks: false as below:
<%= link_to("Demo", #user, data: { turbolinks: false } ) %>
If you would like your app to still make use of Turbolinks then "Opting out of Turbolinks" on the code that is giving you problems is the way to go; just add data-no-turbolink.
I was having problems with using Bootstrap 3 and adding that fixed it. For example;
<li class="list-group-item" data-no-turbolink>
<%= link_to download_path(item) do %>
<button type="button" class="btn btn-success">Download</button>
<% end %>
</li>
I actually managed to solve this on my own.
There's a default gem that is installed with rails 4.0, it is called Turbolinks*.
For some reason, the javascript used in this gem* was causing the doubled requests on my server. That's why only GET requests were behaving like this, and POST requests were normal.
I still don't fully understand why gem* causes that, but after I removed the following line from my application.js file, the doubled requests stopped.
=// require turbolinks
Another solution is to add data-no-turbolink to the tag.
More info here: http://blog.flightswithfriends.com/post/53943440505/how-to-disable-turbolinks-in-rails-4
I wanted a backup of my controller so I could make a new one with a new feature. I made a copy of the original and re-named the original. Like this: tickets_controller_9_18_2013.rb
When I added my new feature in index to the new copied controller that is named as the original controllers name: tickets_controller.rb the development env. used the new feature controller with the original name 'tickets_controller.rb' while the production env. used the re-named original controller 'tickets_controller_9_18_2013.rb'
How do you make the production env. use the copied one that is renamed as the original?
In my controller I am getting all the Users that have the same 'reseller_id' attribute.
Then I am putting the tickets from each user into and array #tickets.
class Users::TicketsController < ApplicationController
def index
if admin_user?
#tickets = []
all_users = User.find_all_by_reseller_id(current_user.reseller_id)
all_users.each do |u|
#tickets += u.tickets
end
#tickets = #tickets.paginate(page: params[:page])
else
#tickets = current_user.tickets.paginate(page: params[:page])
end
The problem is during production environment the array is not the same as the development environment.
I have restarted the apache server and checked for any before_filter s.
Any ideas as to why the results are different?
Below are the logs from both environments when loading the array.
PRODUCTION
Processing by Users::TicketsController#index as HTML
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`remember_token` = 'kaVFM4TddpFadAP8w12Iwg' LIMIT 1
Reseller Load (0.1ms) SELECT `resellers`.* FROM `resellers` WHERE `resellers`.`id` = 1 LIMIT 1
Rendered users/_info.html.erb (1.8ms)
Rendered layouts/_opentag.html.erb (0.1ms)
Ticket Load (0.1ms) SELECT `tickets`.* FROM `tickets` WHERE `tickets`.`user_id` = 1 LIMIT 30 OFFSET 0
Rendered users/tickets/index.html.erb within layouts/application (5.9ms)
Rendered layouts/_header.html.erb (0.0ms)
Rendered layouts/_footer.html.erb (0.1ms)
DEVELOPMENT
Processing by Users::TicketsController#index as HTML
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`remember_token` = 'E0_tHb5p-7XTZAB8XcxtGg' LIMIT 1
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`reseller_id` = 1
Ticket Load (0.1ms) SELECT `tickets`.* FROM `tickets` WHERE `tickets`.`user_id` = 1
Ticket Load (0.1ms) SELECT `tickets`.* FROM `tickets` WHERE `tickets`.`user_id` = 6
Ticket Load (0.1ms) SELECT `tickets`.* FROM `tickets` WHERE `tickets`.`user_id` = 7
Reseller Load (0.1ms) SELECT `resellers`.* FROM `resellers` WHERE `resellers`.`id` = 1 LIMIT 1
Rendered users/_info.html.erb (2.3ms)
Rendered layouts/_opentag.html.erb (0.1ms)
Rendered users/tickets/index.html.erb within layouts/application (67.9ms)
Rendered layouts/_header.html.erb (0.0ms)
Rendered layouts/_footer.html.erb (0.1ms)
If I add the code to re-named original 'tickets_controller_9_18_2013.rb' the production env. uses it and the feature works.
How do I use the new controller named as the original? 'tickets_controller.rb'
Thanks
You should restart your application in production. Rails caches classes in production mode. To reload the classes, restart the application.
I had to place the date in front of the original name like this:
09_13_2013_tickets_controller.rb
Instead of:
tickets_controller_09_19_2013.rb
I had no idea that mattered.
There is #avalibable_colors=['#5A009D', '#004DFF', '#F4F400', '#FF8000'] variable (it is not instance of model), which represent avaliable options - colors, which hadn't been taken yet, through method:
def choose_color
#check=Check.find(params[:check_id])
#colorschemes=#check.colorschemes
current_colors=[]
#check.colorschemes.each do |c|
current_colors.push(c.color)
end
#avalibable_colors=['#5A009D', '#004DFF', '#F4F400', '#FF8000']
current_colors.each do |c|
if #avalibable_colors.index(c)
#avalibable_colors[#avalibable_colors.index(c)]=nil
end
end
#avalibable_colors.compact!
respond_to do |format|
format.js
end
end
After that #avalibable_colors contents only untaken color options.
And generally it works. If I choose 1 color option, it displays rest 4, if 2 - rest 3, if I choose one, and then delete it - it is in avaliable colors.
And that works until whole four. And If I add all four, and then delete one by one, no colors are avaliable until I refresh page.
Server logs:
Started GET "/choose_color?check_id=46" for 127.0.0.1 at 2013-06-13 19:00:31 +0400
Processing by ColorschemesController#choose_color as JS
Parameters: {"check_id"=>"46"}
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."auth_token" = 'oPXJtztDkYdYmVsQ3wxxfQ' LIMIT 1
Check Load (1.0ms) SELECT "checks".* FROM "checks" WHERE "checks"."id" = 46 LIMIT 1
CACHE (0.0ms) SELECT "checks".* FROM "checks" WHERE "checks"."id" = 46 LIMIT 1
Check Load (1.0ms) SELECT "checks".* FROM "checks" WHERE "checks"."id" = $1 LIMIT 1 [["id", "46"]]
Colorscheme Load (0.0ms) SELECT "colorschemes".* FROM "colorschemes" WHERE "colorschemes"."check_id" = 46
Rendered colorschemes/_choose_color.html.erb (0.0ms)
Rendered colorschemes/choose_color.js.erb (5.0ms)
Completed 200 OK in 33ms (Views: 22.0ms | ActiveRecord: 3.0ms)
So I am using mini-profiler and it gives me some nice stats.
However, one thing I have noticed is that I have gotten a lot of the SQL calls down to a minimum and now the biggest thing is the rendering of the various partials and HTML.
For instance, here are 2 different examples of issues I am facing:
Mini-Profiler
GET http://localhost:3000/ 14.0 +0.0
Executing action: index 9.5 +9.0
Rendering: home/index 7.8 +16.0
Rendering: home/_row 7.9 +22.0
Rendering: layouts/application 1038.7 +32.0
Rendering: layouts/_navigation 6.0 +586.0
Development.log
Started GET "/" for 127.0.0.1 at 2013-05-17 18:00:26 -0500
Processing by HomeController#index as HTML
Item Load (0.6ms) SELECT "items".* FROM "items" WHERE "items"."is_approved" = 't'
ActsAsTaggableOn::Tag Load (0.6ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = 13 AND "taggings"."taggable_type" = 'Item' AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL)
Rendered home/_row.html.erb (8.0ms)
Rendered home/index.html.erb within layouts/application (15.7ms)
Rendered layouts/_social_media.html.erb (0.5ms)
(0.4ms) SELECT items.id FROM "items"
ActsAsTaggableOn::Tag Load (0.7ms) SELECT tags.*, taggings.tags_count AS count FROM "tags" JOIN (SELECT taggings.tag_id, COUNT(taggings.tag_id) AS tags_count FROM "taggings" INNER JOIN items ON items.id = taggings.taggable_id WHERE (taggings.taggable_type = 'Item' AND taggings.context = 'tags') AND (taggings.taggable_id IN(13)) GROUP BY taggings.tag_id HAVING COUNT(taggings.tag_id) > 0) AS taggings ON taggings.tag_id = tags.id ORDER BY count LIMIT 5
Rendered layouts/_navigation.html.erb (6.1ms)
Rendered layouts/_site_nav.html.erb (0.6ms)
Rendered layouts/_messages.html.erb (0.2ms)
Rendered layouts/_footer.html.erb (0.1ms)
Completed 200 OK in 1069ms (Views: 1066.0ms | ActiveRecord: 2.3ms)
The output from Mini-Profiler at the top, shows that the main application.html.erb is adding significantly to load time.
Here is another example of another app where the views are the ones that take the most time to render:
Started GET "/" for 127.0.0.1 at 2013-05-17 17:55:01 -0500
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Processing by HomeController#index as HTML
Category Load (0.2ms) SELECT "categories".* FROM "categories" LIMIT 6
Banner Load (0.2ms) SELECT "banners".* FROM "banners" INNER JOIN "banner_types" ON "banner_types"."id" = "banners"."banner_type_id" WHERE (banner_types.name = 'Featured')
Banner Load (0.2ms) SELECT "banners".* FROM "banners" INNER JOIN "banner_types" ON "banner_types"."id" = "banners"."banner_type_id" WHERE (banner_types.name = 'Side')
Product Load (0.3ms) SELECT "products".* FROM "products"
Vendor Load (0.3ms) SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" IN (12, 11, 10)
Vendor Load (0.2ms) SELECT "vendors".* FROM "vendors"
User Load (0.2ms) SELECT "users".* FROM "users"
Rendered home/_popular_products.html.erb (16.1ms)
Rendered home/_popular_stores.html.erb (2.4ms)
Rendered home/index.html.erb within layouts/application (26.4ms)
Piggybak::Sellable Load (0.2ms) SELECT "sellables".* FROM "sellables" WHERE "sellables"."id" = 1 LIMIT 1
Rendered layouts/_login_nav.html.erb (8.2ms)
Rendered layouts/_navigation.html.erb (0.8ms)
CACHE (0.0ms) SELECT "vendors".* FROM "vendors"
Rendered layouts/_store_dropdown.html.erb (2.2ms)
Rendered layouts/_header.html.erb (18.1ms)
Rendered layouts/_messages.html.erb (0.3ms)
Rendered layouts/_footer.html.erb (0.9ms)
Completed 200 OK in 242ms (Views: 209.8ms | ActiveRecord: 1.9ms)
Granted, this particular time is just 209.8ms, but it has been as high as 5,000ms at various loading times.
How can I optimize the rendering of these views & partials? Or what tool can I use to at least figure out what is causing the long load time so I can chip away at it slowly?
You are profiling this in development. Be aware that because of the config.cache_classes setting, you app is reloaded with every request in that environment. Profiling with that setting set to true might give you different (and usually faster) results.
In one of your comments, you mention a Vendor.all.each snippet. I would check how long that actually takes. Just wrap it in a results = Benchmark.measure do block and see what results.real gives you. Let's say it finishes in 50 ms.
You have three options, generally:
If the 50ms is unsatisfactory, read the partial rendering result from a static file that you regenerate in certain intervals. This can be done by using the rails caching mechanisms or by putting it into a file that you regenerate with a cronjob. Either way, this involves some kind of delay in respect to new vendors that might have been created between intervals.
Or, if 50ms is fine for you, you can cast them to JSON (which is fast, have a look at https://github.com/ohler55/oj) and as suggested before, pull it with AJAX and render the html with javascript. This has the advantage that the actual rendering of the html is done within the browser. The browser is faster doing it and it usually has less load to cope with.
If you're unhappy with 50 ms and you can't live with slightly outdated information on your page, you would have to start doing option 2 but without ActiveRecord, so with the mysql ruby adapter directly. Or you make sure that whenever a new Vendor is created or an old one updated, you add JSON into a text column of the respective vendor, perhaps with some after_validation callback. So that you can at least do
vendors = Vendor.select(:json_cache).map{|v| v.json_cache}.join(',')
vendors = "[#{vendors}]"
render :json => vendors
... not exactly beautiful but blazing fast.
I hope, this is what you wanted to know.
In your second example, I see queries for:
All banners of type "Featured"
All banners of type "Side"
All products
All vendors
All users
If your partials have to iterate through huge collections of objects, it will slow down rendering times considerably.
Watch for iterators that call collections like Vendor.all.each do |v|. Model class methods should never be called in the view. Build a collection in the controller of objects you need, then send to the view in an instance variable.
I built a feature in my app to allow users to search for images that would search the image description,member who uploaded it, and image tags but I have this weird issue where if I search for the name 'jason' it breaks, but a name like 'jesse' works just fine.
Here is my controller
def search_results
#tattoos = Tattoo.where("description LIKE ?", "%#{params[:search]}%") | Tattoo.tagged_with("#{params[:search]}", :any => true ) | Member.where("membername LIKE ?", "%#{params[:search]}%").order("created_at DESC").page(params[:page]).per(10)
end
if i search for 'jason' my app breaks, giving me an 'undefined method' error and the console shows a bunch of stuff not shown if I searched for say, 'jesse'. Im not entirely sure how to word that but here is what I mean.
Search - Jason
Started GET "/search_results?utf8=%E2%9C%93&search=jason&commit=search" for 127.0.0.1 at 2012-05-23 20:48:26 -0400
Processing by IndexController#search_results as HTML
Parameters: {"utf8"=>"✓", "search"=>"jason", "commit"=>"search"}
SQL (0.7ms) SHOW TABLES
ActsAsTaggableOn::Tag Load (0.3ms) SELECT `tags`.* FROM `tags` WHERE (name LIKE 'jason')
Tattoo Load (0.2ms) SELECT `tattoos`.* FROM `tattoos` WHERE (description LIKE '%jason%')
Tattoo Load (0.2ms) SELECT `tattoos`.* FROM `tattoos` WHERE (1 = 0)
SQL (0.7ms) SHOW TABLES
SQL (0.5ms) SHOW TABLES
Member Load (0.3ms) SELECT `members`.* FROM `members` WHERE (membername LIKE '%jason%') ORDER BY fullname asc, created_at DESC LIMIT 10 OFFSET 0
Member Load (0.3ms) SELECT `members`.* FROM `members` WHERE `members`.`id` = 7 ORDER BY fullname asc LIMIT 1
ActsAsTaggableOn::Tag Load (0.3ms) SELECT `tags`.* FROM `tags` INNER JOIN `taggings` ON `tags`.id = `taggings`.tag_id WHERE ((`taggings`.taggable_id = 43) AND (`taggings`.taggable_type = 'Tattoo')) AND (taggings.context = 'styles' AND taggings.tagger_id IS NULL)
Rendered shared/_image_roll.html.erb (27.5ms)
Rendered index/search_results.html.erb within layouts/application (29.3ms)
Completed 500 Internal Server Error in 523ms
Processing by ErrorsController#internal_server_error as HTML
Parameters: {"utf8"=>"✓", "search"=>"jason", "commit"=>"search"}
SQL (0.7ms) SHOW TABLES
SQL (0.5ms) SHOW TABLES
Rendered shared/_register.html.erb (483.4ms)
Member Load (0.2ms) SELECT `members`.* FROM `members` WHERE `members`.`id` = 7 ORDER BY fullname asc LIMIT 1
MemberRole Load (0.2ms) SELECT `member_roles`.* FROM `member_roles` WHERE (`member_roles`.member_id = 7) LIMIT 1
SQL (0.6ms) SHOW TABLES
SQL (0.4ms) SELECT COUNT(*) FROM `tattoos` WHERE `tattoos`.`status` = 'pending'
SQL (0.2ms) SELECT COUNT(*) FROM `feedbacks` WHERE `feedbacks`.`approved` = 0
SQL (0.1ms) SELECT COUNT(*) FROM `tattoos` WHERE `tattoos`.`status` = 'reported'
Rendered shared/_navbar.html.erb (87.6ms)
Rendered shared/_login_form.html.erb (2.9ms)
Rendered errors/internal_server_error.html.erb within layouts/application (582.4ms)
Completed 500 Internal Server Error in 587ms (Views: 583.9ms | ActiveRecord: 3.5ms)
ActionView::Template::Error (undefined method `member' for #<Member:0x007f0b0ca077b0>):
1: <ol class="image_roll">
2: <% #tattoos.each do |t| %>
3: <li>
4: <% unless t.member.nil? %>
5:
6: <%= link_to image_tag(t.file.url(:small),:alt=>"#{strip_tags(t.description)}, #{t.style_list}, tattoos"), member_tattoo_path(t.member, t)%>
7: <% else %>
app/views/shared/_image_roll.html.erb:4:in `block in _app_views_shared__image_roll_html_erb__3185787815883689760_69842755443480_3152220748446100192'
app/views/shared/_image_roll.html.erb:2:in `each'
app/views/shared/_image_roll.html.erb:2:in `_app_views_shared__image_roll_html_erb__3185787815883689760_69842755443480_3152220748446100192'
app/views/index/search_results.html.erb:10:in `_app_views_index_search_results_html_erb___3497910042066847338_69842586729040__4333294961394575926'
Search - Jesse:
Started GET "/search_results?utf8=%E2%9C%93&search=jesse&commit=search" for 127.0.0.1 at 2012-05-23 20:48:36 -0400
Processing by IndexController#search_results as HTML
Parameters: {"utf8"=>"✓", "search"=>"jesse", "commit"=>"search"}
ActsAsTaggableOn::Tag Load (0.6ms) SELECT `tags`.* FROM `tags` WHERE (name LIKE 'jesse')
Tattoo Load (0.7ms) SELECT `tattoos`.* FROM `tattoos` WHERE (description LIKE '%jesse%')
Tattoo Load (0.1ms) SELECT `tattoos`.* FROM `tattoos` WHERE (1 = 0)
Member Load (0.5ms) SELECT `members`.* FROM `members` WHERE (membername LIKE '%jesse%') ORDER BY fullname asc, created_at DESC LIMIT 10 OFFSET 0
Rendered shared/_search.html.erb (0.8ms)
ActsAsTaggableOn::Tag Load (0.2ms) SELECT tags.*, taggings.tags_count AS count FROM `tags` JOIN (SELECT taggings.tag_id, COUNT(taggings.tag_id) AS tags_count FROM `taggings` INNER JOIN tattoos ON tattoos.id = taggings.taggable_id WHERE (taggings.taggable_type = 'Tattoo' AND taggings.context = 'styles') AND (taggings.taggable_id IN(SELECT tattoos.id FROM `tattoos`)) GROUP BY taggings.tag_id HAVING COUNT(taggings.tag_id) > 0) AS taggings ON taggings.tag_id = tags.id ORDER BY count DESC LIMIT 40
Rendered index/_tags_list.html.erb (19.7ms)
Rendered layouts/shared/_right_column_std.html.erb (22.3ms)
Rendered shared/_register.html.erb (5.7ms)
Member Load (0.3ms) SELECT `members`.* FROM `members` WHERE `members`.`id` = 7 ORDER BY fullname asc LIMIT 1
MemberRole Load (0.1ms) SELECT `member_roles`.* FROM `member_roles` WHERE (`member_roles`.member_id = 7) LIMIT 1
SQL (0.2ms) SELECT COUNT(*) FROM `tattoos` WHERE `tattoos`.`status` = 'pending'
SQL (0.1ms) SELECT COUNT(*) FROM `feedbacks` WHERE `feedbacks`.`approved` = 0
SQL (0.1ms) SELECT COUNT(*) FROM `tattoos` WHERE `tattoos`.`status` = 'reported'
Rendered shared/_navbar.html.erb (13.5ms)
Rendered shared/_login_form.html.erb (2.9ms)
Rendered index/search_results.html.erb within layouts/application (54.3ms)
Completed 200 OK in 64ms (Views: 57.9ms | ActiveRecord: 3.0ms)
And the view:
<% #tattoos.each do |t| %>
<% unless t.member.nil? %>
<%= link_to image_tag(t.file.url(:small),:alt=>"#{strip_tags(t.description)}, #{t.style_list}, tattoos"), member_tattoo_path(t.member, t)%>
<% else %>
<%= link_to image_tag(t.file.url(:small),:alt=>"#{strip_tags(t.description)}, #{t.style_list}, tattoos"), tattoo_path(t) %>
<% end %>
<% end %>
The "| vs ||" issue is not your problem. Doing this:
Tattoo.where(...) | Tattoo.tagged_with(...) | Member.where(...)
will result in an array that is the union of the three input results. Switching to ||:
Tattoo.where(...) || Tattoo.tagged_with(...) || Member.where(...)
will just give you Tattoo.where(...) since that query will never be false or nil and the || will short circuit as soon as it has non-falsey result; Tattoo.where(...) might might be empty but empty and falsey are different things.
Your problem is that you're mixing two different things in one array: Tattoo instances from the first two queries and Member instances from the last query. Then your ERB assumes that everything in #tattoos is a Tattoo (or something else that responds to member):
<% #tattoos.each do |t| %>
<% unless t.member.nil? %>
If your Member.where(...) finds anything, then you will have at least one Member instance in #tattoos and there is no Member#member method as the error message indicates:
ActionView::Template::Error (undefined method `member' for #<Member:0x007f0b0ca077b0>):
Stop mixing two different classes in #tattoos and your problem should go away. Or you could rename it to, say, #matches and smarten up your view logic to be able to display both Tattoo and Member instances.
One thing I noticed is that you are using | instead of || in
#tattoos = Tattoo.where("description LIKE ?", "%#{params[:search]}%") | Tattoo.tagged_with("#{params[:search]}", :any => true ) | Member.where("membername LIKE ?", "%#{params[:search]}%").order("created_at DESC").page(params[:page]).per(10)
perhaps that might be part of the issue?
I would also argue that you should move this logic into the model, which should make it easier to test.
# Controller
#search_results = Tattoo.results_for_search(params[:search])
#search_results << Member.results_for_search(params[:search])
# Tattoo Model
def results_for_search(search_term)
results = where("description LIKE ?", search_term).all
results << tagged_with(search_term, :any => true)
end
# Member Model
def results_for_search(search_term)
where("membername LIKE ?", search_term).order("created_at DESC")
end
# Search Results View
<% #search_results.each do |result| %>
<% if result.is_a?(Tattoo) %>
...
<% elsif result.is_a?(Member) %>
...
<% end %>
<% end %>