Rails: method in a view - ruby-on-rails

So, I tried searching (a lot :( ) and haven't been able to find anything to help.
I feel like this is something I've done before, but I just can't seem to figure it out.
I have installed a gem (Recommendable, yay!), that allows me access to a bunch of methods(?):
user.like(movie)
=> true
In a view, I tried putting <%= link_to "Like", #user.like(#movie) %>... however, this seems to actually just run #user.like(#movie) on page load... automatically setting that user to like that movie.
What am I missing? :\
Much thanks in advance!

You want to have a a LikesController for stuff like this I guess. And then you can remote link to the create action and inside this create action you can actually do the like you want to do:
class LikesController < ApplicationController
def create
# assuming you have some method to get you the current_user
# and assuming you just want to like movies
movie = Movie.find_by_id(params[:id])
current_user.like(movie) if movie
# maybe check for success and return some meaningfull message
end
end
and then inside the view you can do:
<%= link_to "Like", likes_path(#movie), remote: true %>
This should trigger the like.
Don't forget to create a route in routes.rb for the LikesController.
You should always keep in mind, that for an action a user should be able to take, you need an action in a controller. The view always just presents a given state to the user with options to take action.

Related

How to compare two items within Ruby on Rails?

So I'm trying to re-create GitHub version control for let's say posts. I've found a way to re-create an original post using duplicate AND another method to create a new post based on the original. Cool.
My issue is being able to display both the original and the new on the same page.
What I've attempted thus far is to just rely on the show method with having:
def show
#post = Post.find(params[:id])
end
Then in the view have in the form a checkbox to allow a user to select multiple posts, click a submit, and a new page renders displaying both side by side. Preferably showing the differences between the two but that's a wish list as I deal with this first.
Actually could I just simply do?:
def other_show
#post = Post.where(params[:id])
end
I also added in status as a boolean to help on the view for marking the checkbox. Would I then need to put something in the other_show method about the status?
If you want to "recreate" some sort of version control I suggest you use something like the audited. Instead of building your own. From your example and comments it seems you don't have a clear relation between all related (versions of) posts.
Using this gem, each change to the Post content (for example, if configured properly) would be stored as an audit.
Showing the differences is a different problem. That's usually called a diff and you can find gems that do it for you, for example: diffy
To show 2 different entities on one page you need to give posts_controller both ids.
Declare your show method like this:
def show
#original = Post.find(params[:id])
#compared = Post.find(params[:compared_id])
end
Correct route to this method will look like this:
/posts/:id?compared_id=:another_id
# Example: /posts/1?compared_id=2
To construct such a link in your view, you need to declare link_to method like this:
<%= link_to '1 <> 2', post_path(#post, compared_id: '2') %>
If you want to have a page where user can check 2 checkboxes for certain posts, you'll need to construct such href via Javascript.
But in fact I wouldn't suggest you to modify show method for such a task. It is better to use show method only for showing one entity from database. You can create another method, e.g. compare and pass both parameters there.
def compare
#original = Post.find(params[:original_id])
#compared = Post.find(params[:compared_id])
end
In routes.rb
resources :posts do
get 'compare', on: :collection
end
It will give you helper compare_posts_path, which will lead to /posts/compare and you'll need to pass original_id and compared_id to it, like this:
<%= link_to 'Compare', compare_posts_path(original_id: 'some_id', compared_id: 'some_another_id') %>
It will result to
/posts/compare?original_id=some_id&compared_id=some_another_id

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 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.

Way to see which rails controller/model is serving the page?

This might be a slightly odd question, but I was wondering if anyone know a Rails shortcut/system variable or something which would allow me to keep track of which controller is serving a page and which model is called by that controller. Obviously I am building the app so I know, but I wanted to make a more general plugin that would able to get this data retroactively without manually going through it.
Is there any simple shortcut for this?
The controller and action are defined in params as params[:controller] and params[:action] but there is no placeholder for "the model" as a controller method may create many instances of models.
You may want to create some kind of helper method to assist if you want:
def request_controller
params[:controller]
end
def request_action
params[:action]
end
def request_model
#request_model
end
def request_model=(value)
#request_model = value
end
You would have to explicitly set the model when you load it when servicing a request:
#user = User.find(params[:id])
self.request_model = #user
There are a number of ways that I know of:
First you can do rake routes and check out the list of routes.
Second you could put <%= "#{controller_name}/#{action_name}" %> in your application.html.erb and look at the view to see what it says. if you put it at the extreme bottom you'll always get that information at the bottom of the page.
The controller can be accessed through the params hash: params[:controller]. There isn't really a way to get the model used by a controller though, because there is no necessary correlation between any controller and any model. If you have an instance of the model, you could check object.class to get the model's class name.

Resources