Rails routing: Giving default values for path helpers - ruby

Is there some way to provide a default value to the url/path helpers?
I have an optional scope wrapping around all of my routes:
#config/routes.rb
Foo::Application.routes.draw do
scope "(:current_brand)", :constraints => { :current_brand => /(foo)|(bar)/ } do
# ... all other routes go here
end
end
I want users to be able to access the site using these URLs:
/foo/some-place
/bar/some-place
/some-place
For convenience, I'm setting up a #current_brand in my ApplicationController:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_brand
def set_brand
if params.has_key?(:current_brand)
#current_brand = Brand.find_by_slug(params[:current_brand])
else
#current_brand = Brand.find_by_slug('blah')
end
end
end
So far so good, but now I must modify all *_path and *_url calls to include the :current_brand parameter, even though it is optional. This is really ugly, IMO.
Is there some way I can make the path helpers automagically pick up on #current_brand?
Or perhaps a better way to define the scope in routes.rb?

I think you will want to do something like this:
class ApplicationController < ActionController::Base
def url_options
{ :current_brand => #current_brand }.merge(super)
end
end
This method is called automatically every time url is constructed and it's result is merged into the parameters.
For more info on this, look at: default_url_options and rails 3

In addition to CMW's answer, to get it to work with rspec, I added this hack in spec/support/default_url_options.rb
ActionDispatch::Routing::RouteSet.class_eval do
undef_method :default_url_options
def default_url_options(options={})
{ :current_brand => default_brand }
end
end

Related

Ruby wildcard on path

The following ruby code to store a previous URL is meant to exclude a certain series where the resulting action might send the user in an endless loop.
def current_user_url # store last url
if (request.fullpath != "/users/sign_in?locale=*" &&
request.fullpath != "/users/sign_up?locale=*" &&
request.fullpath != "/users/password?locale=*" &&
request.fullpath != "/users/sign_out?locale=*" &&
request.fullpath != "/users?locale=*" &&
!request.xhr?) # don't store ajax calls
session[:previous_url] = request.fullpath
end
end
when handling self-referential actions
def after_sign_in_path_for(resource)
session[:previous_url] || session[:confirm_url]
end
The wildcard character is not working as desired, as the session variable is storing, for example, /users/sign_in?locale=en notwithstanding the use of the wildcard. note: without the use of ?locale=en the function does work as expected.
How can this code become locale agnostic?
Update with note on solution
In case anyone drops by and is flustered by the second apporach of a wiki for the devise gem, you're not alone. I wasn't too thrilled instinctively with an after filter. Digging into Devise's store location method is an option, though not as straightforward as Max's solution below.
That does need a slight tweak though (and [ahem, yes] the question did not mention devise) where routes need to invoke devise_for and the controller needs to inherit from devise
devise_for :users, :controllers => { registrations: 'registrations', sessions: 'sessions' }
class SessionsController < Devise::SessionsController
skip_before_action :store_location
end
This feels cleaner to me come maintenance time...
Instead of request.full_path you can use request.path which does not include the query string.
But a better solution altogether is just skipping the callback on the effected controllers / actions as this distributes the responsibilities correctly.
class ApplicationController < ActionController::Base
before_action :store_location
def store_location
session[:previous_url] = request.fullpath
end
end
class SessionsController < ApplicationController
skip_before_action :store_location
end
class UsersController < ApplicationController
skip_before_action :store_location, only: :index
end
Wildcards don't work in string comparisons, which are just straight character-by-character equality checks.
You need to use a regular expression, and check for a match, or use String#[]:
if (!request.fullpath[%r{\A/users/sign_in\?locale=.*\z}] &&
...

How to request separate folder view path based on controller name in Sinatra?

Here's the contents of my app/controllers/application_controller.rb:
require 'sinatra/base'
require 'slim'
require 'colorize'
class ApplicationController < Sinatra::Base
# Global helpers
helpers ApplicationHelper
# Set folders for template to
set :root, File.expand_path(File.join(File.dirname(__FILE__), '../'))
puts root.green
set :sessions,
:httponly => true,
:secure => production?,
:expire_after => 31557600, # 1 year
:secret => ENV['SESSION_SECRET'] || 'keyboardcat',
:views => File.expand_path(File.expand_path('../../views/', __FILE__)),
:layout_engine => :slim
enable :method_override
# No logging in testing
configure :production, :development do
enable :logging
end
# Global not found??
not_found do
title 'Not Found!'
slim :not_found
end
end
As you can see I'm setting the views directory as:
File.expand_path(File.expand_path('../../views/', __FILE__))
which works out to be /Users/vladdy/Desktop/sinatra/app/views
In configure.ru, I then map('/') { RootController }, and in said controller I render a view with slim :whatever
Problem is, all the views from all the controllers are all in the same spot! How do I add a folder structure to Sinatra views?
If I understand your question correctly, you want to override #find_template.
I stick this function in a helper called view_directory_helper.rb.
helpers do
def find_template(views, name, engine, &block)
views.each { |v| super(v, name, engine, &block) }
end
end
and when setting your view directory, pass in an array instead, like so:
set :views, ['views/layouts', 'views/pages', 'views/partials']
Which would let you have a folder structure like
app
-views
-layouts
-pages
-partials
-controllers
I was faced with same task. I have little experience of programming in Ruby, but for a long time been working with PHP. I think it would be easier to do on it, where you can easily get the child from the parent class. There are some difficulties. As I understand, the language provides callback functions like self.innereted for solving of this problem. But it did not help, because I was not able to determine the particular router in a given time. Maybe the environment variables can help with this. But I was able to find a workaround way to solve this problem, by parsing call stack for geting caller class and wrapping output function. I do not think this is the most elegant way to solve the problem. But I was able to realize it.
class Base < Sinatra::Application
configure do
set :views, 'app/views/'
set :root, File.expand_path('../../../', __FILE__)
end
def display(template, *args)
erb File.join(current_dir, template.to_s).to_sym, *args
end
def current_dir
caller_class.downcase!.split('::').last
end
private
def caller_class(depth = 1)
/<class:([\w]*)>/.match(parse_caller(caller(depth + 1)[1]))[1]
end
def parse_caller(at)
Regexp.last_match[3] if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
end
end
The last function is taken from here. It can be used as well as default erb function:
class Posts < Base
get '/posts' do
display :index , locals: { variables: {} }
end
end
I hope it will be useful to someone.

How to map routes to controllers in Sinatra?

I'd like to create a simple experimental MVC framework using Sinatra.
I'd like to define resources by name "pages" for example should resolve to:
/pages (index)
/pages/new
/pages/:id/show (show)
as WELL as map to app/controllers/PagesController.rb with corresponding get('/') to be responsible for the index, post('/pages/create') be responsible for creation, etc.
Trouble is even after reading the official documentation I'm terribly confused. I imagine I need to use non-classic Sinatra model for this, but could anyone point me in the right direction?
Thank you
If you want what I think you're wanting, I do this all the time. Initially for this scheme I used the travis-api source as a reference, but essentially what you want to do is extend Sinatra::Base in a "controller" class and then mount up your individual Sinatra "controllers" in rack, something like this:
module Endpoint
def self.included(base)
base.class_eval do
set(:prefix) { "/" << name[/[^:]+$/].downcase }
end
end
end
class Users < Sinatra::Base
include Endpoint
get '/' do
#logic here
end
get '/:id' do
#logic here
end
post '/' do
#logic here
end
patch '/:id' do
#logic here
end
end
class Posts < Sinatra::Base
include Endpoint
post '/' do
#logic here
end
end
and then something like this:
class App
require "lib/endpoints/users"
require "lib/endpoints/posts"
attr_reader :app
def initialize
#app = Rack::Builder.app do
[Users, Posts].each do |e|
map(e.prefix) { run(e.new) }
end
end
end
def call(env)
app.call(env)
end
end
You can adjust this to whatever you need, but the idea is the same, you separate your app into composable Sinatra applications that each have a prefix that they are mounted under using Rack. This particular example will give you routes for:
get '/users'
get '/users/:id'
post '/users'
patch '/users/:id'
get '/posts'
I'll give you a very simple example here:
Create a file controller.rb
get '/pages' do
#pages = Pages.all
erb :pages
end
Next create a views directory in the same folder as teh controller, and create a file named pages.html.erb
This is the corresponding view to your previously created controller action.
Here, you can type something like:
<% #pages.each do |p| %>
<%= p.title %>
<% end %>
Restart your server, visit localhost:PORT/pages and you will see a list of all your page titles.
You can check out this link for a simple sinatra tutorial - http://code.tutsplus.com/tutorials/singing-with-sinatra--net-18965
You can make this as complicated or as simple as you need. For example:
Rails makes a lot of magic happen under the hood, whereas Sinatra is more flexible at the cost of requiring you to implement some of this stuff yourself.
controller_map = {
'pages' => PagesController
}
post '/:controller/new' do
c = params[:controller]
module = controller_map[c]
module.create_new()
...
end
get '/:controller/:id/show' do
c = params[:controller]
id = params[:id]
module = controller_map[c]
module.get(id)
...
end

Access Sinatra settings from a model

I have a modular Sinatra app. I'm setting some custom variables in my configure block and want to access these settings in my model.
The problem is, I get a NoMethodError when I try and access my custom settings from MyModel. Standard settings still seem to work fine though. How can I make this work?
# app.rb
require_relative 'models/document'
class App < Sinatra::Base
configure do
set :resource_path, '/xfiles/i_want_to_believe'
end
get '/' do
#model = MyModel.new
haml :index
end
end
# models/my_model.rb
class MyModel
def initialize
do_it
end
def do_it
...
settings.resource_path # no method error
...
settings.root # works fine
end
end
i think that you should be able to access it via
Sinatra::Application.settings.documents_path
I ended up doing:
#document.rb
class Document
def self.documents_path=(path)
#documents_path = path
end
def self.documents_path
#documents_path
end
...
end
#app.rb
configure do
set :documents_path, settings.root + "/../documents/"
Document.documents_path = settings.documents_path
end
then just using Document.documents_path inside my find method.

Implementing an ActiveRecord before_find

I am building a search with the keywords cached in a table. Before a user-inputted keyword is looked up in the table, it is normalized. For example, some punctuation like '-' is removed and the casing is standardized. The normalized keyword is then used to find fetch the search results.
I am currently handling the normalization in the controller with a before_filter. I was wondering if there was a way to do this in the model instead. Something conceptually like a "before_find" callback would work although that wouldn't make sense on for an instance level.
You should be using named scopes:
class Whatever < ActiveRecord::Base
named_scope :search, lambda {|*keywords|
{:conditions => {:keyword => normalize_keywords(keywords)}}}
def self.normalize_keywords(keywords)
# Work your magic here
end
end
Using named scopes will allow you to chain with other scopes, and is really the way to go using Rails 3.
You probably don't want to implement this by overriding find. Overriding something like find will probably be a headache down the line.
You could create a class method that does what you need however, something like:
class MyTable < ActiveRecord::Base
def self.find_using_dirty_keywords(*args)
#Cleanup input
#Call to actual find
end
end
If you really want to overload find you can do it this way:
As an example:
class MyTable < ActiveRecord::Base
def self.find(*args)
#work your magic here
super(args,you,want,to,pass)
end
end
For more info on subclassing checkout this link: Ruby Tips
much like the above, you can also use an alias_method_chain.
class YourModel < ActiveRecord::Base
class << self
def find_with_condition_cleansing(*args)
#modify your args
find_without_condition_cleansing(*args)
end
alias_method_chain :find, :condition_cleansing
end
end

Resources