Rails: Global instance variables in Rails - ruby-on-rails

In my app users can submit recipes through a form, which will be published on a website. Before recipes get published they are moderated through a moderator.
Therefore my app shows in the navbar a count of all currently unpublished recipes for the moderator like so:
To achieve this at the moment I do the following:
application.rb
before_action :count_unpublished
def count_unpublished
#unpublished_count = Recipe.where(:published => false).count
end
_navbar.html.erb
<li>
<%= link_to recipes_path do %>
Recipes <span class="badge" style="background-color:#ff7373"><%= #unpublished_count %></span>
<% end %>
</li>
It works, but I am wondering now if this is a good practice as now with every action my app hits the recipe database (which is maybe not very elegant).
Is there a better solution to achieve this?

cache_key = "#{current_user.id}_#{unpublished_count}"
#unpublished_count = Rails.cache.fetch(cache_key, expires_in: 12.hours) do
Recipe.where(:published => false).count
end
For More: http://guides.rubyonrails.org/caching_with_rails.html#low-level-caching

To avoid hitting the database, you can introduce caching. It comes in many forms: faster storage (memcached, redis), in-process caching (global/class variables) and so on. And they all share the same problem: you need to know when to invalidate the cache.
Take a look at this guide to get some ideas: Caching with Rails.
If I were you, I would not care about this until my profiler tells me it's a performance problem. Instead, I'd direct my efforts to developing the rest of functionality.

Your falling into the trap of premature optimisation. Before doing any optimisation (which increases code complexity most of the time) you have to profile your code to find the bottleneck. Improving a SQL requests which counts for a small part of the total response time is useless. In the contrary if the SQL takes a big amount of time, that is a great improvement.
For that I can recommend these 2 gems:
https://github.com/miniProfiler/rack-mini-profiler
https://github.com/BaseSecrete/rorvswild (disclaimer: I'm the author of this one)
To reply to your question, the better way would be:
# app/models/recipe.rb
class Recipe < AR::base
# A nice scope that you can reuse anywhere
scope :unpublished, -> { where(published: false) }
end
Then in your navbar.html.erb:
<li>
<%= link_to recipes_path do %>
Recipes <span class="badge" style="background-color:#ff7373"><%= Recipe.unpublished.count %></span>
<% end %>
</li>
You have no more these ugly callback and instance variable in the controller.
Unless you have a lot of recipes (something like 100K or more) performance won't be an issue. In that case you can add an index:
CREATE INDEX index_recipes_unpblished ON recipes(published) WHERE published='f'
Note that the index applies only when published is false. Otherwise it would be counter productive.
I think that caching in your case is not well because the invalidation is extremely complex and leads to awful and easy breakable code. Don't worry to hit the database, we will never write faster code than PostgreSQL/MySQL, etc.

Related

Disable upvote button in acts_as_votable gem after an up vote

Context:
I have a forum thread model which acts_as_votable.
The up vote functionality works fine
The up vote button allows the current_user to up vote the forum thread only once, which is the desired functionality. I am trying to change the css of the up vote button to a different color once the current_user had clicked on it.
<%= link_to forum_thread do %>
<h3>
<%= link_to like_forum_thread_path(forum_thread), method: :put, class: "" do %>
<span class="glyphicon glyphicon-arrow-up"></span>
<% end %>
<%= forum_thread.get_upvotes.size %>
<%= forum_thread.subject %>
</h3>
<% end %>
Problem:
One way to do this is to get a list of all the user_id's(using #forum_thread.votes_for_ids) who have up voted the forum thread and check if the current_user user id matches the list. If so, then disable the button. Is there a more efficient way to do this ?
Solution first:
#user.voted_for? #post
Its available in the documentation as well. Here
Now, lets go to the details:
You current solution does the following:
Fetch all the votes on the Post. Ideally, see to it that you get MySQL to return as fewer ActiveRecord objects as possible. Because, there is a considerable performance loss converting each MySQL row into an Active Record.
Looping through all the ActiveRecords and collecting the USER_ID in it. Looping is another performance hindrance, if you could easily avoid it.
Instead, prefer a MySQL query that returns the ROW/Data of what exactly we need, "If the current user had voted on the Post". something like (You would need to use the correct/apt database table names):
PostVote.find_by_post_id_and_user_id(post_id, user_id).
The above returns if the user had voted or not.
Alternatively, Acts_As_Votable does provide the same without you having to do the heavy lifting:
#user.voted_for? #post

Is this way of calling object supposed to be bad practice when considering loading speed?

My way
controller pattern 1 (note: Here, it's calling all users!!)
#users = User.confirmed.joins(:profile)
view pattern 1 (note: Here, it only shows first 10 users but it show the number of all users!!)
<%= "ALL ("+ #users.count.to_s + " users)" %>
<% #users.limit(10).each do |users| %>
<%= render 'users/user', :user => users %>
<% end %>
Should it be just like this below if I'm considering page loading speed?
Or it won't be changed?
controller pattern 2 (note: I added limit(10), and #users_count to count all users)
#users = User.confirmed.joins(:profile).limit(10)
#users_count = User.confirmed.joins(:profile).count
view pattern 2 (note: I took it off limit(10) and use #users_count for count)
<%= "ALL ("+ #users_count.to_s + " users)" %>
<% #users.each do |users| %>
<%= render 'users/user', :user => users %>
<% end %>
If you have lazy loading disabled, then the second approach would be faster because Rails doesn't need to fetch all records from the database. You should really fetch only the records you need when performing queries.
If you have lazy loading enabled (by default), then it is the same, because the data is fetched when it is needed, so the effect will be the same. You can also put two variables in controller and write the same query as you did in the view and the data will be fetched only if and when it is needed.
#users = User.confirmed.joins(:profile)
#users_count = #users.count
#users = #users.limit(10)
You can check sql generated by the app in your rails console and then decide.
Also, if you are using profile in user.html.erb, consider using includes instead of join. Join can cause n+1 problem if you need associated records. If you don't, you do not want to fetch records you don't need. You can read more about it here, in 12 Eager Loading Associations.
The two options are exactly the same. Neither of them loads all the Users because you're just chaining scopes. The query is only run when you call .each in the view, at which point you've applied the .limit(10) anyway. I'd go with the first option because the code is cleaner.
#users.count does one query to get the count, it doesn't instantiate any User objects.
#users.limit(10).each ... does one query (actually two because you've used includes) with a limit, so it will instantiate 10 objects plus your includes.
you can try #users.find_in_batches
Please take a look
Find in batches
Please let me know
If you want speed loading
I can suggest you memcache Memcache

How to determine if Submission has been voted on in Rails

I've built several apps in rails that have submissions that you can vote on. Vote up or vote down. I've always custom built the voting functionality, and with each app, the code has gotten better and more elegant. But one part that has always been the same is when a user comes to a submission, I do the same thing in the view:
<% if #submission.votes.include?(current_user.votes) %>
"already voted on"
<% else %>
<%= link_to "vote", submission_vote_path(#submission) %>
<% end %>
Or something of this nature. I have a feeling there must be a more efficient way to go about this, but I'm not exactly sure how. Any advice?
It looks fine, but obviously it depends on the queries lying behing the scene.
It's always better to consume ruby code than db queries.
So if you have to check many votes, stick with your current code. Otherwise, make a dedicated query.

Using Rails polymorphism for nested comments

I need to build a nested comments system in a Rails 3 application that allows for comments on many models (articles, posts, etc) and am debating rolling my own solution along the lines of this post. There are gems available like acts_as_commentable_with_threading with awesome_nested_set, but they feel bloated for my needs.
I need to be able to add comments to multiple models
I need to be able to add comments to comments, infinitely deep
I need to be able to efficiently retrieve all descendants for a post, article, etc
I need to be able to efficiently present the comments in their appropriate nesting
My question is, were I to roll my own solution what potential hiccups I could face. I want to avoid going down one path only to reach a dead end. My initial concerns relate to efficiently querying for children. Say, for instance, getting a list of an articles descendant comments (children and children of children).
Anyone have input on this? Thanks.
There are two kinds of nesting you can do: a tree and a nested set.
acts_as_tree stores only a parent_id and so it is really fast to write new entries, but you have to recursively walk the chain of id numbers to get a list of all the children. This is not a good choice when you need to do lots of reads.
awesome_nested_set records three bits of information: parent_id, lft and rgt. The left and right values are calculated so that they contain all the children ids for that entry. This is very fast for read operations but slower to write.
In your case I think awesome_nested_set is more appropriate. You might think it seems overkill, but nested sets get complicated in a hurry. You need to use the nested set pattern to efficiently query children.
You only need to use two methods to render the entire tree of comments: iterate over Comment.roots and for each comment, render comment.children.
class ModelController < ApplicationController
def show
#model = Model.find_by_id(params[:id])
#comments = #model.comments.roots
end
end
<ul id="comments">
<% #comments.each do |comment| %>
<%= render :partial => 'comment', :object => comment %>
<% end %>
</ul>
<!-- _comment partial -->
<li class="comment">
<!-- comment markup -->
<% if comment.children.present? %>
<ul>
<%= render :partial => 'comment', :collection => comment.children %>
</ul>
<% end %>
</li>
To save a nested comment, simply fill in the parent_id and awesome_nested_set will do the rest. I don't think rolling your own solution will be any more elegant than this.
Update: Looks like the awesome_nested_set hasn't been updated in some time. Check out ancestry instead. Does basically the same things.
A tree structure, yes, is a good idea - however it is the query execution itself that is of the utmost concern and i don't think most tree implementations as a gem take this into account and whilst nested set is alright - i reckon if something really bad happened, its overcomplicated in terms of write operations.
I'd check out Recursive CTEs - whilst not database agnostic, it gives you a nice data structure to work with without having to have extra attributes to track.
I used to do something like this by adding the following fields to the comments table:
attached_to_controller (string)
attached_to_id (int)
Then when showing I would make an AJAX call to the comments index and filter based on these two fields.
Of course, when creating comments you need to pass the appropriate values for these fields.

Rails - Best way to implement Optionnal Fragment Caching for testing purposes

I'm using fragment caching a lot and it is essential to me for good performance. However, due to the complexity of the caching I'm using, I need to offer my testers, a way to disable/enable caching as a session variable. (On a user basis only)
I was thinking about implementing a cache_disabled? method, and I now check for it's value everywhere I use cache. Now, I'm stuck with the following piece of caching, and I can't figure out how to nicely integrate this check :
<% cache(#cache_key_for_consultContent) do %>
<div id="consult">
<%= render :partial => 'LOTS_OF_CONTENT' %>
</div>
<% end %>
I need the content to be called when caching is disabled or content isn't cached yet.
thanks for your creativity! (Please keep it DRY)
In your application helper you could try:
def optional_cache(key, &block)
cache(key, &block) unless session[:disable_caching]
end
Then replace your calls to cache() with optional_cache().

Resources