Nested resources without controller names in url - ruby-on-rails

I've got a very simple Rails 3 app with a User model run by Devise and a Notes model. Right now the url and routing looks like this:
# url
/users/MEMBERNAME/notes/TITLE-OF-NOTE
# routing
resources :users do
resources :notes
end
But I would like the urls to look like this, how would the routing look like in this case?
# url
/MEMBERNAME/TITLE-OF-NOTE
Update:
Thanks, now I discovered a new problem though. In my forms I have this code:
<%= form_for([#user, #note]) do |f| %>
and in my controller I redirect like this:
format.html { redirect_to([#user, #note], :notice => 'Note was successfully created.') }
In both those cases when I use #user, #note the old urls are still present. Do you know how to translate the form and the redirects to use the member/title structure?
Thanks in advance!

You can use a custom route here:
get "/:user_id/:id", :to => "notes#show", :as => :short_user_note
Hope this helps!
Update:
To use the newly created named route:
# => /USER_NAME/NOTE_NAME
redirect_to short_user_note_path(#user, #note)
# => /user/USER_NAME/note/NOTE_NAME
redirect_to user_note_path(#user, #note)
# OR
redirect_to url_for([#user, #note])
# OR
redirect_to [#user, #note]
So, the general rule is if you pass an array of active_record objects like below to #redirect_to, #url_for or #form_for methods, the #polymorphic_url method is called internally, and generates the standard RESTful route.

You can the routes behaviour you're after with working route helpers in Rails 3 with like the following:
resources :users, :path => ''
resources :users, :path => '', :only => [] do
resources :notes, :path => '', :except => [:index]
end
See my blog post for the details on how I arrived at this solution.

Related

Routing error on Ruby on Rails 3 voting system

All I'm trying to add is a button that, when pressed, increments an "accuracy" value by 1. I've looked at many, many, StackOverflow solutions but none of them have worked for me so far. I'm working with a colleague's code, and if it helps, most of it is taken from a Ruby on Rails textbook ("Learn Rails by Example") and may be hacked together.
my view looks like:
<%= link_to 'Accurate2', :action => :vote_up, :id => #post.id%>
my code looks like:
def vote_up
#in posts_controller.rb
#post = Post.find(params[:id])
#post.rate_it( 1, current_user.id ) #with "acts_as_rateable" plugin
#post.save
my routing looks like:
resources :users do
resources :comments
resources :posts
end
resources :posts do
resources :comments
match "vote_up", :on => :collection
match "vote_down", :on => :collection
end
the error I receive is:
http://localhost:3000/posts/vote_up?id=1
CanCan::AccessDenied in PostsController#vote_up
You are not authorized to access this page.
Parameters:
{"id"=>"1"}
You're using the CanCan gem which apparently doesn't give you the necessary authorization. Check this for details:
https://github.com/ryanb/cancan/wiki/defining-abilities
Also, as a sidenote, I would recommend changing these:
match "vote_up", :on => :collection
match "vote_down", :on => :collection
to:
member do
post "vote_up"
post "vote_down"
end
and making the appropriate changes in the view:
<%= button_to 'Vote up', {:action => :vote_up, :id => #post.id} %>
The reason is that actions like these shouldn't be done with GET requests since the user may accidentally submit them multiple times by refreshing the page etc.

Rails using routes.rb to redirect old URLs

I have an old site built in Coldfusion, a new site built in Rails. I would like to redirect the old URLs to the new URLs. I'm not sure if routes is the way to go or not (I'm a noob). The URLs will be very similar. This should be easy but I'm not sure of the best method.
Old URL:
mysite.com/this-is-the-slug-right-here/
New URL:
mysite.com/blog/this-is-the-slug-right-here
Here is the issue, I have 3 "content types". The old site url didn't make a distinction between the content types. The new Rails site has a controller for each of the content types: Blog, Photo, Mobile Photo.
So in the example above /blog/ is the controller (content type) and this-is-the-slug-right-here is the permalink or slug for the content. Which I am getting like this:
#content = Content.where(:permalink => params[:id]).first
Should I be using routes.rb, or do I need some kind of catch-all script? Any help in getting me pointed in the right direction would be greatly appreciated.
Edit to further clarify
Here is a blog post: http://jyoseph.com/treadmill-desk-walk-this-way/
The new URL for this would be /blog/treadmill-desk-walk-this-way because it is a content type of blog.
And a photo post: http://jyoseph.com/berries/
The new URL for this would be /photos/berries because it is a content type of photo.
The content type is an attribute on the content model, stored in the attribute content_type.
Here's my routes.rb file:
resources :contents
match 'mophoblog/:id', :to => 'mophoblog#show'
match 'photos/:id', :to => 'photos#show'
match 'blog/:id', :to => 'blog#show'
root :to => "index#index"
match ':controller(/:action(/:id(.:format)))'
Got it using #mark's answer, here's what I ended up with.
in my routes.rb
match ':id' => 'contents#redirect', :via => :get, :as => :id
in my contents controller:
def redirect
#content = Content.where(:permalink => params[:id]).first
if #content.content_type.eql?('Photo')
redirect_to "/photos/#{#content.permalink}", :status => :moved_permanently
elsif #content.content_type.eql?('Blog')
redirect_to "/blog/#{#content.permalink}", :status => :moved_permanently
elsif #content.content_type.eql?('MoPhoBlog')
redirect_to "/mophoblog/#{#content.permalink}", :status => :moved_permanently
end
end
I'm sure this could be improved upon, specifically the way I am redirecting, but this solved my problem perfectly.
You can't use routes.rb to do this, however it's simple enough to set up a route, get the content type and redirect.
Something like:
routes.rb
match.resources :photos
match.resources :mobile_photos
match.resources :blog
#everything_else all resource and named routes before
match ':article_id' => 'articles#redirect', :via => :get, :as => :article_redirect
#articles_controller.rb
def redirect
#content = Content.find params[:id]
if #content.content_type.eql?('photo')
redirect_to photo_path(#content), :status => :moved_permanently
elsif #content.content_type.eql?('mobile_photo')
redirect_to mobile_photo_path(#content), :status => :moved_permanently
...
end
Now it occurs to me as I write this that you probably only want one controller for all this?
Just thought people might be interested of a solution to do a more general redirect:
redirector = lambda do |env|
path = env['action_dispatch.request.path_parameters'][:path]
redirect("/new-url-base#{path}").call(env)
end
match 'old-url-base:path' => redirector, :constraints => {:path => /.*/}
redirect() just returns a simple rack app that returns the redirect headers, so wrapping it in a lambda allows you to change the arguments to it. It'd probably be better for maintenance to wrap it up in an object instead:
match 'old-url-base:path' => Redirector.new("new-url-base", :path), :constraints => {:path => /.*/}
Rails 4 and later have support for redirect built in:
http://guides.rubyonrails.org/routing.html#redirection

Creating new rails action doesn't work?

i have a controller "Apps". It consists of one action "index". Now I want to add a new action called "buy":
def buy
respond_to do |format|
format.html
end
end
i added a buy.html.erb to the views, but when browsing to /apps/buy, i get following message:
Unknown action - The action 'show' could not be found for AppsController
in the routes I added this:
match '/apps/buy', :controller => 'apps', :action => 'buy'
thanks in advance!
The url is being caught by the standard /apps/:id route, I assume you also have resources :apps in your routes?
Simply place the buy route first:
match '/apps/buy', :controller => 'apps', :action => 'buy'
resources :apps
Bear in mind that routes are executed in the order they are defined, so the specific ones need to precede the general.
A simpler approach as #Ryan suggests is adding a collection route to the resource:
resources :apps, :collection => { :buy => :get }

Setting up restful routes as a total newb

I'm getting the following error:
Unknown action
No action responded to show. Actions: activate, destroy, index, org_deals, search, and suspend
Controller:
class Admin::HomepagesController < Admin::ApplicationController
def org_deals
#organization = Organization.find(:all)
end
Routes:
map.root :controller => 'main'
map.admin '/admin', :controller => 'admin/main'
map.namespace :admin do |admin|
admin.resources :organizations, :collection => {:search => :get}, :member => {:suspend => :get, :activate => :get}
To note: This is a controller inside of a controller.
Any idea why this is defaulting to show?
Update:
I updated what the routes syntax is. Read that article, and tried quite a few variations but its still adamantly looking for a show.
Firstly, it looks like your routes file has the wrong syntax. If you are trying to establish routes for nested resources, you'd do it like so:
map.resources :admin
admin.resources :organizations
end
This would give you paths such as:
/admin/
/admin/1
/admin/1/organizations
/admin/1/organizations/1
The mapping from route to controller/action is done via a Rails convention, where HTTP verbs are assigned in ways that are useful for the typical CRUD operations. For example:
/admin/1/organizations/1
would map to several actions in the OrganizationsController, distinguished by the type of verb:
/admin/1/organizations/1 # GET -> :action => :show
/admin/1/organizations/1 # PUT -> :action => :update
/admin/1/organizations/1 # DELETE -> :action => :destroy
"Show" is one of the seven standard resourceful actions that Rails gives you by default. You can exclude "show" with the directive :except => :show, or specify only the resourceful actions you want with :only => :update, for example.
I recommend you look at Rails Routing from the Outside In, which is a great introduction to this topic.
EDIT
I see I ignored the namespacing in my answer, sorry. How about this:
map.namespace(:admin) do |admin|
admin.resources :homepages, :member => { :org_deals => :get }
end
This will generate your org_deals action as a GET with an id parameter (for the organization). You also get a show route, along with the six other resourceful routes. rake routes shows this:
org_deals_admin_homepage GET /admin/homepages/:id/org_deals(.:format) {:controller=>"admin/homepages", :action=>"org_deals"}
Of course your homepages_controller.rb has to live in app/controllers/admin/
EDIT redux
Actually, you want organizations in the path, I'll bet, in which case:
map.namespace(:admin) do |admin|
admin.resources :organizations, :controller => :homepages, :member => { :org_deals => :get }
end
which gives you:
org_deals_admin_organization GET /admin/organizations/:id/org_deals(.:format) {:controller=>"admin/homepages", :action=>"org_deals"}
By specifying admin.resources ... you are telling Rails you want the seven default different routes in your application. If you do not want them, and only want the ones you specify, do not use .resources. Show is called because that's the default route called for a GET request with a path such as /admin/id when you have the default resources.

customize rails url with username

I want to copy the twitter profile page and have a url with a username "http://www.my-app.com/username" and while I can manually type this into the address bar and navigate to the profile page I can't link to the custom URL.
I think the problem is in the routes - here's the code in my routes.rb
map.connect '/:username', :controller => 'users', :action => 'show'
Also, I have Question and Answer models and I want to link to them with the customized URL like so:
http://www.my-app.com/username/question/answer/2210
There's nothing wrong with your route. Just remember to define it at the end, after defining all other routes. I would also recommend using RESTful routes and only if you want to have better looking URLs use named routes. Don't use map.connect. Here's some good reading about Rails routes.
Here's how this could look:
map.resources :questions, :path_prefix => '/:username' do |question|
question.resources :answers
end
map.resources :users
map.user '/:username', :controller => 'users', :action => 'show'
Just a draft you can extend.
To create urls you need to define to_param method for your user model (read here).
class User < ActiveRecord::Base
def to_param
username
end
end
I know this questions is old but it will help someone.
You could try the below. I've used it in a rails 4 project and all seems to be working great. The reason for the as: :admin is I also had a resources posts outside of this scope. It will add a admin to the helper calls e.g. admin_posts_path
scope ":username", module: 'admin', as: :admin do
get '', to: 'profiles#show'
resources :posts
end
I have used like this
In View part
portfolio.user.name,:id =>portfolio) %>
and in rout.rb
map.show_portfolio "portfolios/:username", :action => 'show_portfolio', :controller => 'portfolios'

Resources