Route alias, is it possible? - ruby-on-rails

I have a Vehicle model:
Routes:
map.resources :vehicles, :has_many => :suppliers
Everything works great, but Vehicle has a boolean attribute is_truck. I want to make an Alias so I can get the same resources filtering only trucks, I tried with:
Routes:
map.trucks '/trucks', :controller => :vehicles, :action => :index, :is_truck => true
map.trucks '/trucks/by_supplier/:supplier', :controller => :vehicles, :action => :index, :is_truck => true
The first one works well, but when I search within a Form the second doesn't work and searches all suppliers.
Controller:
class VehiclesController
def index
if params[:supplier]
#vehicles = Vehicle.all :conditions => { :is_truck => params[:is_truck] }
else
#vehicles = Vehicle.all
end
end
...
end
Search Form:
<% form_for :truck, :url => {:controller => :trucks, :action => :index}, :html => {:method => :get} do |f| %>
<% f.text_field :search %>
<% f.submit 'Search Trucks' %>
<% end %>
Is it possible to map.resources as an alias ?

I found a cleaner way to do it, but Search is still broken under a specific supplier:
# Show all vehicles
map.connect '/vehicles/supplier/:supplier', :controller => :vehicles, :action => :index
map.resources :vehicles
# Only show trucks
map.connect '/trucks/supplier/:supplier', :controller => :vehicles, :action => :index, :is_truck => true
map.resources :vehicles, :as => 'trucks', :requirements => { :is_truck => true }
Resource: http://api.rubyonrails.org/classes/ActionController/Resources.html

Just amend your routes in the following way:
map.resources :vehicles, :has_many => :suppliers,
:collection => { :trucks => :get }
And check rake routes for the routes this generates. It will allow you to list vehicles which are trucks:
trucks_vehicles GET /vehicles/trucks(.:format)
{:controller=>"vehicles", :action=>"trucks"}
So you now just need to add a new action called "trucks" which works similar to "index". Forms should keep track on it's own (via form fields) if you create a truck or another vehicle. Don't try to trick around with rails routing, which usually would mean your app design is flawed which will get you into trouble later.
You may take a look at STI (single table inheritance: one table stores multiple classes of vehicles). Another way would be to create a trucks controller which inherits from the vehicles controllers and overwrites just some methods like so:
class TrucksController < VehiclesController
def new
#is_truck = true
super
end
...
end
or
class TrucksController < VehiclesController
before_filter :this_is_a_truck
...
private
def this_is_a_truck
#is_truck = true
super
end
end
Update: Here's another one (given you have a is_truck column):
class TrucksController < VehiclesController
around_filter :with_truck_scope
...
private
# Scope every active record access with an is_truck condition
# you may want to put this directly into the model to get rid of the .send
# method and directly access "Vehicle.with_truck_scope &block" here
def with_truck_scope(&block)
Vehicle.send :with_scope, :find => { :conditions => "is_truck = 1" },
:create => { :is_truck => 1 }, &block
end
end
But I recommend really first to try out going with the :collection and :member parameters of Rails' routing.

are you processing it in your controller?
smth like:
class VehiclesController
def index
if params[:supplier]
#vehicles = Vehicle.all :conditions => { :supplier_id => params[:supplier] }
else
#vehicles = Vehicle.all
end
end
end

Not sure if it's possible to make an alias but at least you can try to swap your routes:
map.trucks '/trucks/by_supplier/:supplier', :controller => :vehicles, :action => :index, :is_truck => true
map.trucks '/trucks', :controller => :vehicles, :action => :index, :is_truck => true
Since routes works like 'first added - has higher priority' your solution could fail since map.trucks '/trucks' catch '/trucks/by_supplier/:supplier' as well. Also I'd recommed to refactor it a bit:
map.with_options :controller => :vehicles, :action => :index, :is_truck => true do |v|
v.trucks '/trucks/by_supplier/:supplier'
v.trucks '/trucks'
end

I recommend you to use another resource, just adding:
map.resources :vehicles, :as => :trucks, :has_many => :suppliers
Then handle it in your controller with something like:
def index
conds = {}
conds = { ... } if request.uri =~ /trucks/ # you can be more specific about the regexp if you need to
#vehicles = Vehicle.all :conditions => conds
end
What do you think about it?

Related

Adding custom form into Devise edit page Ruby on Rails

I am having a problem to add custom form into Devise edit page. Currently, I have two models User and UserAddress. I splitted edit page on multiple pages, here are my routes:
devise_for :users, path: "user", path_names: {sing_in: "sign_in", sing_out: "sign_out", sing_up: "sign_up", edit: "edit"}, :controllers => { :registrations => :registrations }
devise_scope :user do
match 'user/edit' => 'registrations#edit', via: :get, :as => :user_basic
match 'user/edit/addresses' => 'registrations#addresses', via: :get, :as => :user_addresses
...
resources :user_addresses, only: [:create, :destroy]
end
resources :users, path: "user", only: [:show]
I created User Addresses controller to handle create and destroy actions:
class UserAddressesController < ApplicationController
def create
#User = User.find(params[:user_id])
if params[:addresses]
params[:addresses].each do |address|
#User.address.create(address: address)
end
#addresses = #User.addresses
redirect_back(fallback_location: request.referer, notice: "Success")
end
end
def destoy
#address = Address.find(params[:id])
#User = #address.user
#address.destroy
#addresses = Address.where(user_id: #user.id)
respond_to :js
end
end
And now I want to add the ability to add new addresses from Devise edit page (I will use ajax lately to update the records under the form), like this (haml):
= form_for user_addresses_path, url: user_addresses_path(current_user), method: 'post', :html => { class: "form", :multipart => true } do |f|
= f.text_field :name
= f.text_field :address
= f.submit
When I click submit, I am getting the following error:
No route matches [POST] "/user/edit/addresses.1"
I think that I need to move resources :user_addresses, only: [:create, :destroy] out of the devise_scope, however I don't want to change the url (www.url.com/user/edit/addresses). What am I doing wrong? Is it even possible to add custom forms into Devise? Thank you very much for your help and time.
UPDATE:
UserAddress model:
class UserAddress < ApplicationRecord
belongs_to :user, :foreign_key => 'user_id'
# Geocoder
geocoded_by :address
after_validation :geocode, :if => lambda{ |obj| obj.address_changed? }
end
Here your route user/edit/address is being generate from the match and is written via get method so you need to change it with
match 'user/edit/addresses' => 'registrations#addresses', via: :post, :as => :user_addresses
and in the form url path helper you are passing current user as a parameter while it does not exist in your route so form should get replace with
= form_for user_addresses_path, url: user_addresses_path, method: 'post', :html => { class: "form", :multipart => true } do |f|

paginate 'nested' rails routes for seo

I need to figure out how to properly use routes to create a url structure like so:
items/page/2
items/expired/page/2
I have items/page/2 working and then I have this which I want to to correct:
items/expired?page=2
I am using Kaminari to provide pretty url structure for rails 4.2 with a concern.
https://github.com/amatsuda/kaminari/#creating-friendly-urls-and-caching
My controller has two actions: index and expired
My views under items are index.html.haml and expired.html.haml
routes.rb
concern :paginatable do
get '(page/:page)', :action => :index, :on => :collection, :as => ''
end
concern :expired_paginatable do
get '(page/:page)', :action => :expired, :on => :collection, :as => ''
end
get 'items/expired', to: "items#expired", :concerns => :expired_paginatable
resources :items, :concerns => :paginatable
my views both have:
= paginate #items
I know I do not need two concerns but thought I would try it.
I ended up changing my resources block to this:
resources :items do
collection do
get 'expired/page/:page', :action => :expired
get :expired
end
concerns :paginatable
end
dropping:
concern :expired_paginatable do
get '(page/:page)', :action => :expired, :on => :collection, :as => ''
end
get 'items/expired', to: "items#expired", :concerns => :expired_paginatable
resources :items, :concerns => :paginatable

How do I point to the admin artist link correctly?

I want my artist links to look like this:
http://admin.foobar.com/artists/123
http://www.foobar.com/123
My Routes setup looks like this:
class AdminSubDomain
def matches?(request)
whitelists = IpPermission.whitelists
if whitelists.map { |whitelist| whitelist.ip }.include? request.remote_ip
request.subdomain == 'admin'
else
raise ActionController::RoutingError.new('Not Found')
end
end
end
Foobar::Application.routes.draw do
constraints AdminSubDomain.new do
..
resources :artists, :only => [:index, :show], :controller => 'admin/artists'
end
get ':id' => 'artists#show', :as => 'artist' do
..
end
end
Rake routes returns:
artist GET /artists/:id(.:format) admin/artists#show
artist GET /:id(.:format) artists#show
At the moment, <%= link_to 'Show', artist_path(artist, :subdomain => :admin) %> points to: http://admin.foobar.dev:3000/123.
It should look like: http://admin.foobar.dev:3000/artists/123
What am I doing wrong?
You have used the same name (artist) for both routes, so when you call artist_path, you get the last one you've defined, which is: get ':id' = 'artists#show', :as => 'artist' do ....
Use a different name for the admin route to distinguish it:
constraints AdminSubDomain.new do
..
resources :artists, :only => [:index, :show], :controller => 'admin/artists', :as => 'admin_artists'
end
Then you can call it with: <%= link_to 'Show', admin_artist_path(artist, :subdomain => :admin) %>.

polymorphic_path for custom collection route

I have the following route definition:
resources :documents do
collection do
post :filter
end
end
and the following model structure:
class Document < ActiveRecord::Base
belongs_to :documentable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :documents, :as => :documentable
end
and controller structure:
class DocumentsController < ApplicationController
def index
# not important
end
def filter
# not important
end
end
I can easily in a view say:
polymorphic_path([#user, Document])
to get the path /users/1/documents, but I want to be able to say:
filter_polymorphic_path([#user, Document])
to get the path /users/1/documents/filter, unfortunately, this doesn't work.
Anyone know how I can pull this off without adding the following to my routes, for each of my documentable models:
resources :users do
resources :documents do
collection do
post :filter
end
end
end
polymorphic_path([#user, Document], :action => 'filter') gives you /users/:user_id/documents/filter.
Also, polymorphic_path([#user, Document], :action => 'filter', :sort_order => 'this-order') gives you /users/:user_id/documents/filter?sort_order=this-order.
I ran into the same problem thinking you can replace the edit in edit_polymorphic_path to whatever method you want.
See: http://api.rubyonrails.org/classes/ActionDispatch/Routing/PolymorphicRoutes.html
This would do it, and it reads nicely.
polymorphic_path([:filter, #user, Document])
Or these
polymorphic_path([:filter, #user, :documents])
polymorphic_path([:filter, #user, Document.new])
And with a query param
polymorphic_path([:filter, #user, Document], :q => 'keyword')
And, in a view you can also do this:
= link_to "Documents", [[:filter, #user, :documents], :q => 'keyword']

Rails 3 routing from root show

Currently
Project101::Application.routes.draw do
match '/:id' => 'companies#show'
resources :companies do
resources :customers
resources :users
resources :categories
resources :addresses
end
devise_for :users
resources :users, :controller => "users"
root :to => "companies#index"
end
Everything belongs to a company. Trying to create routes like www.example.com/:id/customers where :id is always the company id.
At the moment www.example.com/:id works but all url's are generated as /companies/:id/cusotmers.
Saw Rails 3 Routing Resources with Variable Namespace.
Is this the right way of doing this?
EDIT
Kept :as => :company to help generate the URL's, Links, etc a little easier for me. Sure others could do cleaner or better method. Also had to manually create the edit, destroy, new with different urls so I could use them in links if user was admin.
Project101::Application.routes.draw do
match '/' => 'companies#index'
match '/companies' => 'companies#index'
match '/:company_id' => 'companies#show', :as => :show_company
match '/companies/:id/edit' => 'companies#edit', :as => :edit_company
match '/companies/:id/new' => 'companies#new', :as => :new_company
match '/companies/:id/destroy' => 'companies#destroy', :as => :delete_company
scope '/:company_id', :as => :company do
resources :customers
resources :users
resources :categories
resources :services
resources :addresses
end
devise_for :users
resources :users, :controller => "users"
root :to => "companies#index"
end
Then just used basic nested_resources for links, controllers and forms.
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_company
def current_company
if params[:company_id] != nil
#current_company ||= Company.find(params[:company_id])
else
#current_company = nil
end
return #current_company
end
end
Basic links
<%= link_to "Customers", company_customers_path(current_company) %>
links for specific customer
<%= link_to #customer.name, edit_company_customer_path(current_company, #customer) %>
Controllers look like
class CustomersController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource
def new
#company = current_company
#customer = #company.customers.new
end
def create
#customer = Customer.new(params[:customer])
if #customer.save
flash[:notice] = "Successfully created customer."
redirect_to company_customer_path(current_company, #customer)
else
render :action => 'new'
end
end
end
And finally my forms look like
<%= form_for [#company, #customer] do |f| %>
<%= f.error_messages %>
....
<% end %>
Yes, if you always want the routes to begin with the company id you can wrap them in a scope like this:
scope ":company_id" do
resources :customers
resources :users
resources :categories
resources :addresses
end

Resources