Collecting first segments of all routes in rails 3 - ruby-on-rails

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.

Related

Rails 3 route helpers in model not respecting sub-URI

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

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.

Rails Paperclip, DRY configuration

In order to DRY up my code for attachments pictures, I created an initializer to override the #default_options variable used by Paperclip.
This way, I don't have to specify again and again the url, path and storage I want.
I'd like to go a step further and include the validation in it but I can't make it work...
Any Idea?
EDIT 1: I want at least to validate both presence and size.
EDIT 2: Part of my current code
module Paperclip
class Attachment
def self.default_options
if Rails.env != "production"
#default_options = {
:url => "/assets/:class/:attachment/:id/:style/:normalized_name",
:path => ":rails_root/public/assets/:class/:attachment/:id/:style/:normalized_name",
:default_style => :original,
:storage => :filesystem,
:whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
}
else
...
end
end
end
normalized_name is an outside function, feat: http://blog.wyeworks.com/2009/7/13/paperclip-file-rename
EDIT 3:
This blog: http://omgsean.com/2009/02/overriding-paperclip-defaults-for-your-entire-rails-app/ presnents the default_options hash with a validations key.
So it could be possible, not found yet though.
You will not be able to move the validations into a default_options hash (as these validations are performed outside the attachment class (inside a paperclip module). My thought is that if you have the same validations across all your models, you might need to look into using inheritance to decrease code duplication. I would advise against moving validations into an initializer.

Appending empty query string param to the URL in Rails

Is there a way to append something to the query string with no value set?
I would like to see this kind of URL being generated: http://local/things?magic.
What I'm looking for is when the user goes to http://local/other?magic then every URL in the generated page would contain magic in the end.
Following code is almost a solution but it gives me http://local/things?magic=. There is an extra = that I don't want there.
def default_url_options(options)
if params.has_key?("magic")
{ :magic => "" }
end
end
Setting { :magic => nil } will remove the magic entirely from the generated URLs.
Edited due to the changed question on the comment below:
Ah I see.
You could easily use the same URL for all devices and just separate by checking the request environment for HTTP_USER_AGENT.
But if this distinction is not enough (I think it is enough for more than 80% of all cases) you could do the following:
in config/routes.rb:
map.with_options :prefix => '/m', :format => 'mobile' do |mobile|
mobile.resources :apples
mobile.resource :user
mobile.connect #...
end
# generates
# /m/apples
# /m/apples/new
# ...
# /m/user
# ...
I have not tested this, but maybe you will have to add a MIME type mapping for :format => 'mobile'.
This is what cookies are for! cookies[:magic] = 'magic' and then it all automatically works just the way you're looking for. Not with URL query-string parameters, but whenever the browser requests another URL (until the browser is closed), it will include the cookie in the request.

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