Creating VoteUp and VoteDown Buttons with Acts_as_Votable - ruby-on-rails

I am new to RoR and I am trying to use the acts_as_votable plugin. I can see that there are methods such as
#object.vote :voter => #user, :vote => 'like'
But I don't know how to call this method based upon a button click on a view, which is my ultimate goal. The underlying methods exist, I just need to provide an action by the user.

There are two options. You can call the methods in a controller (or model) or you can do it with AJAX right there on the page. For something like voting, the AJAX method is probably more common, because it doesn't involve reloading the page.
Here's some simple examples of the sort of thing you want to do:
http://wowkhmer.com/2011/09/19/unobtrusive-ajax-with-rails-31/
http://stackoverflow.com/questions/10264453/is-it-possible-to-call-a-rails-helper-method-from-within-javascript
Also, take a look at the button_to docs:
http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to

Related

Best way to reset ActiveRecord Attribute with a link

I have a counter in my model that I want to give the user the ability to
reset it, I'm wondering what's the best way to achieve this. I can think of
two ways:
By a custom controller action.
Simple and easy but I can't decide which HTTP verb to use. I can make the
case that it should be a GET because the user clicks a link that reset
the counter and the result are always the same, i.e. counter
becomes 0. But it could also be a POST/PATCH since we are modifying
something on the server but POST/PATCH requires a form which leads to
the other way.
By a link that submits an edit form with the counter reset to 0 without
the user seeing the form.
I like this solution because it can be done with RESTful controller
methods. But I have no idea how to do that with Rails, or even if it's
possible.
So which is "Rails Way" to do this? and how do I do it?
Rather than creating a custom action, another approach is to create a well-named controller and stick to the RESTful controller method names.
config/routes.rb
resource :counter_reset, only: [:create]
app/controllers/counter_reset_controller.rb
class CounterResetController < ApplicationController
def create
# reset your counter
end
end
Then POST to counter_reset_path in your view
Personally, I would use button_to — this generates a single button that submits to the URL; it performs a POST operation by default. If you don't like the button style, you can switch to using link_to; however, keep in mind that if a user has JavaScript disabled, the request will fallback to using GET.
<%= button_to "Reset counter!", counter_reset_path %>
<%= link_to "Reset counter!", counter_reset_path, method: :post %>
http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to
http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
Update:
If you prefer not to create a new controller, you can create a new route that maps to a custom action in your existing controller:
config/routes.rb
resources :counters do
post :reset, to: "counters#reset"
end
app/controllers/counters_controller.rb
class CountersController < ApplicationController
def reset
# reset your counter
end
end
In your view:
<%= button_to "Reset counter!", counter_reset_path %>
Actually you don't need a form, for me i would add a new action, it would look something like this ( of course depends on how your current routing looks like )
/user/:id/counter/reset # with action = post
And the link is very simple, you just create a link_to and add a method: :post which will add a data-method: :post in the html, the rest will be handled by the unobtrusive js.
The reason I don't recommend the form method, is users might use it to update different attributes that you might not want to update, or at least even change the counter to whatever number they want, I prefer the specific number to be defined in the controller not the view/form.

How to organize business logic and js logic, for voting in Rails?

In rails, I want to handle the visual vote icons and the database record upon clicking on a vote button. It seems like it would be a good idea to have both of these in one authoritative location (so that they don't go out of sync), rather than duplicating the control flow logic for the front and back ends in different files.
There is some logic as follows, and I need to handle the front end aspect and the back end aspects.
What's he best way to do this in rails?
There is a poll, and there are different options. Each option is given a scope id.
The following code handles intuitive voting behavior for an option that allows only one option.
model method called by controller
if current_user voted_for? #votable
#votable.unvote_by current_user
#votable.vote_by current_user, scope: params[:vote_type] unless current_user voted_for? #votable, scope: params[:vote_type]
else
#votable.vote_by current_user, scope: params[:vote_type]
end
Now this is fine for the back end, and I need front-end.
asset javascript file
// Detect if record was voted for by current user, obtain #id
// add class 'voted' to child with matching #id
$('#poll .option').on(click, ->
if ($('this').first().class('voted') ) {
$('this').first().removeClass('voted');
} else if ( $('this').siblings('option').first().class('voted') ){
$('this').siblings('option').first().removeClass('voted');
} else {
$('this').first().addClass('voted');
}
Will this work properly with a rails remote: true link?
html.haml
#poll
= link_to (content_tag :div, class: #voted), vote_path(:vote_type)
= link_to "", vote_path(vote_type: "2"), class=
Using acts_as_votable API to conditionally set class in view. Use CSS to style 'voted'
controller
def show
# can be moved to model
if current_user.voted_on? #votable
#voted = 'voted'
else
#voted = ''
end
end
I have not used ajax calls in the above. Do I need to?
The above javascript seems like it would get very messy very quickly, if I use ajax.
It also doesn't prevent multiple voting, or the visual votes going out of sync with what's actually in the database.
Now the above so far duplicates the if/else control flow in the back and front ends.
But is it better to combine them in a js.erb file where it does both?
That's why I was thinking it might be better to combine things into one js.erb that handles both front and back. But that doesn't seem like good design either. Perhaps there is a way using ajax to put in more validations and increase robustness? Anyway, this is all just nice-to-haves. As long as it works, that's good.
It seems having a custom js.erb file is not good for using responds_with... I am confused. on how to proceed.
Sorry for the multiple questions within this question. I am just trying to implement an intuitive voting system, and it's not very easy to get bits and pieces of information from different sources.
Thanks in advance.
This is a very generic question. Aw as for the backend you can use some of the gems to do that as it would make your life easier something like acts_as_votable gem for rails.
Then include it at your ActiveRecord model:
class Record < ActiveRecord::Base
acts_as_votable
end
Now you will be able to use a lot of helper methods like:
#record = Record.create!(attributes)
#record.liked_by #user1
#record.downvote_from #user2
#record.vote_by :voter => #user4, :vote => 'bad'
#record.vote_by :voter => #user5, :vote => 'like'
As for your fronend, you can send some Ajax requests with the like of link_to remote: true or form_for remote:true. Then you can use RJS or even doing that manually on the client side using jQuery and change the state of the divs.
I'll demonstrate with code here:
In Controller you'll have an action something like:
def vote
#record = Record.find(params[:post_id])
#record.liked_by current_user
end
Html
<%= link_to 'like', vote_path(#record), class: 'vote', remote: true %>
JS
$('.vote')
.on('ajax:send', function () { $(this).addClass('loading'); })
.on('ajax:complete', function () { $(this).removeClass('loading'); })
.on('ajax:success', function (data) { $(this).html(data.count); });

Use a params[:value] to reference a controller method in Rails

I currently have a form (using form_tag). One of the fields is a dropdown list of options. Each option value matches the name of a method in my controller. What I want to do is when the form submit button is clicked, it runs the controller method corresponding directly to the value selected in the dropdown field.
I've built a work-around right now, but it feels too verbose:
def run_reports
case params[:report_name]
when 'method_1' then method_1
when 'method_2' then method_2
when 'method_3' then method_3
when 'method_4' then method_4
else method_1
end
# each method matches a method already defined in the controller
# (i.e. method_1 is an existing method)
I had thought that it may work to use the dropdown option value to run the corresponding method in my controller through the form_tag action (i.e. :action => params[:report_name]), but this doesn't work because the action in the form needs to be set before the params value is set. I don't want to use javascript for this functionality.
Here is my form:
<%= form_tag("../reports/run_reports", :method => "get") do %>
<%= select_tag :report_name, options_for_select([['-- Please Select --',nil],['Option 1','method_1'], ['Option 2','method_2'], ['Option 3','method_3'], ['Option 4','method_4']]) %>
<%= submit_tag "Run Report" %>
<% end %>
Any suggestions?
Can I change my controller method to look something like this - but to actually call the controller method to run? I'm guessing this won't run because the params value is returned as a string...
def run_reports
params[:report_name]
end
WARNING: this is a terrible idea
You could call the method via a snippet of code like this in the controller:
send(params[:report_name].to_sym)
The reason this is a terrible idea is that anyone accessing the page could manually construct a request to call any method at all by injecting a request to call something hazardous. You really, really do not want to do this. You're better off setting up something to dynamically call known, trusted methods in your form.
I think you should rethink the design of your application (based on the little I know about it). You have a controller responsible for running reports, which it really shouldn't be. The controllers are to manage the connection between the web server and the rest of your app.
One solution would be to write a new class called ReportGenerator that would run the report and hand the result back to the controller, which would run any of the possible reports through a single action (for instance, show). If you need variable views you can use partials corresponding to the different kinds of reports.
As for the ReportGenerator, you'll need to be a little creative. It's entirely possible the best solution will be to have an individual class to generate each report type.

How do I store a new entry in Rails when a button is clicked?

I have a method in one of my models that, when called, fetches a tweet using the twitter gem and stores some parts of it. I'd like to be able to trigger that action from the web interface to my app. What is the Rails Way to accomplish this? I've seen some references to not calling model methods from views, so should I be doing this from within a controller somehow instead?
My method (the relevant models are Sponsor and Sponsortweet (so my model name wouldn't conflict with Tweet, from the gem):
def create_tweet
tweet = Twitter.user_timeline(self.twitter).first
self.sponsortweets.create!(content: tweet.text,
tweet_id: tweet.id,
tweet_created_at: tweet.created_at,
profile_image_url: tweet.user.profile_image_url,
from_user: tweet.from_user,)
end
EDIT:
I created a tweet method in my sponsors controller:
def tweet
#sponsor = Sponsor.find(params[:id])
#sponsor.create_tweet
end
and added the following to my config/routes.rb: match 'tweet', to: 'sponsors#tweet', via: :post.
As well as the following code in my view (I'm using haml):
= button_to :tweet, tweet_path(#sponsor)
However, clicking the button results in the following error:
Couldn't find Sponsor without an ID
Your view should have a button that posts to a specific route in your controller. That controller would then call the method in your model. Having no idea what your app actually looks like, here's an example:
EDIT includes better example
View (assuming it's a Sponsor view):
<%= button_to :submit, tweet_path %>
Controller:
def tweet
Sponsor.create_tweet
end
And your model would stay the same, except you'd change your method to a class method like so:
def self.create_tweet
...your code here...
end
Since it seems this isn't tied to any particular sponsor, you'll use a class method and thus don't need an instance of the class to call your method. That said, it seems like you would want an instance of your class at some point...
I'd be curious to hear other people's answers, as I'm now wondering if there is such a way to bypass the controller all-together.
However, my take on this is that, since Rails is an MVC (Model View Controller) framework, I think the Rails way of accomplishing what you're considering is probably to simply handle the action normally; through the controller to the model.
If I am correct in assuming you have a button or link, or perhaps some AJAX, which is initiating the server-side Twitter processing, then I would set up your routing for that URL to point to a controller action method, which would then call your model method myModel.create_tweet.

Is there a more elegant way? (accessing a rails controller from a static home page?)

OK, this is working but I feel there is a better way to do this in Rails... I have a home page which, if you have not signed in, is not currently pulling in anything from any model or controller. It exists at /pages/home.html.erb
On that page, I want to grab the next party from my Parties model and tell the website visitor about that party. Easy enough, right?:
/app/controllers/parties_controller.rb
def nextparty
#party = Party.find(:first, :order => "begins_on")
end
Now, in my home page, I used this and it works fine:
/app/views/pages/home.html.erb
<% #PartyCont = PartiesController.new() %>
<% #party = #PartyCont.nextparty() %>
<h3>The next party is <%= #party.name %></h3>
I tried helper methods, partials, ApplicationHelper, but this was the only code that actually worked. Most of the other things I tried seemed to fail because the #Party class was not instantiated (typically the error indicated the class with a temporary name and "undefined method").
Hey, I'm happy that it works, but I feel like there is a better way in Rails. I've seen a few posts that use code like the above example and then say "But you really shouldn't ever need to do this!".
Is this just fine, or is there a more Rails-like way?
UPDATE:
I think the problem is more than just elegance... I just realized that all RSPEC tests that hit the home page are failing with:
Failure/Error: get 'home'
ActionView::Template::Error:
undefined method `begins_on' for nil:NilClass
Thanks!
You want a controller behind every view and you don't want views crossing controller boundaries in order to present information. Consider having a welcome controller (or whatever you prefer to call it). It can have an index action:
def index
#party = Party.find(:first, :order => "begins_on")
end
In config/routes.rb, make it the root controller action:
root :to => "welcome#index"
Also, to DRY that up add a .nextparty class method to the Party model and call that from both of your controller actions instead of the find method.
Your view should only show data that already was made available by your controller. You want to display a party resource, so the request should go to the parties controller. If I understand your use case correctly, than more specifically to the index method on the PartiesController.
There you should have the following code:
def index
#party = Party.find(:first, :order => "begins_on")
end
That instance method will be available in your corresponding view app/views/parties/index.html.erb
<h3>The next party is <%= #party.name %></h3>
To make this available as your homepage you will have to adjust your route as well:
config/routes.rb
root :to => "parties#index"
Your view should contain as little logic as possible and mainly be concerned with how things look.
Your controller should get data for the view ready and make sure to call the right method on the model.
All the heavy business logic should be in your model.
I think you should work through a basic introductory Rails tutorial.

Resources