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.
Related
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.
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
I'm trying to build a condition based on wether or not a "user" is a "member". Basically I need a way of checking if the current_user.id matches any of the user_id of any members. The non-working code I have right now is:
<% if current_user = #page.members %>
you can view this content.
<% end %>
I'm looking for something along the lines of: "If current_user.id exists in the "user_id" of any members."
Something like this, based on the field names in your question:
<% if #page.members.map(&:user_id).include? current_user.id %>
You can view this content
<% end %>
Assuming your #page.members variable contains an array, you can use the include? method:
<% if #page.members.include? current_user %>
you can view this content.
<% end %>
If you're using an array of ids, you will of course need to change the test slightly to look for the current user's id:
<% if #page.members.include? current_user.id %>
you can view this content.
<% end %>
#member_ids = #page.members.map{|m| m.id()}
then check for the condition as below
#memeber_ids.include? current_user.id()
Has said before include? should do the thing.
I'm just answering to tell you about a gem called CanCan, that gives you easy access for authorization "helpers".
Why you should use CanCan instead of doing what you are actually doing?
Don't reinventing the weel most of the times it's a goob practice.
You are placing business logic on the view (bad practice).
CanCan most likely has been developed thinking on security, and all the best practices in mind.
You save some developing hours.
Sorry if I repeated myself.
This is probably easy but I'm a bit of a newbie on wrapping my head around these things sometimes.
Synopsis: I'm trying to make a checklist application that technicians go through and answer questions about what has been completed or done in the field. The technicians then submit this for review. The questions are created, managed, and updated by the managers.
UPDATE
I'm a designer, so I naturally magnetize to PS. Here's a photo of the concept: http://screensnapr.com/u/a9k1ps.png
checklist model contains: header, subheader, question, and answer.
Everything is a string, except the answer field, which is an integer for a check box.
I'm not quite sure which RESTful page to start with after that though. I need the header displayed like this (in view), but editable/submittable through the check box all on one page.
This view has to DISPLAY the checklist and EDIT the checklist at the same time. The manager needs to be able to add new headers, subheaders, and questions, which the technicians can then answer.
<% #checklists.each do |checklist| %>
<h1> <%=h checklist.header %> </h1>
<h3> <%=h checklist.subheader %> </h3>
<ul>
<li>
<%=h checklist.question %>
<% form_for #checklists do |f| %>
<%= f.check_box("checklist", "answer") %>
<% end %>
</li>
</ul>
<% end %>
Would this work and would it best to stick this in the index or edit action? Would I be better doing a partial of some sort? nested_attributes? I'm a bit lost at this point because I'm trying to manage two actions (index, edit) within one file.
If you want a manager to update/modify the checkboxes and the technicians to fill in the forms, you need a couple of extra tables. One containing the questions and one containing the values that are checked. Also, it seems better to split the controller into two, one for each user type:
For the manager part you can simply make a controller like any other controller: using the index action to show all questions and the edit/update/etc actions to modify them.
For the technician part you need to define a project table, containing some information about the project the technician is working on. And a checkboxes table containing the project_id and the checkbox_ids, in order to link the checkboxes to a certain project.
See A Guide to Active Record Associations for more information about creating associations between tables.
Without looking at this further, I'm willing to bet you want
form_for checklist.question and POST to questions_controller, which would use the #update action.
I'm developing a simple rails app for my own use for learning purposes and I'm trying to handle 2 models in 1 form. I've followed the example in chapter 13 of Advanced Rails Recipes and have got it working with a few simple modifications for my own purposes.
The 2 models I have are Invoice and InvoicePhoneNumber. Each Invoice can have several InvoicePhoneNumbers. What I want to do is make sure that each invoice has at least 1 phone number associated with it. The example in the book puts a 'remove' link next to each phone number (tasks in the book). I want to make sure that the top-most phone number doesn't have a remove link next to it but I cannot figure out how to do this. The partial template that produces each line of the list of phone numbers in the invoice is as follows;
<div class="invoice_phone_number">
<% new_or_existing = invoice_phone_number.new_record? ? 'new' : 'existing' %>
<% prefix = "invoice[#{new_or_existing}_invoice_phone_number_attributes][]" %>
<% fields_for prefix, invoice_phone_number do |invoice_form| -%>
<%= invoice_form.select :phone_type, %w{ home work mobile fax } %>
<%= invoice_form.text_field :phone_number %>
<%= link_to_function "remove", "$(this).up('.invoice_phone_number').remove()" %>
<% end -%>
</div>
Now, if I could detect when the first phone number is being generated I could place a condition on the link_to_function so it is not executed. This would half solve my problem and would be satisfactory, although it would mean that if I actually wanted to, say, delete the first phone number and keep the second, I would have to do some manual shuffling.
The ideal way to do this is presumably in the browser with javascript but I have no idea how to approach this. I would need to hide the 'remove' link when there was only one and show all 'remove' links when there is more than one. The functionality in the .insert_html method that is being used in the 'add phone number' link doesn't seem adequate for this.
I'm not asking for a step-by-step how-to for this (in fact I'd prefer not to get one - I want to understand this), but does anyone have some suggestions about where to begin with this problem?
There is a counter for partial-collections:
<%= render :partial => "ad", :collection => #advertisements %>
This
will render "advertiser/_ad.erb" and
pass the local variable ad to the
template for display. An iteration
counter will automatically be made
available to the template with a name
of the form partial_name_counter. In
the case of the example above, the
template would be fed ad_counter.
For your problem of detecting whether a row is the first one or not, you could add a local variable when calling the partial:
<%= render :partial => 'mypartial', :locals => {:first => true} %>
As it would be much easier to detect in the main file, whether a row is the first or not I guess.
Instead of detecting whether a phone number is the first, you could also detect whether a phone number is the only one. If not, add remove links next to all numbers otherwise, do not display the remove link. Note that besides showing/hiding the link, you also need to add code, to prevent removing of the last number by (mis)using an URL to directly delete the number instead of using your form.