Overriding Rails Default Routing Helpers - ruby-on-rails

I'm writing an app where I need to override the default routing helpers for a model. So if I have a model named Model, with the corresponding helper model_path() which generates "/model/[id]". I'd like to override that helper to generate "/something/[model.name]". I know I can do this in a view helper, but is there a way to override it at the routing level?

You can define to_param on your model. It's return value is going to be used in generated URLs as the id.
class Thing
def to_param
name
end
end
The you can adapt your routes to scope your resource like so
scope "/something" do
resources :things
end
Alternatively, you could also use sub-resources is applicable.
Finally you need to adapt your controller as Thing.find(params[:id]) will not work obviously.
class ThingsController < ApplicationController
def show
#thing = Thing.where(:name => params[:id).first
end
end
You probably want to make sure that the name of your Thing is unique as you will observe strange things if it is not.
To save the hassle from implementing all of this yourself, you might also be interested in friendly_id which gives you this and some additional behavior (e.g. for using generated slugs)

You need the scope in routes.rb
scope "/something" do
resources :models
end

Related

rails path helper not recognized in model

In my rails application I have a teams model. My route.rb file for teams looks like this:
resources :teams
In my teams_controller.rb file the line team_path(Team.first.id) works however the team_path url helper is not recognized in my model team.rb. I get this error message:
undefined local variable or method `team_path' for # <Class:0x00000101705e98>
from /usr/local/rvm/gems/ruby-1.9.3-p392/gems/activerecord-4.1.1/lib/active_record/dynamic_matchers.rb:26:in `method_missing'
I need to find a way for the model to recognize the team_path path helper.
You should be able to call the url_helpers this way:
Rails.application.routes.url_helpers.team_path(Team.first.id)
Consider solving this as suggested in the Rails API docs for ActionDispatch::Routing::UrlFor:
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
# you can do that by including Rails.application.routes.url_helpers in your class:
#
# class User < ActiveRecord::Base
# include Rails.application.routes.url_helpers
#
# def base_uri
# user_path(self)
# end
# end
#
# User.find(1).base_uri # => "/users/1"
In the case of the Team model from the question, try this:
# app/models/team.rb
class Team < ActiveRecord::Base
include Rails.application.routes.url_helpers
def base_uri
team_path(self)
end
end
Here is an alternative technique which I prefer as it adds fewer methods to the model.
Avoid the include and use url_helpers from the routes object instead:
class Team < ActiveRecord::Base
delegate :url_helpers, to: 'Rails.application.routes'
def base_uri
url_helpers.team_path(self)
end
end
Models are not supposed to be dealing with things like paths, redirects or any of that stuff. Those things are purely constructions of the view or the controller.
The model really should be just that; a model of the thing that you are creating. It should fully describe this thing, allow you to find instances of it, make changes to it, perform validations upon it... But that model wouldn't have any notion of what path should be used for anything, even itself.
A common saying in the Rails world is that if you're finding it difficult to do something (like call a path helper from a model) you are doing it wrongly. Take this to mean that even if something is possible, if it is hard to do in Rails it is likely not the best way to do it.
to add on the previous answer you can use Rails.application.routes.url_helpers just add in route :as like the following example:
get "sessions/destroy/:param_id", as: :logout
so you can use it as following:
Rails.application.routes.url_helpers.logout_path(:param_id => your_value)
Hopefully, this would help

How to use plain Ruby object with url helpers

I am rendering a page from a simple custom model (not ActiveRecord, plain ActiveModel) and I cannot get the url/path helpers to generate an url with their id, like this:
person_path(model)
# I want: /person/3
# I get: /person
Is there any concrete class I must inherit or function to implement so the url helpers work with my custom model?
I heard about to_param but it is not working, at least not with this:
class Person
include ActiveModel::Model
def id
3
end
def to_param
id.to_s
end
end
According the documentation that should work:
Any class that includes ActiveModel::Model can be used with form_for,
render and any other Action View helper methods, just like Active
Record objects.
But I guess there is still a missing function needed for the url helpers to work
You need to define a persisted? method that returns true: the default implementation always returns false, which causes rails to generate a path with no id.
It would be good if you can share the code from config/routes.rb or at least the result from rake routes.
Double check your routes again. I think you may have defined the route as a singular resource resource :person which will not add an ID to the url.

Rails creates routes from objects

I have a routes file that looks something like this (It's very deeply nested...I know):
scope :admin, module: :admin do
namespace :breadth do
resources :areas, as: 'areas' do
resources :sequences, as: 'sequences'
end
end
end
When running rake routes I get a back all of the routes including one called breatdh_area_sequences (which is exactly what I want). The problem is that when I create a form rails builds up the wrong url based on the parameters I'm giving it:
= form_for [:breadth, breadth_sequence.area, breadth_sequence] do |f|
...
This gives me:
undefined method `breadth_breadth_area_breadth_sequences_path' for #<#<Class:0x007fe55bcf16b8>:0x007fe55d9a3f68>
Exactly how does rails take this array of parameters and create a URL path out of it? I'm assuming it calls a method on each object? Is this something I can override in order to get the named route I'm expecting (not overriding :url on form_for)
Since you want to name your objects differently from the routes, it won't work. Your routes/controllers need to be named just as your models are named, if your model is BreadthArea (as the route believes it is) your route should also be named in the same way.
In order to make this work I had to override the self.model_name methods on both BreadthArea and BreadthSequence. This method is how form_for figures out the URL when an object (or an array of objects) is used at the first argument.
class BreadthArea < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new(self, nil, "Area")
end
...
end
class BreadthSequence < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new(self, nil, "Sequence")
end
...
end

Pretty Paths in Rails

I have a category model and I'm routing it using the default scaffolding of resources :categories. I'm wondering if there's a way to change the paths from /category/:id to /category/:name. I added:
match "/categories/:name" => "categories#show"
above the resources line in routes.rb and changed the show action in the controller to do:
#category = Category.find_by_name(params[:name])
it works, but the 'magic paths' such as link_to some_category still use the :id format.
Is there a way to do this? If this is a bad idea (due to some possible way in which rails works internally), is there another way to accomplish this? So that /categories/music, for example, and /categories/3 both work?
Rails has a nifty model instance method called to_param, and it's what the paths use. It defaults to id, but you can override it and produce something like:
class Category < ActiveRecord::Base
def to_param
name
end
end
cat = Category.find_by_name('music')
category_path(cat) # => "/categories/music"
For more info, check the Rails documentation for to_param.
EDIT:
When it comes to category names which aren't ideal for URLs, you have multiple options. One is, as you say, to gsub whitespaces with hyphens and vice versa when finding the record. However, a safer option would be to create another column on the categories table called name_param (or similar). Then, you can use it instead of the name for, well, all path and URL related business. Use the parameterize inflector to create a URL-safe string. Here's how I'd do it:
class Category < ActiveRecord::Base
after_save :create_name_param
def to_param
name_param
end
private
def create_name_param
self.name_param = name.parameterize
end
end
# Hypothetical
cat = Category.create(:name => 'My. Kewl. Category!!!')
category_path(cat) # => "/categories/my-kewl-category"
# Controller
#category = Category.find_by_name_param(param[:id]) # <Category id: 123, name: 'My. Kewl. Category!!!'>
If you don't want to to break existing code that relying on model id you could define your to_param like this:
def to_param
"#{id}-#{name}"
end
so your url will be: http://path/1-some-model and you still can load your model with Model.find(params[:id]) because:
"123-hello-world".to_i
=> 123
Although possibly more than you need, you may also want to look into 'human readable urls' support like friendly_id or one of the others (for instance, if you need unicode support, etc.) that are described here at Ruby Toolbox.

Rails routing - custom routes for Resources

I'm building currently one Rails app and I'd like to stick to all those fancy things like REST and Resources, but I'd like to customise my routes a little. I want my GET route to be little more verbose - the app I'm creating is a simple blog, so instead of GET /posts/1 I'd prefer something like GET /posts/1-my-first-post.
Any ideas how to do this? Didn't find anything on the web.
Routes:
map.resources :posts
Model:
class Post < ActiveRecord::Base
def to_param
"#{id.to_s}-#{slug}"
end
end
Should do the trick.
Btw: http://railscasts.com/episodes/63-model-name-in-url
Define a to_param method in your Model and all the url helpers will youse what you return with that method, e.g.:
class Post < ActiveRecord::Base
der to_param
slug
end
end
You will also need to adapt your controllers for that. Replace:
Post.find(params[:id])
with:
Post.find_by_slug(params[:id])
Also note that the find method raises ActiveRecord::RecordNotFound exception when the record can't be found while using the find_by_* method no Exceptions will be raised so you need to check that manually.
You could find the friendly_id plugin useful as it will also handle redirections if you rename your slugs (thus seo friendly), handles name collisions and seamlessly integrates with the find method so you don't need to touch your controller methods (except for the redirection thingy).
Alternatively...
Add a method like this to post.rb
def path
"/posts/#{id}-#{slug}"
end
Then use the following in your views:
Alternatively...
Add a method like this to application_helper.rb
def permalink(post)
"#{post_path(post)}-#{post.slug}"
end
Then use the following in your views (using permalink(#post) instead of post_path)
<%= link_to #post.title, permalink(#post) %>

Resources