I would like to do something like github has with nested urls, and like How do I route user profile URLs to skip the controller? but not really sure how to go on with it.
For example, looking at a commit they have: ':user/:repo/commit/:sha', with the controller being commit. How would I replicate this type of nested resource?
thank you :)
If commit is a RESTful controller that uses :sha instead of an id to find records. You could do this instead:
map.resource :commits, :path_prefix => ':user/:repo', :as => 'commit'
It will create the standard RESTful routes that look like http://yoursite.tld/:user/:repo/commit/:id
Again, if you'll never be translating the id portion in the url to a commit id, then there's no reason you can't use it as a :sha value.
example:
class CommitController < ApplicationController
def show
#commit = Commit.find(:first, :conditions => {:sha => params[:id]})
end
...
end
You may also want to over ride to_param in the commit model to return the sha value.
class Commt < ActiveRecord::Base
...
def to_param
sha
end
end
So that now link_to commit_url(#commit, :user => current_user, :repo => #repo) will provide a url that matches your scheme.
How about
map.connect ':user/:repo/commit/:sha', :action => :index
Or use map.resource instead of map.connect if you need a RESTful route.
In the controller, the URL information can be retrieved from params, for example params[:user] returns the username.
You can name your routes as you like, and specify which controllers and actions you'd like to use them with.
For example, you might have:
map.connect ':user/:repo/commit/:sha', :controller => 'transactions', :action => 'commit'
This would send the request to the 'commit' method in 'transactions' controller.
The other variables can then be accessed in your controller using params:
#user = User.find(params[:user])
#repo = Repo.find(params[:repo])
Related
I have a custom route that looks like:
match 'firsttime' => 'registrations#new', :via => :get, \
:defaults => {:promotion_path => :firsttime}
The goal with the above route is to be able to have a url like http://www.mysite.com/firsttime which maps to the "new" method for the registrations controller, with the promotion for that registration being the "firsttime" promotion.
In one of my models, I have a shortcut method to try and generate this url:
class Registration < ActiveRecord::Base
include ActionView::Helpers
include ActionDispatch::Routing
include Rails.application.routes.url_helpers
belongs_to :promotion
def get_share_link()
promotion_path = promotion.url_path
url_to_share = url_for :controller => 'registrations', :action => 'new', :promotion_path => promotion_path
end
end
Calling the method get_share_links() fails with the error:
No route matches {:controller=>"registrations", :action=>"new",
:promotion_path=>"firsttime"}
What am I doing wrong, or am I even using the right method for this?
routes are used in the Controller. Stick this as a before filter in your controller instead.
Edit #1
Is this devise or another gem for authentication?
EDIT #2
Can you get to http://www.mysite.com/registrations/new?promotion=firsttime presuming it's localhost for now of course, since you are in dev mode.
Question #1
What do you mean by "In one of my models, I have a shortcut method to try and generate this url:" what are you generating this for and where is it used? mailings? It seems your route works, it's just not being made when you want it to, and it's unclear where you want it to be used.
Answer
does it work with only_path: false supplied as an option?
i prefer writing the routes of my Rails applications by hand and i now have a situation where i am not sure what is the best way to do things. I want to have a building controller that shows a different page for every building like :
building/town_center
building/sawmill
..
Each of the above should have its own action and view page. I would normally create a route like:
scope :path => '/building', :controller => :building do
get '/view/:slug' => :view, :as => 'view_building'
end
But this only specifies a single action that would then need to call another internal controller method to redirect to the needed template to show. So, i would like your opinion, would you just specify a different route for every building(and action) explicitly ? Or just redirect in the view_building action ?
I think you are after something like this:
match "/building/:name", :to => "buildings#show", :as => :building
Then in your controller action 'show' just render template for the building name:
render :template => 'buildings/#{params[:name]}'
I have a site listing many jobs, but I also want each account to be able to access its jobs in one place. Thus, I am using these routes:
map.resources :jobs
map.resource :account, :has_many => :jobs
This gets me URLs like localhost/jobs/ and localhost/account/jobs. However, both seem to render JobsController::index. How can I either make a conditional in the index action (how do I access whether account/jobs or just jobs was specified in the URL?) or change the account route to render a different action? What's the proper way to do this?
You can use a block when creating your routes, and then pass a :controller parameter, like so
map.resource :account do |account|
# If you have a special controller 'AccountJobsController'
account.resources :jobs, :controller => "account_jobs"
end
It may be cleaner for you to put your controllers into a directory structure, and then you can reference them in a nested way. For example:
map.resource :account do |account|
account.resources :jobs, :controller => "accounts/jobs"
end
If you use the above snippet, you should then create a controller in app/controllers/accounts/jobs_controller.rb, which is defined like so:
class Account::JobsController < ApplicationController
##
## etc.
##
end
You can always use rake routes to check which routes have been generated and which controllers they'll use.
Adding a requirement to the resource definition allows you to pass extra parameters
map.resources :jobs
map.resource :account, :has_many => :jobs, :requirements => {:account => true}
Then params[:account] will be set if the routing url was 'http://www.mysite.tld/account/jobs' and unset if it it was 'http://www.mysite.tld/jobs'
As with all other restful routing the action depends on the context.
GET requests without an id route to index.
GET requests with an id route to show
POST requests route to create
PUT requests route to update
DELETE requests route to destroy.
If you run "rake routes" you should see something like this
account_jobs GET /accounts/:account_id/jobs/:job_id {:controller => 'jobs', :action => 'index'}
This means when your action is called via the /account/jobs route you should have an :account_id parameter. You can then do your logic switch based on the existence of this param:
if params[:account_id].nil?
...
else
...
end
Right now my user profile URLs are like so:
http://example.com/users/joeschmoe
And that points to the show method in the user controller.
What I'd ideally like to do is offer user profile URLs like this:
http://example.com/joeschmoe
So, what sort of route and controller magic needs to happen to pull that off?
I disagree with what jcm says about this. It's not a terrible idea at all and is used in production by the two biggest social networks Facebook and MySpace.
The route to match http://example.com/username would look like this:
map.connect ':username', :controller => 'users', :action => 'show'
If you want to go the subdomain route and map profiles to a URL like http://username.example.com/, I recommend using the SubdomainFu plugin and the resulting route would look like:
map.root :controller => 'users', :action => 'show' , :conditions => {:subdomain => /.+/}
These broad, catch all routes should be defined last in routes.rb, so that they are of lowest priority, and more specific routes will match first.
I also recommend using a validation in your User model to eliminate the possibility of a user choosing a username that will collide with current and future routes:
class User < ActiveRecord::Base
validates_exclusion_of :username, :in => %w( messages posts blog forum admin profile )
…
end
This does not make sense unless you have no controllers. What happens when you want to name a controller the same as an existing user? What if a user creates a username the same as one of your controllers? This looks like a terrible idea. If you think the /user/ is too long try making a new custom route for /u/
So your custom route would be...
map.connect 'u/:id', :controller => 'my/usercontroller', :action => 'someaction'
In routes.rb this should do the trick:
map.connect ":login", :controller => 'users', :action => 'show'
Where login is the name of the variable passed to the show method. Be sure to put it after all other controller mappings.
Well, one thing you need is to ensure that you don't have name collisions with your users and controllers.
Once you do that you, can add a route like this:
map.connect ':username', :controller => 'users', :action => 'show'
Another thing people have done is to use subdomains and rewrite rules in the web server, so you can have http://joeshmoe.example.com
In Rails 4 to skip controller from url you have to do add path: ''.
resources :users, path: '' do
end
My User model has the usual id primary key, but it also has a unique login which can be used as an identifier. Therefore, I would like to define routes so that users can be accessed either by id or by login. Ideally, the routes would be something like this:
/users/:id (GET) => show (:id)
/users/:id (PUT) => update (:id)
...
/users/login/:login (GET) => show (:login)
/users/login/:login (PUT) => update (:login)
...
What is the best way to do this (or something similar)?
So far, the best I could come up with is this:
map.resources :users
map.resources :users_by_login,
:controller => "User",
:only => [:show, :edit, :update, :destroy],
:requirements => {:by_login => true}
The usual RESTful routes are created for users, and on top of that, the users_by_login resource adds the following routes (and only those):
GET /users_by_login/:id/edit
GET /users_by_login/:id/edit.:format
GET /users_by_login/:id
GET /users_by_login/:id.:format
PUT /users_by_login/:id
PUT /users_by_login/:id.:format
DELETE /users_by_login/:id
DELETE /users_by_login/:id.:format
These routes are actually mapped to the UserController as well (for the show/edit/update/destroy methods only). An extra by_login parameter is added (equal to true): this way, the UserController methods can tell whether the id parameter represents a login or an id.
It does the job, but I wish there was a better way.
Just check to see if the ID passed to the controller methods is an integer.
if params[:id].is_a?(Integer)
#user = User.find params[:id]
else
#user = User.find_by_login params[:id]
No need to add special routes.
Actually Kyle Boon has the correct idea here. But it is slightly off. When the params variable comes in all the values are stored as strings so his example would return false every time. What you can do is this:
if params[:id].to_i.zero?
#user = User.find_by_login params[:id]
else
#user = User.find params[:id]
end
This way if the :id is an actual string Ruby just converts it to 0. You can test this out by looking at the params hash using the ruby-debug gem.
(I would have just commented but I don't have enough experience to do that yet ;)
Not exactly sure what you are doing here but this may be of some help.
You can define actions that are outside of the automatic RESTful routes that rails provides by adding a :member or :collection option.
map.resources :users, :member => { :login => [:post, :get] }
This will generate routes that look like this:
/users/:id (GET)
...
/users/:id/login (GET)
/users/:id/login (POST)
Another thing you could do just use the login as the attribute that you look up (assuming that it is unique). Check out Ryan Bates screencast on it. In your controller you would have:
def show
#user = User.find_by_login(params[:id])
...
end
He also has another screencast that may help you. The second one talks about custom named routes.