Sinatra route regex constraints? - ruby-on-rails

i rebuild a small rails (too overkill) app in sinatra. i have a route like this:
match 'verify/:name/:bundle/:license' => 'verify#index', :constraints => { :bundle => /.*/ }
how can i rebould it in sinatra in terms of the constraints attribute?
thanks!

You can either do it this way: (taken from Sinatra's documentation)
get %r{/hello/([\w]+)} do
"Hello, #{params[:captures].first}!"
end
Or inside the block itself:
get '/hello/:name' do
raise Sinatra::NotFound unless params[:name].match /\w+/
"Hello, #{params[:name]}!"
end

If you're using ruby 1.9, you can use named captures in a regex route, like so:
require 'sinatra'
get %r{verify/(?<name>\w+)/(?<bundle>.*)/(?<license>\w+)} do |name, bundle, license|
# do stuff
end

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

How to test route constraints with rspec

I'm working on an application that will be primarily served as an API (other than a few minor views, such as session/registration, which will be "standard"). I like the approach that was finalized in Railscast #350: Versioning an API, and so followed it. My routes look like:
namespace :api, :defaults => {:format => 'json'} do
scope :module => :v1, :constraints => ApiConstraints.new(:version => 1, :default => false) do
resources :posts, :only => [:create, :show, :destroy, :index]
end
scope :module => :v2, :constraints => ApiConstraints.new(:version => 2, :default => true) do
resources :posts, :only => [:create, :show, :destroy, :index]
end
end
In each route, my Constraint is a new ApiConstraints object, which is located in my ./lib folder. The class looks like this:
class ApiConstraints
def initialize(options)
#version = options[:version]
#default = options[:default]
end
def matches?(req)
#default || req.headers['Accept'].include?("application/vnd.MYAPP.v#{#version}")
end
end
Now, when testing manually, everything works as expected. In my API, I may have between 5 and 10 controllers per version, and don't want to test that the API constraints works for each individual controller, as that makes no sense. I'm looking for one spec file that tests my API constraints, but I'm unsure of where to put that spec.
I've tried adding a spec/routing/api_spec.rb file to test things, but it's not working properly, as it complains that some things aren't provided, like so:
it "should route an unversioned request to the latest version" do
expect(:get => "/api/posts", :format => "json").to route_to(:controller => "api/v1/posts")
end
The above throws an error even though the controller matches properly. It fails with the following error:
The recognized options <{"format"=>"json", "action"=>"index", "controller"=>"api/v1/posts"}>
did not match <{"controller"=>"api/v1/posts"}>,
difference: <{"format"=>"json", "action"=>"index"}>.
Notice that the controller was properly determined, but since I don't want to test for the format and action in this test, it errors out. I would like there to be 3 "API specs":
It should route an unversioned request to the latest version
It should default to the JSON format if none is specified
It should return a specified API version when requested
Does anyone have experience with writing specs for these kinds of routes? I don't want to add specs for every controller inside the API, as they're not responsible for this functionality.
Rspec's route_to matcher delegates to ActionDispatch::Assertions::RoutingAssertions#assert_recognizes
The the argument to route_to is passed in as the expected_options hash (after some pre-processing that allows it to also understand shorthand-style arguments like items#index).
The the hash that you're expecting to match the route_to matcher (i.e., {:get => "/api/posts", :format => "json"}) is not actually a well-formed argument to expect. If you look at the source, you can see that we get the path to match against via
path, query = *verb_to_path_map.values.first.split('?')
The #first is a sure sign that we're expecting a hash with just one key-value pair. So the :format => "json" component is actually just being discarded, and isn't doing anything.
The ActionDispatch assertion expects you to be matching a complete path + verb to a complete set of controller, action, & path parameters. So the rspec matcher is just passing along the limitations of the method it delegates to.
It sounds like rspec's built-in route_to matcher won't do what you want it to. So the next suggestion would be to assume ActionDispatch will do what it is supposed to do, and instead just write specs for your ApiConstraints class.
To do that, I'd first recommend not using the default spec_helper. Corey Haines has a nice gist about how to make a faster spec helper that doesn't spin up the whole rails app. It may not be perfect for your case as-is, but I just thought I'd point it out since you're just instantiating basic ruby objects here and don't really need any rails magic. You could also try requiring ActionDispatch::Request & dependencies if you don't want to stub out the request object like I do here.
That would look something like
spec/lib/api_constraint.rb
require 'active_record_spec_helper'
require_relative '../../lib/api_constraint'
describe ApiConstraint do
describe "#matches?" do
let(:req) { Object.new }
context "default version" do
before :each do
req.stub(:headers).and_return {}
#opts = { :version => nil, :default => true }
end
it "returns true regardless of version number" do
ApiConstraint.new(#opts).should match req
end
end
end
end
...aaand I'll let you figure out exactly how to set up the context/write the expectations for your other tests.

Sinatra on Rails '/' mapping

I might be missing something but how can I map "/" in Rails to execute the Sinatra application? I have:
class Core < Sinatra::Base
get '/' do
"This is root but it is caput."
end
get '/test' do
"This is test and it works"
end
end
So if I do routing like that:
match '/test' => Core
match '/'=>Core
only '/test' fires Sinatra app '/' runs the Rails. Actually I want every route to be handled by Sinatra app.
You just need to remove index.html from the public folder.
You can mount the whole app with mount Core, :at => '/' in order to let Sinatra do all the routing.
Given that Sinatra creates a DSL for defining routes (that are not accessible directly as methods), you'll probably need to add a helper method so you can specifically invoke the route:
i.e.
def launch_sinatra_app
status, headers, body = call env.merge("PATH_INFO" => '/')
[status, headers, body.map(&:upcase)] # proper rack response
end
and then in config/routes.rb for rails:
match :root => 'Core#launch_sinatra_app'

Remove Routes Specified in a Gem?

Is there a way to remove routes specified in a gem in Rails 3? The exception logger gem specifies routes which I don't want. I need to specify constraints on the routes like so:
scope :constraints => {:subdomain => 'secure', :protocol => 'https'} do
collection do
post :query
post :destroy_all
get :feed
end
end
Based on the Rails Engine docs, I thought I could create a monkey patch and add a routes file with no routes specified to the paths["config/routes"].paths Array but the file doesn't get added to ExceptionLogger::Engine.paths["config/routes"].paths
File: config/initializers/exception_logger_hacks.rb
ExceptionLogger::Engine.paths["config/routes"].paths.unshift(File.expand_path(File.join(File.dirname(__FILE__), "exception_logger_routes.rb")))
Am I way off base here? Maybe there is a better way of doing this?
It is possible to prevent Rails from loading the routes of a specific gem, this way none of the gem routes are added, so you will have to add the ones you want manually:
Add an initializer in application.rb like this:
class Application < Rails::Application
...
initializer "myinitializer", :after => "add_routing_paths" do |app|
app.routes_reloader.paths.delete_if{ |path| path.include?("NAME_OF_GEM_GOES_HERE") }
end
Here's one way that's worked for me.
It doesn't "remove" routes but lets you take control of where they match. You probably want every route requested to match something, even if it is a catch all 404 at the bottom.
Your application routes (MyApp/config/routes.rb) will be loaded first (unless you've modified the default load process). And routes matched first will take precedence.
So you could redefine the routes you want to block explicitely, or block them with a catch all route at the bottom of YourApp/config/routes.rb file.
Named routes, unfortunately, seem to follow ruby's "last definition wins" rule. So if the routes are named and your app or the engine uses those names, you need to define the routes both first (so yours match first), and last (so named routes point as you intended, not as the engine defines.)
To redefine the engine's routes after the engine adds them, create a file called something like
# config/named_routes_overrides.rb
Rails.application.routes.draw do
# put your named routes here, which you also included in config/routes.rb
end
# config/application.rb
class Application < Rails::Application
# ...
initializer 'add named route overrides' do |app|
app.routes_reloader.paths << File.expand_path('../named_routes_overrides.rb',__FILE__)
# this seems to cause these extra routes to be loaded last, so they will define named routes last.
end
end
You can test this routing sandwich in the console:
> Rails.application.routes.url_helpers.my_named_route_path
=> # before your fix, this will be the engine's named route, since it was defined last.
> Rails.application.routes.recognize_path("/route/you/want/to/stop/gem/from/controlling")
=> # before your fix, this will route to the controller and method you defined, rather than what the engine defined, because your route comes first.
After your fix, these calls should match each other.
(I posted this originally on the refinery gem google group here: https://groups.google.com/forum/?fromgroups#!topic/refinery-cms/N5F-Insm9co)

Specifying Entire Path As Optional Rails 3.0.0

I want to create a Rails 3 route with entirely optional parameters. The example broken route is:
match '(/name/:name)(/height/:height)(/weight/:weight)' => 'people#index'
Which results in 'rake:routes' yielding:
/(/name/:name)(/height/:height)(/weight/:weight)
And thus adding an initial slash to all links:
...
The route works if I specify it as:
match '/people(/name/:name)(/height/:height)(/weight/:weight)' => 'people#index'
But I want to have this as the root URL (as with the first example, which does not work). Thanks.
I don't know if this can be done with Rails' routing engine, but you can add a custom middleware to your stack and it should work just fine.
Put this in lib/custom_router.rb
class CustomRouter
def initialize(app)
#app = app
end
def call(env)
if env['REQUEST_PATH'].match(/^\/(name|height|weight)/)
%w(REQUEST_PATH PATH_INFO REQUEST_URI).each{|var| env[var] = "/people#{env[var]}" }
end
#app.call(env)
end
end
and add
config.middleware.use "CustomRouter"
to your config/application.rb.
You can then set the route like
match '/people(/name/:name)(/height/:height)(/weight/:weight)' => 'people#index'
and it will work.
Does it work if you use a separate root mapping?
root :to => 'people#index'
match '(/name/:name)(/height/:height)(/weight/:weight)' => 'people#index'
It does seem like a pretty major oversight in the new routing system.
There could be a way to hack it through a rack middleware (maybe overriding Rack::URLMap), but that's a little out of my league.

Resources