How to call a method and send new parameters in RoR? - ruby-on-rails

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

Related

Route skips view and calls method straight away

I am still new to rails and running into a rather weird problem (at least from my perspective):
There is a view with a form and a route and a method in a controller. Somehow the method gets called straight away instead of rendering the view, waiting for input and the passing that on to the method.
This is what it kinda looks like:
Controller
class Some::ThisController < ApplicationController
def method_a
variable_a = params[:variable_a].to_time
variable_b = #other stuff
#variable_c = # do stuff with the variable_a & variable_b
end
View (method_a.rb)
= form_tag this_method_a_path do
= text_field_tag :variable_a
= text_field_tag :variable_a
= submit_tag 'Apply'
Routes (some.rb)
The::Application.routes.draw do
namespace :some do
# leave all the unimportant stuff
match this/method_a => this#method_a, :as => :method_a
So what is my issue?
The view is not being rendered - I only get:
undefined method `to_time' for nil:NilClass
When I rename the method the view renders fine.
What do I want?
The view to render, so I can fill out the form and submit it and then have the method return whatever is in #variable_c.
I cant figure out what goes wrong. Maybe it is too late today...
You need two separate controller methods, one to render the view and one to accept the submission of the form. params[:variable_a] will be unavailable when you're in method_a because the form hasn't been submitted yet, it's just being rendered!
Try this:
class Some::ThisController < ApplicationController
def method_a
# nothing, just let Rails render the method_a view
end
# this will accept the submission of the form
def method_b
variable_a = params[:variable_a].to_time # this will now be available because the user has submitted the form
variable_b = #other stuff
#variable_c = # do stuff with the variable_a & variable_b
Add the new method to the routes:
The::Application.routes.draw do
namespace :some do
# leave all the unimportant stuff
get 'some/method_a' => 'some#method_a', :as => :method_a
post 'some/method_b' => 'some#method_b'. :as => :method_b
And now your view will be:
= form_tag method_b_path do
= text_field_tag :variable_a
= text_field_tag :variable_b
= submit_tag 'Apply'
When the user hits the submit button the params :variable_a and :variable_b will be POSTed to the method_b action in your controller and you will be able to call params[:variable_a] or b.
If you don't understand how this works maybe this will help:
User visits the path GET /some/method_a and your app receives the request at the method_a action in your controller and responds by rendering the method_a.html.erb view.
The form is rendered and the user fill out the form and clicks submit sending a new request to the method_b action in your controller. Along with this request the params from the text_fields are included and you can then use the for your computations.
Hope that's clear enough.
Well, this much depends on how you call your route.
Is it a get request? A url-defined "variable_a"? In this case, you are not defining it in you route, so it goes to null...
Or is it a post/patch request? In which case you would most probably have a form posting to a route and hence to a controller method... But you need first to render the form.
So, what you should have is:
A method in your controller to call your form view as 'get'
In your case, as simple as adding in your routes:
match "this/method_a", to: "controller#draw_form", via: 'get'
In your controller
def draw_form
render "method_a"
end
and then, when your form posts to the same url ("this/method_a" in your routes), there will be parameters to be processed by your method_a action in your controller.

calling a ruby function from rails - it takes to a view which I don't want

I want to make a ruby function called within rails. I followed this question, but I get redirected to
http://localhost:3000/my_func?arg=arg
which I don't want. I only want to execute the function when the button is clicked, but remain on the same page!
EDIT:
I modified the code, or better yet, made a scratch site just to solve this. I only did:
rails new eraseme
cd eraseme
rails g controller public home
app/controllers/public/home.html.erb
class PublicController < ApplicationController
def home
end
def my_func
end
end
app/views/public/home.html.erb
<%= button_to "Button", {:action => "my_func", :arg => "arg"} %>
What I want to do, is execute my_func when I click the button. But when I click, I get
No route matches {:action=>"my_func", :arg=>"arg", :controller=>"public"}
So, from what I can tell. I think you want to make a button that will send an AJAX/HTTP request to the Controller in order to call a function, that won't return a HTML file at completion.
In order to accomplish this, you must do the following.
1.) Set the HTTP routing for this call, this is within the config/routes.rb file.
match '/public/my_func' => 'public#my_func'
This allows your application to direct any HTTP requests with the following URL to call your method within the controller. Your button_to method will send this call with that URL due to the :action => "my_func" param.
2.) Also include the :remote => true, :form => { "data-type" => "json" } as more parameters to the button_to method.
This will transform the button from a traditonal HTTP request to an HTTP Request via AJAX request.
Let me know how this works, I think there are alternatives to using AJAX in this case, if its a very simple method then you can perhaps include it within the view as erb.

Type Error in RSpec controller spec

I am having troubles with an RSPec test. The test does a PUT with some objects in the request. The controller which receives the PUT seems to be not getting the correct values
For example, 'put :update, :id => #channel.id, :channel => #channel, :tags => #tag' Then, in the Controller, when I try to use params[:tags] there is an integer in that location. A Gist with the Spec and the controller method is at https://gist.github.com/3715021
This started happening when I upgraded from Rails 3.0.13 to 3.1.8
Any idea what might be happening here and how to resolve it?
I'm assuming that #tag is an object from your Tags model. When you give Rails an object like
`get :action, :foo => foo`
or in a url helper (e.g., foo_path(foo)),
Rails will turn your object into a parameter suitable for use in a url via the #to_param method. You're probably getting an integer because Tag#to_param returns the id of the tag in your db.
It looks like your update action, by contrast, expects params[:tags] to be a hash, presumably generated from a form that includes fields for values like tags[:name].
I can't help much more without knowing more about the relevant code. But I'm guessing what you want to do is change your test to read
put :update, :id => #channel.id, :channel => #channel, :tags => { :name => 'tag' }
or something like that, mimicking the params you'd get by actually submitting the form that PUTs to your update action.
This is difficult to help you because we don't know what you're trying to do. For example, it would be helpful if you showed more of the test (for example, the values you set as your variables) and the specific results of the test.
Anyway, is the #tags variable an arel object? and if so, are you expecting the ID as the value to be passed? If not, then you probably want to specify the attribute referenced in #tags. For example, #tags.name... Or, does #tags reference a hash, itself?

How to call a controller's method from a view?

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.

Automatically append parameters to *_url or *_path methods (Rails)

I have a particular set of views relating to one of my controllers, whereby I want any call to *_path or *_url to append a set of parameters.
Is there some magic method I can override that will let me do this? I have no idea where in the Rails code the *_path or *_url methods are even handled.
Edit for clarity: I'm looking for a way to do this such that I don't have to modify every link in every view where this needs to occur. I don't want every coder who touches this set of views to have to remember to append a parameter to every link they add to the page. The same parameter should be appended automatically. I consider a change to the *_url or *_path call a failure. Similarly, having to override every *_url or *_path call is considered a failure since a new method would have to be added/removed whenever a new link is added/removed.
You can do this by overriding url_for since all the routing methods call it.
module ApplicationHelper
def url_for(options = {})
options.reverse_merge!(#extra_url_for_options) if #extra_url_for_options
super
end
end
Now all you need to do is use a before_filter to set #extra_url_for_options to a hash to force all urls.
class MyController < ApplicationController
before_filter do { #extra_url_for_options = { :format => 'html' } }
end
Note that this will force all links to use the extra options.
Thanks to Samuel's answer, I was able to create a final working solution via a new helper, which I've included below.
module ExampleHelper
def url_for(options={})
options = case options
when String
uri = Addressable::URI.new
uri.query_values = #hash_of_additional_params
options + (options.index('?').nil? ? '?' : '&') + uri.query
when Hash
options.reverse_merge(#hash_of_additional_params)
else
options
end
super
end
end
You can try to use the with_options method. In your view you can do something like
<% with_options :my_param => "my_value" do |append| -%>
<%= append.users_path(1) %>
<% end %>
Assuming you have the users_path of course. my_param=value will be appended to the url
You could make a helper method:
def my_path(p)
"#{p}_path all the parameters I want to append"
end
and in the view use
<%= eval(my_path(whatever)) %>
Eval with give you dynamic scope, so every variable available in your view can be used in the helper. If your parameters are constant you can get rid of eval calls.

Resources