How to call a controller's method from a view? - ruby-on-rails

I'm developing a small application in Ruby-On-Rails. I want to call a controller's method from a view. This method will only perform some inserts in the database tables. What's the correct way to do this? I've tried something like this but apparently the method code is not executed:
<%= link_to 'Join', method: join_event %>

The method option in a link_to method call is actually the HTTP method, not the name of the action. It's useful for when passing the HTTP Delete option, since the RESTful routing uses the DELETE method to hit the destroy action.
What you need to do here, is setup a route for your action. Assuming it's called join_event, add the following to your routes.rb:
match '/join_event' => 'controllername#join_event', :as => 'join_event'
Be sure to change controllername to the name of the controller you are using. Then update your view as follows:
<%= link_to 'Join', join_event_path %>
The _path method is generated based on the as value in the routes file.
To organize your code, you might want to encapsulate the inserts into a static model method. So if you have a model called MyModel with a name column, you could do
class MyModel
# ...
def self.insert_examples
MyModel.create(:name => "test")
MyModel.create(:name => "test2")
MyModel.create(:name => "test3")
end
end
Then just execute it in your action via:
MyModel.insert_examples

In addition to agmcleod's answer, you can expose controller methods to the view with ActionController::Base::helper_method:
class EventsController < ApplicationController
helper_method :join_event
def join_event(event)
# ...
end
end
But in this case, I think you're best off following his advice and moving this method to the model layer, since it's interacting with the database.

Related

Rails dynamic routing to different instance variables of controller

First of all I'm newbie to rails
I have a controller like this one. The queries are working fine.
class StoreController < ApplicationController
def men_clothing
#men_clothing=Category.find_by_name("clothes").products.where(product_type: "men")
#men_clothing_tshirt=Category.find_by_name("clothes").sub_categories.find_by_name("t-shirt").products
end
Now I have a view for men_clothing in which I'm able to show all the products in #men_clothing instance variable by iterating over it.
But in my home page I have a links which I want to direct to #men_clothing_tshirt instance variable such that clicking on that link will show all the products of this instance variable.And if there is another link it should direct to a different instance variable.
How should I achieve this? Or suggest a alternative way to do this. Do explain how it works.
I know I can do that by making separate actions for each instance variable and making a view for it. But in that case I will have to make a lot of views.
Maybe you could try something similar to this link?
[:tshirt, :pant, :banana_hammock].each do |category|
get "mens_#{category}/:id", :controller => :mens, :action => :show, :type => category, :as => "mens_#{category}"
end
Then you'll get your paths that you're looking for, e.g. mens_tshirt_path, mens_pant_path, etc.
In your controller, you would change the action to change based on the incoming 'type'
class MenController < ApplicationController
before_filter :find_clothing_by_category
private
def find_clothing_by_category
#clothing = Clothes.where(category: params[:type].to_s)
end
end
your link is not redirecting to your instance, its redirecting to your action. so you have to define a new method for new link_to that, and define your #men_clothing_tshirt object in that method like this:
def your_method
#men_clothing_tshirt=Category.find_by_name("clothes").sub_categories.find_by_name("t-shirt").products
end
and in your link_to redirect to your_method:
link_to "Tshirt", your_method_path
Hope it will help.Thanks

link_to custom action but wrong method?

all, I'm trying to get a custom action to work with a put method: in the
in _post.html.erb i have a link_to statement:
<%= link_to 'End now', post, :method => :put, :action => endnow %>
routes.rb contains:
resources :posts do
member do
put :endnow
end
and posts_controller.rb looks like:
class PostsController < ApplicationController
helper_method :endnow
[.. code for create, edit, destroy, etc ..]
def endnow
puts params
end
end
rake routes's relevant line looks like:
endnow_post PUT /posts/:id/endnow(.:format) posts#endnow
However, the action endnow helper doesn't run when clicking on this link.
Strangely, it does run with an index action (which i can tell from the puts command.
Of course, eventually the code for endnow will update #post, but for now, it just doesn't run properly.
Maybe i'm going about this the wrong way - all I'm trying to achieve is to update #post upon clicking the link to that post, and before showing it.
Any ideas / Alternatives?
Why not use the route helper method provided to you? Change your link to
<%= link_to 'End now', endnow_post_path(#post), method: :put %>
Things you're doing wrong:
If you want to specify the :action, use the Symbol for the action (you're missing a colon). :action => endnow should be action: :endnow
I will assume you have a #post instance variable you're passing from your controller to your action. You should be using that instead of post (unless you do in fact have a local post variable you're omitting from your code)
You are using endnow as an action; you should remove the helper_method :endnow line in your controller because it's not something you want to/should be accessing from your view.
This can all be avoided by using the route helper (for endnow_post you'd append _path to get the local route path: endnow_post_path), and pass in your #post as an argument.
Because you're trying to do a PUT request, you must make sure you have something like jquery-ujs included in your asset pipeline to convert these links to form submissions behind the scenes; browsers don't support PUT via the click of a link on their own.
As for why you're getting the template error when you get your link_to working, Rails is telling you that you need to create a app/views/posts/endnow.html.erb file. Your action has only puts params which does not terminate execution, leaving Rails to assume you still are trying to render some endnow.html.erb template.
Are there other ways to do what you're trying to do (change a single attribute of a specific model)? Sure. Are there better ways? That's pretty subjective; it may not be the most RESTful way, but it's arguably easier to deal with (if for example there are very specific authorization rules to check before updating the attribute you are modifying in endnow. Does the way you've started fleshing out work? Absolutely.
Finally, as a bump in the right direction, after you fix your link_to and remove the the helper_method as I have described above, your endnow action might look like this:
def endnow
post = Post.find!(params[:id])
post.some_attribute_here = some_new_value_here
post.save
redirect_to :root and return # <- this line sets a redirect back to your homepage and terminates execution, telling rails to do the redirect and **not** to render some endnow.html.erb file
end

How to make a model aware of its controller in Rails?

I am making a Rails application, and i would like to be able use a model object passed to a view to get the URL of some action on this object, like this, for example:
link_to object.public_send(attribute),
{ :controller => object.controller_path,
:action => :show,
:id => object.id }
What would be a good way to do this? Can it be done with a decorator like Draper? Are there some examples online?
Update. I have thought about this and decided that a decorator is not a good place to keep controller information. It is not decorator's responsibility. A decorator should only know to render formatted data with markup. For now i have created a module called Accessor where i try to mix models with controller and routing awareness. I still wonder if there is a better way to do.
If you don't mind having another instance variable on your view, you can implement this using a very simple class (no need for decorators).
class MyRouter
def initialize(controller, object)
#controller = controller
#object = object
end
def url_for(action_name)
controller.url_for(object, :action => action_name)
end
end
On your controllers:
class AController
def edit
#router = MyRouter.new(self, object)
render 'shared_view'
end
end
class BController
def edit
#router = MyRouter.new(self, object)
render 'shared_view'
end
end
And on your shared view:
<%= #router.url_for(:show) # Varies with the controller that rendered the view %>
Of course, this assumes that the controller you want as target is the same controller that renders the view, which might not be true. Still, using this pattern you can accommodate a more complex logic that suits your needs (having multiple Router classes, for instance), without having to change the view.
I've found a very interesting solution in Objects on Rails by Avdi Grimm: Exhibits for REST. In short, his idea is to apply multiple Ruby's SimpleDelegators as decorators with various functions.

Create a link to a defined method?

As my first Rails app, I'm trying to put together a simple blog application where users can vote on posts. I generated the Blogpost scaffold with a integer column (entitled "upvote") for keeping track of the vote count.
In the Blogpost model, I created a function:
def self.voteup
blogpost.upvote += 1
end
On the Blogpost index view, I'd like to create a link that does something like:
link_to "Vote up" self.voteup
But this doesn't seem to work. Is it possible to create a link to a method? If not, can you point me in the right direction to accomplish this?
What you are trying to do goes against the MVC design principles. You should do the upvoting inside a controller action. You should probably create a controller action called upvote. And pass in the post id to it. Inside the controller action you can retrive the post with the passed in ID and upvote it.
if you need serious voting in your rails app you can take a look at these gems
I assume that you need to increment upvote column in blogspots table. Redirection to a method is controllers job and we can give links to controller methods only. You can create a method in Blogposts controller like this:
def upvote_blog
blogpost = Blogpost.find(params[:id])
blogpost.upvote += 1
blogpost.save
redirect_to blogpost_path
end
In your index page,
<% #blogposts.each do |blogpost| %>
...
<%= link_to "Vote up", :action => upvote_blog, :id => blogpost.id %>
...
<% end %>
You can not map Model method to link_to in view. you can create an action in controller to access the Model method and map it using link_to, also if the action is other than CRUD, then you should define a route for the same in route.rb

How to call a method and send new parameters in RoR?

Let's say I have methodA
def methodA
note = Note.find(params[:id])
note.link = params[:link]
note.linktype = params[:linktype]
note.save
redirect_to(notes_url)
end
When I call this method from a view like this, it works fine
<%= link_to image_tag(w.link, :border =>0), methodA_path(:linktype => w.linktype, :link => w.link, :id => #note.id) %>
But, if I call the method from another method in the same controller like this:
def methodB
...
methodA(:id => params[:id], :link => link, :linktype => "image")
end
I get this error:
wrong number of arguments (1 for 0)
The parameters that methodA is getting are still the same parameters that methodB got, not the ones that I'm sending from methodB. How do I get around this problem? Thank for reading.
Several things:
The Ruby, and therefore Ruby on Rails, naming convention is to use underscore notation rather than camelcase. So it should be method_a rather than methodA.
It looks like methodA is a controller action. If you look at your method signature, you're not actually defining any method parameters. That's a good thing: actions don't take any.
The params call in the methodA action is not accessing method parameters, but is access the Rails request params hash.
In your view, you're not actually calling the method. Rather, you're linking to the action, which, when clicked, initiate a request that is routed to that action. The actual method you're calling is methodA_path, which is generating the URL. This is a shortcut to url_for that automatically fills in some parameters for you (the other ones are in the hash you're passing). This method was automatically generated for you from your routes. Do a rake routes from the root of your app for a little more information.
If you wanted to call the action method from methodB, which is probably unwise, you don't need to pass it the parameters. Since methodB is also an action being called in its own request cycle, the params hash is still available to methodA, and it will find all of those things just fine. I'd suggest, however, extracting any common functionality into a third helper method and calling that from each action; calling actions from other actions feels like a code smell to me.
A bit of a summary: methodA and methodA_path are different methods. The former takes no parameters but accesses the Rails request parameters hash, while the latter takes parameters to pass to url_for.
This is all pretty basic, so I strongly suggest you read Agile Web Development with Rails (3rd edition for Rails 2, 4th for Rails 3).
A call to method_a_path and method_a are not the same.
The method_a does not take parameters. It accesses the parameters from the params hash set to the controller instance during action invocation.
The method_a_path does not invoke method_a, it just generates the URL for invoking the method. Actual invocation happens when the user clicks on the link and the rails server processes the request.
If you want to reuse the method in a diffrent context extract the code of the action to a new method as shown below:
class PostsController < ApplicationController
def add_media
add_media_with_para(params)
end
def action2
add_media_with_para(:id => params[:id], :link => link, :linktype => "image")
end
private
def add_media_with_para p = {}
note = Note.find(p[:id])
note.link = p[:link]
note.linktype = p[:linktype]
note.save
redirect_to(notes_url)
end
end
Now in your view you can obtain the path to add_media action as follows:
posts_add_media_path(:linktype => w.linktype, :link => w.link, :id => #note.id)
I assume by addMedia you mean methodA
Note, method methodA_path is not the same as methodA. First one is automatically generated because you have a route named methodA and returns url necessary to access that route. Thus, it returns string. While methodA usually renders html template.
If you want to delegate rendering to another action, you can do something like this: redirect_to :action => :methodA, :id => 1, :otherParam => 2

Resources