Why was :overwrite_params deprecated in Rails 3 - ruby-on-rails

With overwrite_params, I was doing this to prepare a PDF request for a page:
url_for(:overwrite_params => {:format => :pdf})
overwrite_params has been deprecated after Rails 2.3.8, is there a reason for this deprecation? What's the standard accepted alternative?

This is what your looking for:
url_for params.merge(:format => "PDF", :only_path => false)
This will create an absolute url for the current page including the current params.

Note that the suggested solution of merging with params is not correct as it also contains data from the request body (sometimes misleadingly referred to as POST variables). A better (the correct?) solution is to merge with request.GET instead (another misnomer as e.g. POST requests obviously may contain query parameters, too).

Using overwrite_params is allowing you to make mistakes and bad design - you are not protected from overwriting params in urls which are not relevant to your call.
It is much better aways know what is coming. Keep your params in a bean/model and serialize this bean to url.
If you want to break the rules of the good design you can aways do it yourself(with the .merge option) but then it is your responsibility.

I am guessing they deprecated the method to simplify it, so you just have to pass in the parameters you want to overwrite, instead of writing :overwrite_params. It makes sense that this method should overwrite the parameters by default.. if you are specifying parameters you obviously want to use the specified values instead of the existing.
You should be able to do it like this instead:
url_for(:format => :pdf)
A quick test on my users index page returns this:
/users.pdf

Related

Intricate Rails 3 Routing Configuration - Can routes.rb Access Initializers' Code?

I'm converting a bunch of landing pages written in php in order to add them to my RoR-based site (that's been live for more than 2 years now). These landing pages are divided into several versions, but unfortunately there is no consistency as far as URL names go. My problem is that the php pages I'm converting already have a high page rank, therefore I'd like to keep their URLs exactly the way it was.
I'm not sure how to set my routes.rb so that example.com/* will always go to my homepage; however, when (* == 'name-of-one-of-the-landing-pages') Rails will route to a separate controller, where a specific action will determine which page to render, based on an Initializer and the params hash, all this while the URL is, as mentioned, identical to what it was prior to the php-to-RoR conversion, namely www.example.com/name_of_landing_page, rather than www.example.com/*controller_name*/name_of_landing_page.
I know of the :path property that enables one to exclude the controller name from the path if passed an empty string (i.e. resources :examples, :path => ''), but that doesn't quite solve the entire problem.
I was thinking about writing an initializer that would hold a hash of all relevant landing pages, and using constraints in routes.rb to check against it, but I'm not sure if this kind of implementation is possible and how to go about it. A code example would be much appreciated.
Is there some kind of syntax for routes.rb that would enable me to do so, or perhaps a better solution?
To answer the first question: in routes.rb, inside the do/end block you will actually be in the context of ActionDispatch::Routing::Mapper, so no you won't. But, right after that block, you are back to the top level of your application and will have access to whatever variables you initialized inside your initializers, however, that code might be better suited to go in application.rb.
The only thing you should be doing in routes.rb is defining routes.
You could also handle the request for the legacy pages in rack
def call(env)
request = Rack::Request.new(env)
return [200, {"Location" => request.url("http://www.example.com")} if request.host == "www.oldpage.com"
end
More info here: http://railscasts.com/episodes/222-rack-in-rails-3
I don't understand why complex things involved. Setting such should be very simple in route.
Suppose your have a controller to handle static pages named "PagesController"
get 'name-of-one-of-the-landing-pages-a', to: 'pages#a'
get 'name-of-one-of-the-landing-pages-b', to: 'pages#b'
There is no need to add controller names in the path. You can control all of them.

Using parameter[:user_id] ,params[:user_id], params["userid"] in Rails?

While studying a Rails application I saw statements like:
parameter[:user_id]
params[:user_id]
params["userid"]
Can anyone tell me if there any major difference between them? Or, are all
fetching parameters only and can I use them interchangeably?
parameter[:user_id]
I don't think this is something official. However there's a parameters method on the current request object. See request.parameters in a controller action.
params[:user_id]
Using the params[:user_id] is the same as calling request.parameters[:user_id]. Also params[:user_id] is the same as params["user_id"]. See HashWithIndifferentAccess.
I am not sure if that's just a typo on your part, but params[:user_id] and params["userid"] are not the same, even with HashWithIndifferentAccess. The _ won't just go away so they can hold different values.
No, you need to look at the context in which each one is used. params is the normal parameter hash made available to methods in your controllers. It contains the parameters passed in as part of the request as GET or POST params.
So, given the following URL:
http://www.testsite.org/some_resource?user_id=13
The params[:user_id] would contain the value 13.
If the URL instead was:
http://www.testsite.org/some_resource?userid=13
You would need to use params[:userid] to get the value. So it all comes down to the way the URLs are made for the different controllers.
There's a third way, where you can map parts of the URL itself to params in the routes into your application. See config/routes.rb in your application. For instance with the following route:
match '/user/:user_id' => 'users#show'
You could pass in an URL like this:
http://www.testsite.org/user/13
And in your UsersController.show method you could access the user id by using params[:user_id] like normal.
The parameter hash is most likely a local copy of or reference to the params hash in a helper method or something.
params and parameters are the same. They return both GET and POST parameters in a single hash.
There is no such a thing as parameter.
ref: rails api

How can I make rails route helpers always use to_param to generate the path, even when I just pass in an ActiveRecord ID?

So, I'm implementing a pretty/SEO-friendly URL scheme for my rails app. I have a model called Artist, and I would like the Rails artist_path helper to always generate the friendly version of the path.
In my routes.rb file, I have the following line:
get 'artists/:id(/:slug)', :to => 'artists#show', :as => 'artist'
If the slug is left out, or is incorrect (it's calculated by the artist name), the controller 301 redirects to the correct URL. However, for SEO reasons, I want to ensure that all links internal to my site have the correct URL to start with.
The Artist model has the two following (very simple) functions to allow this to work:
def slug
name.parameterize
end
def to_param
"#{id}/#{slug}"
end
If I call artist_path with an artist object, this works as intended:
> app.artist_path(Artist.find 1234)
=> "/artists/1234/artist-name"
However, when I use call it with just the ID, it does not seem to use to_param at all:
> app.artist_path(id: 1234)
=> "/artists/1234"
tl;dr: How can I force Rails to always instantiate the object and call to_param on it when artist_path is called, even when only the ID is specified?
As far as I'm aware, the reason why what you're asking won't work is because when you pass in values to the built-in/automatic URL helpers (like an ID, per your example), those values just get used to "fill in the blanks" in the route URL.
If you pass an array, the values from the array will get used in order until the URL is filled in. If you pass a hash, those properties will get replaced into the URL. An object, like your model, will use it's to_param method to fill in the values... etc.
I understand your concern regarding "having knowledge of the limitations of that model," however, this behavior is standard in Rails and I don't believe it would really throw anyone. When you pass in an ID, as you do in your example, you're not telling the URL helper to "lookup a record via the model using this ID," you're simply telling it to "replace ':id' in the URL string with the ID you're providing." I'm fairly certain the built-in URL helpers wouldn't even know how to lookup the record - what model to use, etc. - other than maybe inferring from the route/controller name.
My best suggestion is to always use the instantiated model/record. If you were hoping the URL Helper would look that up for you, then there's no extra overhead as far as querying the database goes. If there's some additional reason you want to avoid instantiating the record yourself, I'd be glad to hear it and possibly provide other suggestions.

Routing without the model name

Using this question and railscast 63 I've got my articles routed to articles/article_permalink.
I'd like them to be accessible without the model name in the url so my-domain.com/article_permalink routes directly to the article. I'd only want this to happen on the show action. Is this possible?
I think you need something like ...
(in routes.rb)
match '/:id' => 'articles#show', :via => 'get'
(needs to be last, or towards the end of the routes as it can match requests intended for other routes)
To change the article_path(...) helpers, "as" might help: http://guides.rubyonrails.org/routing.html#overriding-the-named-helpers
Or you can add a helper for that specific path.
If I understand your question, you want the model tied to the route "articles/article_permalink" to be dynamic based upon which is article is selected from a list?
Would you be open to appending a model ID to the end of the URL as a query string? A more complicated approach would be to have your links POST, with the model ID as a hidden input field. Your controller could determine if it was accessed via get/post, and handle it accordingly, but that doesn't feel right.
Regardless, when the controller action is fired up based upon a request to "articles/article_permalink", it has to know which model to fetch. With HTTP being stateless, something has to be passed in. You could get fancy and write JavaScript to fire one AJAX call, set a session var, and then fire the GET, but that's messy.
I hope I understood the question...

how do I make the URL's in Ruby on Rails SEO friendly knowing a #vendor.name?

My application is in RoR
I have an action/view called showsummary where the ID has been passed into the URL, and the controller has used that to instantiate #vendor where #vendor.name is the name of a company.
I would like the URL to be, rather than showsummary/1/ to have /vendor-name in the URL instead.
How do I do that?
All of these solutions use find_by_name, which would definitely require having an index on that column and require they are unique. A better solution that we have used, sacrificing a small amount of beauty, is to use prefix the vendor name with its ID. This means that you dont have to have an index on your name column and/or require uniqueness.
vendor.rb
def to_param
normalized_name = name.gsub(' ', '-').gsub(/[^a-zA-Z0-9\_\-\.]/, '')
"#{self.id}-#{normalized_name}"
end
So this would give you URLs like
/1-Acme
/19-Safeway
etc
Then in your show action you can still use
Vendor.find(params[:id])
as that method will implicitly call .to_i on its argument, and calling to_i on such a string will always return the numerical prefix and drop the remaining text- its all fluff at that point.
The above assumes you are using the default route of /:controller/:action/:id, which would make your URLs look like
/vendors/show/1-Acme
But if you want them to just look
/1-Acme
Then have a route like
map.show_vendor '/:id', :controller => 'vendors', :action => 'show'
This would imply that that it would pretty much swallow alot of URLs that you probably wouldnt want it too. Take warning.
I thought I'd mention String#parameterize, as a supplement to the tagged answer.
def to_param
"#{id}-#{name.parameterize}"
end
It'll filter out hyphenated characters, replace spaces with dashes etc.
Ryan Bates has a great screencast on this very subject.
Basically you overload the to_param method in the Vendor model.
def to_param
permalink
end
Then when you look up the resource in your controller you do something like this:
#vender = Vender.find_by_name(params[:id])
But the problem with this is that you'll have to make sure that the vendors' names are unique. If they can't be then do the other solution that Ryan suggests where he prepends the the id to the name and then parses the resulting uri to find the item id.
You do this by modifying the routes that are used to access those URL's and changing them to use :name, rather than :id. This will probably mean that you have to write the routes yourself rather than relying on resources.
For instance add this to the routes.rb file:
map.with_options :controller => "vendor" do |vendor|
vendor.connect "/vendor/:name", :action => "show"
# more routes here for update, delete, new, etc as required
end
The other change that will be required is that now you'll have to find the vendor object in the database by the name not the id, so:
#vendor = Vendor.find_by_name(params[:name])
Internally (at least to my knowledge/experimentation) whatever parameter name is not specified in the URL part of the route (i.e. not within the "/Controller/Action/:id" part of it) is tacked on to the end as a parameter.
Friendly ID
http://github.com/norman/friendly_id/blob/26b373414eba639a773e61ac595bb9c1424f6c0b/README.rdoc
I'd have to experiment a bit to get it right, but there's two primary parts to the solution.
1) Add a route.
in config/routes, add a line that sends requests of the form baseurl/controller/:vendor-name to the action showsummary, (or maybe a new action, show_summary_by_vendor_name)
[also, if you planned on using baseurl/:vendorname, that's fine too]
For convenience, make sure the parameter is something like :vendor-name, not the default :id
2) Write the controller action.
In the controller file, either edit your showsummary action to differentiate based on whether it's called with an id or with a vendorname, or just write a show_summary_by_vendor_name. (depending on best practices, and what route you wrote in 1. I don't know off the top of my head which is preferable)
You can then do
#vendor = Vendors.find_by_name(params[:vendor_name])
or something like that, and then render it the way you would in regular showsummary.
3) Use that as the link.
Once you confirm that baseurl[/controller?]/vendor-name works, and shows the summary, make sure all the links in your application, and elsewhere, use that link. Off the top of my head, I can't remember how difficult it is to integrate a custom route into link_to, but I think it's doable. Most search engines [google] rely heavily on links, so good SEO will have you using those named links, not the numbered ones. I think. I don't know much about SEO.
Take a look also at this quck start to SEO for Rails

Resources