Rails 3 route helpers in model not respecting sub-URI - ruby-on-rails

Using Rails 3.2.13.
I've got Nginx and Unicorn setup to serve a Rails application from a sub-URI. I have some views where I need to send links to resources, so I'm using a path helper from with a model:
def to_exhibit()
return {
:label => self.id,
:name => self.name,
:edit_path => Rails.application.routes.url_helpers.edit_vehicle_path(self),
}
end
This will produce a URL like http://localhost:8080/vehicles/10/edit, but what I really want is http://localhost:8080/app/vehicles/10/edit (where /app is my sub-URI). This works fine when calling edit_vehicle_path directly from a view. I hacked around this problem previously by creating my own helper:
module ApplicationHelper
def self.sub_uri_path(path)
root = Rails.application.config.relative_url_root
return '%s%s' % [ root, path ]
end
end
config.relative_url_root is defined in my config/environment files. This works, but there has to be a proper way to do it, plus I don't want to have to maintain this when I inevitably forget about it a year from now.

Why dont you wrap your routes into a scope?
scope Rails.env.production? ? '/app' : '/test' do
resources :posts, :comments
...
end
See http://guides.rubyonrails.org/routing.html#controller-namespaces-and-routing

You can set it by using the :script_name parameter:
Rails.application.routes.url_helpers.edit_vehicle_path(self, :script_name => '/app')
http://zergsoft.blogspot.jp/2014/04/using-non-root-mount-point-with-rails-3.html

Related

Routing Error with Post/Put requests (Passenger Headers)

I've run into a weird problem and after a bunch of research can't get any closer. I've got several forms that upload files via Carrierwave. When I upload the information, part of the route gets cut off (I think).
For example, I have a multi-part form submitting to:
https:/domain/programs/223/add_file as POST
but on submission I get the error
No route matches [POST] "/223/add_file"
even though what's in my address bar is the complete route. And if submit the complete route as a GET request it works fine. When I run rake routes the route shows up just fine.
Here is a subset of my route:
resources :programs do
match "add_file" => "programs#add_file"
If it matters, I'm running Rails 3.2.2 with Passenger on Apache. The problem only happens on this production server, never in development.
Any ideas? I'm stuck on this one as it effects multiple routes and I've tried defining a custom route just for that form with no luck.
Update:
When I remove multi-part => true or the file_field_tag from the form it fixes the problem. It's still an issue but seems to be less about routing than about the form with file uploads.
Create passenger_extension.rb in the lib folder with this code:
Passenger 3
module PhusionPassenger
module Utils
protected
NULL = "\0".freeze
def split_by_null_into_hash(data)
args = data.split(NULL, -1)
args.pop
headers_hash = Hash.new
args.each_slice(2).to_a.each do |pair|
headers_hash[pair.first] = pair.last unless headers_hash.keys.include? pair.first
end
return headers_hash
end
end
end
Passenger 5
module PhusionPassenger
module Utils
# Utility functions that can potentially be accelerated by native_support functions.
module NativeSupportUtils
extend self
NULL = "\0".freeze
class ProcessTimes < Struct.new(:utime, :stime)
end
def split_by_null_into_hash(data)
args = data.split(NULL, -1)
args.pop
headers_hash = Hash.new
args.each_slice(2).to_a.each do |pair|
headers_hash[pair.first] = pair.last unless headers_hash.keys.include? pair.first
end
return headers_hash
end
def process_times
times = Process.times
return ProcessTimes.new((times.utime * 1_000_000).to_i,
(times.stime * 1_000_000).to_i)
end
end
end # module Utils
end # module PhusionPassenger
And then in 'config/application.rb' do:
class Application < Rails::Application
...
config.autoload_paths += %W(#{config.root}/lib)
require 'passenger_extension'
end
And then restart a webserver.
NOTICE: I'm not sure that this doesn't break any other functionality so use it on your own risk and please let me know if you find any harm from this approach.
One issue here is you're not specifying whether the route is defined on the collection or a member. Which one of these is the correct route?
programs/:id/add_file
programs/add_file
You should construct your routes like this:
resources :programs do
post 'add_file', :on => :member
end
or
resources :programs do
member do
post 'add_file'
end
end
The above will take post requests at programs/:id/add_file and send them to ProgramsController.add_file with the params[:id] as the program id.
If you want this on the collection, you could do:
resources :programs do
post 'add_file', :on => :collection
end
or
resources :programs do
collection do
post 'add_file'
end
end
This would take post requests at programs/add_file and send them to ProgramsController.add_file, but no params[:id] would be set.
In general you should always specify whether routes are on the collection or member, and you should specify which verb a route should accept (ie use 'get' or 'post' etc. instead of 'match').
Try the above and see if that solves your problem, if not please let me know and I'll take another look.
I think you may need to add
:via => [:post]
to your route specification. It seems odd that it'd work on development and not on production, but as I understand rails routing, the matcher that you've added is only going to respond to get.
Try changing your match to
match "add_file" => "programs#add_file", :via => [:post]
Also, based on the answer just submitted by Andrew, you're probably better off using the member specifier to be explicit about the fact that the operation is happening on a particular Program with a particular id, and not the collection. It also should save some code in your add_file method which is probably working hard to get the id parameter from the url.

Collecting first segments of all routes in rails 3

I'm trying to implement routes where the first segment is the profile's alias or id:
resources :profiles, :path => '' do
...
end
And I need to validate that alias is not already taken by first segments of other(higher) routes. What I have now is:
validates :alias, :exclusion => {:in => Rails.application.routes.routes.map{|r| r.path.spec.to_s.split('(').first.split('/').second}.compact.uniq },
....
In development everything is ok. In production Rails.application.routes.routes.map... returns empty array. But only inside validation in model, if I put it somewhere in view just to test it returns array of first segments of all routes as expected. What am I doing wrong or maybe there is a better solution?
I'd guess that you have a timing problem. Your routing table in Rails.application.routes probably hasn't been built when your model is loaded in production mode; but, in development mode, your model is probably being reloaded on each request so Rails.application.routes will be populated when your model is loaded and your validates call:
validates :alias, :exclusion => { :in => Rails.application.routes.routes.map { ... } }
is executed.
An easy solution would be to switch to a validation method:
class Model < ActiveRecord::Base
validate :alias_isnt_a_route, :if => :alias_changed?
private
def alias_isnt_a_route
routes = Rails.application.routes.routes.map { ... }
if(routes.include?(alias))
errors.add(:alias, "Alias #{alias} is already used for a route")
end
end
This way, you don't look at Rails.application.routes until you need to check an alias and by that time, the routes will have been loaded. You could, of course, cache the route prefix list if you wanted to.
You'll also want to add some sanity checking to your application's initialization phase. Someone in your production environment could add, say, 'pancakes' as their alias while you add a /pancakes route while developing: your validation will miss this new conflict. Something simple like this:
config.after_initialize do
Rails.application.reload_routes! # Make sure the routes have been loaded.
# Compare all the existing aliases against the route prefixes and raise an
# exception if there is a conflict.
end
in your config/application.rb would be sufficient.

scope in routes not working with Cucumber/Capybara, default scope parameter not working in WEBrick

I have this route to separate users of different agencies:
scope "/:agency" do
resources :users
end
In ApplicationController I've added this behavior to default_url_options:
def default_url_options(options={})
{ :agency => params[:agency] }
end
Everything looks ok with the server and params[:agency] reports what's in the URL.
But while testing with Cucumber + Capybara
user_path(1)
becomes
http://www.example.com/1/users
instead of
http://www.example.com/theagency/users/1
Reading a bit of documentation I tried this:
scope "/:agency", :defaults => { :agency => 'test-agency'} do
resources :users
end
The tests appear to be working (the URL are built correctly) but in fact all URLs are built with test-agency as the first parameter, whatever the parameter is.
Any idea to have the tests work with this setup?

Named routes in mounted rails engine

I'm making a small rails engine which I mount like this:
mount BasicApp::Engine => "/app"
Using this answer I have verified that all the routes in the engine are as the should be:
However - when I (inside the engine) link to a named route (defined inside the engine) I get this error
undefined local variable or method `new_post_path' for #<#<Class:0x000000065e0c08>:0x000000065d71d0>
Running "rake route" clearly verifies that "new_post" should be a named path, so I have no idea why Rails (3.1.0) can't figure it out. Any help is welcome
my config/route.rb (for the engine) look like this
BasicApp::Engine.routes.draw do
resources :posts, :path => '' do
resources :post_comments
resources :post_images
end
end
I should add that it is and isolated engine. However paths like main_app.root_path works fine - while root_path does not
The right way
I believe the best solution is to call new_post_path on the Engine's routes proxy, which is available as a helper method. In your case, the helper method will default to basic_app_engine, so you can call basic_app_engine.new_post_path in your views or helpers.
If you want, you can set the name in one of two ways.
# in engine/lib/basic_app/engine.rb:
module BasicApp
class Engine < ::Rails::Engine
engine_name 'basic'
end
end
or
# in app/config/routes.rb:
mount BasicApp::Engine => '/app', :as => 'basic'
In either case, you could then call basic.new_posts_path in your views or helpers.
Another way
Another option is to not use a mounted engine and instead have the engine add the routes directly to the app. Thoughtbot's HighVoltage does this. I don't love this solution because it is likely to cause namespace conflicts when you add many engines, but it does work.
# in engine/config/routes.rb
Rails.application.routes.draw do
resources :posts, :path => '' do
resources :post_comments
resources :post_images
end
end
# in app/config/routes.rb:
# (no mention of the engine)
On Rails 4 the engine_name directive did not work for me.
To access a named route defined in engine's routes from engine's own view or controller, I ended up using the verbose
BasicApp::Engine.routes.url_helpers.new_post_path
I recommend defining a simple helper method to make this more usable
# in /helpers/basic_app/application_helper.rb
module BasicApp::ApplicationHelper
def basic_app_engine
##basic_app_engine_url_helpers ||= BasicApp::Engine.routes.url_helpers
end
end
With this in place you can now use
basic_app_engine.new_post_path
In case you need to access your main application helper from the engine you can just use main_app:
main_app.root_path
use the below in you app to access the engine routes
MyApp::Engine.routes.url_helpers.new_post_path

Rails RESTful Routes: override params[:id] or params[:model_id] defaults

I'm trying to understand how to change this rule directly on the map.resources:
supposing I have a route:
map.resource :user, :as => ':user', :shallow => true do |user|
user.resources :docs, :shallow => true do |file|
file.resources :specs
end
end
so I would have RESTful routes like this:
/:user/docs
/docs/:id
/docs/:doc_id/specs
So I see that is difficult to track the params[:doc_id] on this case because sometimes its params[:id] and sometimes its params[:doc_id] and in this case I would like to always call for one specific name so I won't have to create two different declarations for my filters.
Well, I did a little bit of research and I found this patch:
http://dev.rubyonrails.org/ticket/6814
and basically what this does is give you the ability to add a :key parameter on you map.resources so you can defined how you would like to reference it later so we could have something like:
map.resources :docs, :key => :doc ...
so I always would call the param with params[:doc] instead.
But actually this patch is a little bit old (3 years now)
so I was wondering if we don't have anything newer and already built-in for rails to do this task?
P.S I'm not sure about that to_param method defined inside the model, apparently this didn't change anything on my requests, and on the logs I still getting:
Parameters: {"doc_id"=>"6"} or Parameters: {"id"=>"6"} all the time.
One method of making the parameters a little more friendly without writing fully custom routes is
# docs_controller.rb
def show
#doc = Doc.find(params[:doc].to_i)
end
# doc.rb
def to_param
[ id, permalink ].join("-")
# assumes you're storing a permalink formatted version of the name of your doc
end
# routes.rb
map.resources :docs
This will give you URLs that look something like example.com/docs/234-the-name-of-your-doc

Resources