Rails routing model method parameter - ruby-on-rails

I have the route
map.member 'members/:id/:name_url', :controller => 'members', :action => 'show', :requirements => { :id => /\d+/ }
and on my Member model I have a name_url method which takes the name and converts it to lowercase and changes spaces to dashes
the problem is that if I run
link_to "Bill", member
it gives me an "member_url failed to generate from" error
is there a way to achieve that? I was thinking a view helper that generated the link, but I couldn't access that from the controller if I needed to...

Assuming this is the show action of the MembersController
class MembersController
def show
#member = Member.find_by_name("Bill")
end
In app/views/members/show.html.erb, You'll want to use:
<%= link_to #member.name, member_path(#member, :name_url => "something") %>

The problem is the :name_url parameter in your route:
map.member 'members/:id/:name_url', :controller => 'members', :action => 'show', :requirements => { :id => /\d+/ }
When you pass an ActiveRecord object as an option for url_for (which is what link_to does), Rails will implicitly call the model's to_param method. Which unless overridden only returns id the id. You could override to_param, but that won't give you the url you want. Because to_param is used to create the string that replaces id in urls. The best you could do with minimum changes is settle for something like this:
members/:id
where :id is actually :id-:name_url
Really the best option is what Dan McNevin suggests. However if that's too long for you, you can always just make it a helper:
def link_to_member member
link_to member.name, member_url(member, :name_url => member.name)
end
And use it in place of link_to.
link_to "Bill", member => link_to_member member

Related

undefined method `each' for "#<Complaigns::ActiveRecord_Relation:0x00000003cfb4c8>":String

I have a model, called Complaign, with some other attributes along with the date of complaign (c_date).
In the ComplaignController, I have an index view, which displays all the complaigns. There is a filter which takes from date and to date. On filtering that, it works fine, and properly displays the complaigns fired on those dates.
Now I want the result of this query to be passed on to a different method, say export method.
I thought of passing this from the index view, since it is stored in #complaigns.
This is my index method:
def index
if params[:from] && params[:to]
from = params[:from].to_date
to = params[:to].to_date
#complaigns = Complaigns.where(:c_date => from..to)
else
#complaigns = Complaigns.all
end
end
In the index view, this is what I have written
<%= link_to "Export", {:controller => "complaigns", :action => "export", :complaigns => #complaigns}%>
This is the export method
def export
#complaigns = params[:complaigns]
end
Now, in the export view, when I do the follwing line:
#complaigns.each, I get this error--
undefined method `each' for "#<Complaign::ActiveRecord_Relation:0x00000003cfb4c8>":String
Now this is because, I think, there is no method each in String class. Is there any way, I can convert the String to Complaign type in the method export or while passing it from the index view, pass it as Complaign object instead of String? Is there any other way of doing this?
You can't pass Ruby on Rails model objects directly in the controller parameters, you can pass their corresponding ids and then load the models from the database. HTTP / Ruby on Rails is stateless. If you always go to index before export, one way how to solve this might be:
<%= link_to "Export", {:controller => "complaigns", :action => "export", :complaigns => #complaigns.map(&:id)}%>
def export
#complaigns = Complaigns.find(params[:complaigns])
end
It looks like #complaigs is passed as string instead of actual active_record relations object. You should calculate #camplaigs in export instead of sending them. Or you can pass array of ids
#complaigns.collect(&:id)
HTTP is stateless protocol, which means each request is independent from the other (unless you use cookie or session). For your export method, it knows nothing about the result of index method unless you pass enough information to it. So a simple solution can be:
index view
<%= link_to "Export", {:controller => "complaigns", :action => "export", :from => params[:from], :to => params[:to] }%>
export method
def export
if params[:from] && params[:to]
from = params[:from].to_date
to = params[:to].to_date
#complaigns = Complaigns.where(:c_date => from..to)
else
#complaigns = Complaigns.all
end
end
Here, the parameters for index is passed to export.
But this method is not DRY enough. You can consider using index for export like this:
def index
if params[:from] && params[:to]
from = params[:from].to_date
to = params[:to].to_date
#complaigns = Complaigns.where(:c_date => from..to)
else
#complaigns = Complaigns.all
end
if params[:export].present?
render 'export'
else
render 'index'
end
end
Then in your export link, you can use this:
<%= link_to "Export", {:controller => "complaigns", :action => "index", :from => params[:from], :to => params[:to], :export => true }%>
PS. These codes are not tested. Just for demonstration purpose.
You can't do what you are trying to do. The problem is that when the view is rendered, the resulting HTML is text, and therefore #complaigns is turned into it's equivalent text (the same as #complaigns.to_s).
To do what you want, you need to pass to your link, the same paramaters you used to create #camplaigns in your index view. So:
Change your link to:
<%= link_to "Export", {:controller => "complaigns", :action => "export", :to => params[:to], :from => params[:from]}%>
And then change your export method to:
def export
index
end

How do I pass more than one parameter into a link_to call where I specify controller and action?

I am using a link_to to initiate a controller method that requires two parameters in order to perform the steps I need it to take. I can't seem to get the syntax right, and I'm wondering if it is because you can't pass more than one parameter in using this particular syntax. Here is what I have so far:
<%= link_to 'Select',
{controller: 'groups',
action: 'associate_subgroup_with_org',
organization_id: organization.id,
subgroup_id: #activity.group.id},
class: 'button' %>
def associate_subgroup_with_org
#organization = Group.find(params[:organization_id])
#subgroup = Group.find(params[:subgroup_id])
#subgroup.parent_group_id = #organization.id
respond_to do |format|
format.js
end
end
The link is not working and I never enter my controller action associate_subgroup_with_org. Can someone help me get the syntax right?
You can make a route like this:
get '/groups/associate_subgroup_with_org' => 'groups#associate_subgroup_with_org', :as => :associate_subgroup
And you can send any no. of parameters with link_to:
<%= link_to 'Select',
{controller: 'groups',
action: 'associate_subgroup_with_org',
organization_id: organization.id,
subgroup_id: #activity.group.id},
class: 'button' %>
Or,
<%= link_to 'Select',associate_subgroup_path(organization_id: organization.id, subgroup_id: #activity.group.id),class: 'button' %>
You need to specify it in your routes. Something like this:
get "/groups/:id/subgroup/:state" => "groups#subgroup", :as => :subgroup
And write the link like:
subgroup_path(#organization, #subgroup)
With whatever symbols you're using.
Using controller and actions in link_to/form_url is not recommended. I guess you have a groups resources, I mean in routes.rb something like resources :groups. if so then add a collection method there like:
resources :groups do
#....
post :associate_subgroup_with_org
end
Now you can use associate_subgroup_with_org_groups_path(p1: v1, p2: v2, .....)
Or you can define one named route as:
post 'groups/associate_subgroup_with_org', as: :associate_subgroup_with_org
Now you can use associate_subgroup_with_org_path(p1: v1, p2: v2, .....)
Hope its clear

How to make a custom route in Rails? By custom I mean one that reacts to params

So essentially I've setup a route to match "products/:product", which seems to respond to a page like baseurl/products/toaster and displays the toaster product. My problem is I can't seem to use link_to to generate this path, and by that I mean I don't know how. Any help on this?
There are several solutions on this one :
<%= link_to 'Toaster', { :controller => 'products', :action => 'whatever', :product => 'toaster' } %>
But it's not really Rails Way, for that you need to add :as => :product at the end of your route. This will create the product_path helper that can be used this way :
<%= link_to 'Toaster', product_path(:product => 'toaster') %>
Within your routes file you can do something like:
match "products/:product" => "products#show", :as => :product
Where the controller is ProductsController and the view is show
within the Products controller your have
def show
#product = Hub.find_by_name(params[:product])
respond_to do |format|
format.html # show.html.erb
end
end
Where whatever is in the products/:product section will be available via params.
Then, since we used :as in your routes you can do this with link_to:
<%= link_to product(#product) %>
Where #product is an instance of a product or a string. This is just an example and the param can be anything you want, the same goes for controller/action. For more info you should check out this.
Hope this helps!

Rails pass data to form for different model

I have two models, Users and Shifts.
Users: id, name
Shifts: user_id, time_length
User has_many Shifts; Shift belongs_to User. Fairly simple.
What I want to do is add a button on my show user controller (/users/1) that links to the new Shift controller view (/shifts/new). I've managed to do this with a button, as I want to pre-populate the form with the information from my Users model (i.e. send across the user.id).
I'm using the following code, which is linking fine, but can't work out how to pass the user.id details
button_to "Create Shift", {:controller => "shifts", :action => "new"},{ :method => "get"}
You can pass in extra parameters to the second argument like so:
button_to "Create Shift", { :controller => "shifts", :action => "new", :user_id => user.id }, { :method => "get" }
This should generate a URL like /shifts/new?user_id=5.
You can use Nested resources:
In routes.rb write:
map.resources :users do |users|
users.resources :shifts
end
Then the path for the new shift form would be new_user_shift_path = /users/:id/shift/new
And in the shifts_controller you can get the user like this:
#user = User.find(params[:user_id])
Then you put it in your form as a hidden tag field. (Or it won't be necessary - I don't know exactly)

Pretty (dated) RESTful URLs in Rails

I'd like my website to have URLs looking like this:
example.com/2010/02/my-first-post
I have my Post model with slug field ('my-first-post') and published_on field (from which we will deduct the year and month parts in the url).
I want my Post model to be RESTful, so things like url_for(#post) work like they should, ie: it should generate the aforementioned url.
Is there a way to do this? I know you need to override to_param and have map.resources :posts with :requirements option set, but I cannot get it all to work.
I have it almost done, I'm 90% there. Using resource_hacks plugin I can achieve this:
map.resources :posts, :member_path => '/:year/:month/:slug',
:member_path_requirements => {:year => /[\d]{4}/, :month => /[\d]{2}/, :slug => /[a-z0-9\-]+/}
rake routes
(...)
post GET /:year/:month/:slug(.:format) {:controller=>"posts", :action=>"show"}
and in the view:
<%= link_to 'post', post_path(:slug => #post.slug, :year => '2010', :month => '02') %>
generates proper example.com/2010/02/my-first-post link.
I would like this to work too:
<%= link_to 'post', post_path(#post) %>
But it needs overriding the to_param method in the model. Should be fairly easy, except for the fact, that to_param must return String, not Hash as I'd like it.
class Post < ActiveRecord::Base
def to_param
{:slug => 'my-first-post', :year => '2010', :month => '02'}
end
end
Results in can't convert Hash into String error.
This seems to be ignored:
def to_param
'2010/02/my-first-post'
end
as it results in error: post_url failed to generate from {:action=>"show", :year=>#<Post id: 1, title: (...) (it wrongly assigns #post object to the :year key). I'm kind of clueless at how to hack it.
Pretty URLs for Rails 3.x and Rails 2.x without the need for any external plugin, but with a little hack, unfortunately.
routes.rb
map.resources :posts, :except => [:show]
map.post '/:year/:month/:slug', :controller => :posts, :action => :show, :year => /\d{4}/, :month => /\d{2}/, :slug => /[a-z0-9\-]+/
application_controller.rb
def default_url_options(options = {})
# resource hack so that url_for(#post) works like it should
if options[:controller] == 'posts' && options[:action] == 'show'
options[:year] = #post.year
options[:month] = #post.month
end
options
end
post.rb
def to_param # optional
slug
end
def year
published_on.year
end
def month
published_on.strftime('%m')
end
view
<%= link_to 'post', #post %>
Note, for Rails 3.x you might want to use this route definition:
resources :posts
match '/:year/:month/:slug', :to => "posts#show", :as => :post, :year => /\d{4}/, :month => /\d{2}/, :slug => /[a-z0-9\-]+/
Is there any badge for answering your own question? ;)
Btw: the routing_test file is a good place to see what you can do with Rails routing.
Update: Using default_url_options is a dead end. The posted solution works only when there is #post variable defined in the controller. If there is, for example, #posts variable with Array of posts, we are out of luck (becase default_url_options doesn't have access to view variables, like p in #posts.each do |p|.
So this is still an open problem. Somebody help?
It's still a hack, but the following works:
In application_controller.rb:
def url_for(options = {})
if options[:year].class.to_s == 'Post'
post = options[:year]
options[:year] = post.year
options[:month] = post.month
options[:slug] = post.slug
end
super(options)
end
And the following will work (both in Rails 2.3.x and 3.0.0):
url_for(#post)
post_path(#post)
link_to #post.title, #post
etc.
This is the answer from some nice soul for a similar question of mine, url_for of a custom RESTful resource (composite key; not just id).
Ryan Bates talked about it in his screen cast "how to add custom routes, make some parameters optional, and add requirements for other parameters."
http://railscasts.com/episodes/70-custom-routes
This might be helpful. You can define a default_url_options method in your ApplicationController that receives a Hash of options that were passed to the url helper and returns a Hash of additional options that you want to use for those urls.
If a post is given as a parameter to post_path, it will be assigned to the first (unnassigned) parameter of the route. Haven't tested it, but it might work:
def default_url_options(options = {})
if options[:controller] == "posts" && options[:year].is_a?Post
post = options[:year]
{
:year => post.created_at.year,
:month => post.created_at.month,
:slug => post.slug
}
else
{}
end
end
I'm in the similar situation, where a post has a language parameter and slug parameter. Writing post_path(#post) sends this hash to the default_url_options method:
{:language=>#<Post id: 1, ...>, :controller=>"posts", :action=>"show"}
UPDATE: There's a problem that you can't override url parameters from that method. The parameters passed to the url helper take precedence. So you could do something like:
post_path(:slug => #post)
and:
def default_url_options(options = {})
if options[:controller] == "posts" && options[:slug].is_a?Post
{
:year => options[:slug].created_at.year,
:month => options[:slug].created_at.month
}
else
{}
end
end
This would work if Post.to_param returned the slug. You would only need to add the year and month to the hash.
You could just save yourself the stress and use friendly_id. Its awesome, does the job and you could look at a screencast by Ryan Bates to get started.

Resources