More Dynamic Routes? - ruby-on-rails

I have this site where I want to be able to export all the data using CSV. There is a controller called "dataexport" and it has a method for each model. In my routes.rb file, I have this:
match "export_checkouts", :to => "dataexport/checkouts_csv"
match "export_committees", :to => "dataexport/committees_csv"
match "export_libitems", :to => "dataexport/libitems_csv"
match "export_locations", :to => "dataexport/locations_csv"
match "export_logs", :to => "dataexport/logs_csv"
match "export_patrons", :to => "dataexport/patrons_csv"
match "export_products", :to => "dataexport/products_csv"
match "export_questions", :to => "dataexport/questions_csv"
match "export_reasons", :to => "dataexport/reasons_csv"
match "export_roles", :to => "dataexport/roles_csv"
match "export_sales", :to => "dataexport/sales_csv"
match "export_shifts", :to => "dataexport/shifts_csv"
match "export_tasks", :to => "dataexport/tasks_csv"
match "export_tickets", :to => "dataexport/tickets_csv"
match "export_types", :to => "dataexport/types_csv"
match "export_users", :to => "dataexport/users_csv"
match "export_visitors", :to => "dataexport/visitors_csv"
match "export_years", :to => "dataexport/years_csv"
Is there a more dynamic way of doing this? This definitely goes against the "DRY" paradigm and was wondering if anyone could help me with this. I was thinking that you could just do this in one line by replacing the model names with a variable but I'm not quite sure how to go about doing this.

Why not just:
match "export/:model", :to => "dataexport/export_csv"
and use params[:model] to get the correct Model, then direct the dataexport controller export_csv method to ask the model for the data in CSV format like:
class DataexportController do
def export_csv
params[:model].constantize.export_csv
end
end

You could try this:
%w(checkouts committees).each do |model|
match "export_#{model}", :to => "dataexport/#{model}_csv"
end
Obviously fill out the array with all of the models you need this for.
However, whilst this cuts down on the code, you are still polluting your routes. You should consider that there might be a more Rails-way of doing this.
One thing Rails has support for is responding to different formats in controllers. So if a JSON format is requested by the browser, a JSON file is provided for by Rails (as long as you write the code for it). It sounds to me like you could just do the same thing with a CSV format.
What you are defining as "export" is really just the index method on a normal controller. It's just that rather than displaying the data as HTML, you are displaying it as CSV. I haven't really looked into this myself and so I'm not sure exactly how you would go about doing it. Something like this:
class FooController < ApplicationController
def index
respond_to do |format|
format.html #This will load your standard html index view
format.csv { #CSV stuff goes here. Perhaps you can get it to load app/views/foo/index.csv.erb somehow }
end
end
There is some discussion on this here: http://weblog.rubyonrails.org/2006/12/19/using-custom-mime-types

Related

routes.rb - Controller name not supported

I'm having quite a peculiar issue. This is the portion of routes.rb it relates to:
resources :players
match '/players/:userid', :to => 'Players#show'
When you visit localhost:3000/players/1234 it gives this error:
'Players' is not a supported controller name. This can lead to potential routing problems.
The related code in the controller:
def show
begin
if Player.find_by(:uid => :userid) then
#playerattributes = Player.find_by(:uid => :userid)
if player[:profile_complete] == true then
#playerinfo = {
:age => player[:age],
:team => player[:team],
:position => player[:position]
}
else
#playerinfo = 0
end
end
player = Player.find_by(:uid => :userid)[:info]
rescue ActiveRecord::RecordNotFound
redirect_to root_url
end
end
The problem doesn't end there. When the page does load (which it sometimes randomly works), this line acts up:
if Player.find_by(:uid => :userid) then
Using PostgreSQL, and the query gets displayed. Instead of using the :userid value from the URL (i.e. localhost:3000/players/1234 would be 1234), it just inputs the text "userid".
Am I missing something obvious?
Really appreciate any help.
Change: Players to players so:
match '/players/:userid', :to => 'Players#show'
to
match '/players/:userid', :to => 'players#show'
Read more.
To read user id value in your controller, use params[:userid], not just :userid.
Rails individual routes is going to write in this way.
EG: requested action "browser url_name(whatever you want)", to:
'controller_name#method_name'
In your case it should be:
match '/players/:userid', :to => 'players#show'
Similarly for a get request:
get '/players/:userid', :to => 'players#show'

Matching show route not generating a path?

I have a Model called UserPrice and when I make the show route match this:
match "/:id/:product_name/:purchase_date/:price", :to => "user_prices#show"
It generates no new route path to use nor does it change it in the view. Why doesn't it do this? How would I get it to do this?
You should use the as => [name] syntax:
match "/:id/:product_name/:purchase_date/:price", :to => "user_prices#show", :as => :show
will create show_path and show_url (see http://guides.rubyonrails.org/routing.html#naming-routes)

Rails 3: making a catch-all route easier to read and amend

I'm trying to write a catch-all route in Rails 3, but I want to reserve some terms in it. I'm specifically following the example put forth in this post, in the answer by David Burrows: Dynamic routes with Rails 3
The syntax I am using is the following:
match '*path' => 'router#routing', :constraints => lambda{|req| (req.env["REQUEST_PATH"] =~ /(users|my-stuff)/).nil? }
Now, that syntax works just fine - if a user visits a page with "user" or "my-stuff" in the path, it falls through the catch-all and goes to a specific place. If the user goes to any other URL, it goes to my routing logic.
My question is more about readability - is there a way I can match the route against something other than a regex? Is there a way to provide an array of terms to match against? Also, is there a way to match specific segments of the route, as opposed to the entire thing?
Obviously Rails has built-in routing, but this project has a requirement that for certain routes, the controller not be present in the URL. Hence, the catch-all.
Thanks for any help
Here's the updated routes file per the answer below:
class RouteConstraint
RESERVED_ROUTES = ['users', 'my-stuff']
def matches?(request)
!RESERVED_ROUTES.map {|r| request.path.include?(r)}.empty?
end
end
App::Application.routes.draw do
resources :categories
resources :sites
match '*path' => 'router#routing', :constraints => RouteConstraint.new
devise_for :users, :path_names =>{ :sign_in => 'login', :sign_out => 'logout', :registration => 'register' }
root :to => "router#routing"
end
You can use a class to specify the constraints if you want something cleaner once you have multiple routes to try:
class MyConstraint
BYPASSED_ROUTES = ['users', 'my-stuff']
def matches?(request)
BYPASSED_ROUTES.map {|r| request.path.include?(r)} .empty?
end
end
TwitterClone::Application.routes.draw do
match "*path" => "router#routing", :constraints => MyConstraint.new
end
This example is adapted from the rails routing guide.
It's taking a lambda; you can use whatever criteria you want.

rails routing aliases

I have a model called Spaces which has different types of places... such as Bars, Restaurants, etc. It has the same columns, same, model, controller, etc. no fancy STI, I just have one field called Space_type which I would like to determine an aliased route.
Instead of domain.com/spaces/12345 it would be /bars/12345 or /clubs/12345
Currently I have:
resources :spaces do
collection do
get :update_availables
get :update_search
get :autocomplete
end
member do
post :publish
post :scrape
end
resources :photos do
collection do
put :sort
end
end
resources :reviews
end
Also, Is there a way I can do this so that anytime I use the space_url it can figure out which one to use?
The routes are not a way to interact with your model directly. So, as long as you write a standard route, you can make things work. For instance, to make /bars/12345 and /clubs/12345 for your spaces_controller (or whatever the name of the controller is) , you can create routes like :
scope :path => '/bars', :controller => :spaces do
get '/:id' => :show_bars, :as => 'bar'
end
scope :path => '/clubs', :controller => :spaces do
get '/:id' => :show_clubs, :as => 'clubs'
end
# routes.rb
match "/:space_type/:id", :to => "spaces#show", :as => :space_type
# linking
link_to "My space", space_type_path(#space.space_type, #space.id)
which will generate this urls: /bars/123, /clubs/1 ... any space_type you have
And it looks like STI wold do this job little cleaner ;)
UPD
Also you can add constraints to prevent some collisions:
match "/:space_type/:id", :to => "spaces#show", :as => :space_type, :constraints => { :space_type => /bars|clubs|hotels/ }
And yes - it is good idea to put this rout in the bottom of all other routes
You can also wrap it as a helper (and rewrite your default space_url):
module SpacesHelper
def mod_space_url(space, *attrs)
# I don't know if you need to pluralize your space_type: space.space_type.pluralize
space_type_url(space.space_type, space.id, attrs)
end
end

Rails 3 routing - what's best practice?

I'm trying out Rails, and I've stumbled across an issue with my routing.
I have a controller named "Account" (singular), which should handle various settings for the currently logged in user.
class AccountController < ApplicationController
def index
end
def settings
end
def email_settings
end
end
How would I set-up the routes for this in a proper manner? At the moment I have:
match 'account(/:action)', :to => 'account', :as => 'account'
This however does not automagically produce methods like account_settings_path but only account_path
Is there any better practice of doing this? Remember the Account controller doesn't represent a controller for an ActiveModel.
If this is in fact the best practice, how would I generate links in my views for the actions? url_to :controller => :account, :action => :email_settings ?
Thanks!
To get named URLs to use in your views, you need to specify each route to be named in routes.rb.
match 'account', :to => 'account#index'
match 'account/settings', :to => 'account#settings'
match 'account/email_settings', :to => 'account#email_settings'
Or
scope :account, :path => 'account', :name_prefix => :account do
match '', :to => :index, :as => :index
match 'settings', :to => :settings
match 'email_settings', :to => :email_settings
end
Either works the same, it's just a matter of choice. But I do think the first method is the cleanest even if it isn't as DRY.
You could also define it as a collection on the resource:
resources :login do
collection { get :reminder, :test }
end
Of course, this also defines the default CRUD actions as well. I'm currently only using two of those for my not-an-actual-model controller, but I don't think/expect there will be any problem with the extra routes.

Resources