Ruby wildcard on path - ruby

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}] &&
...

Related

Ruby - devise : confirmations_controller stop registrations_controller

I have a problem with devise I can't find the solution.
When a user sign_up, I need to call several services to make his profile. So here is the registrations_controller.rb.
require_relative '../../../app/services/affinities'
require_relative '../../../app/services/astroprofil'
require_relative '../../../app/services/geocode'
class Users::RegistrationsController < Devise::RegistrationsController
ASTROPROFIL = Astroprofil.new
AFFINITIES = Affinities.new
GEOCODE = Geocode.new
after_action :create_astroprofil, only: %i[new create]
after_action :create_affinities, only: %i[new create]
private
def create_astroprofil
return unless user_signed_in?
ASTROPROFIL.profil(current_user)
end
def create_affinities
return unless user_signed_in?
affinities(current_user, ten_mates)
end
def affinities(user, mates)
AFFINITIES.partner_report(user, mates)
AFFINITIES.sign_report(user, mates)
AFFINITIES.match_percentage(user, mates)
end
def ten_mates
mates_by_gender = User.where(gender: current_user.looking_for).where.not(id: current_user.id)
return mates_by_gender.sample(10)
end
end
When I sign up everything works perfectly, a new user is entirely created.
But as soon as I try to add a confirmation per mail with devise, the mails are sent but it stops the 'create_astroprofil' and the 'create_affinities' methods.
Do you have any idea about what's happening ?
I would say it's coming from this line
registrations_controller.rb#L28
Since you cannot login without having confirmed your email, I'm pretty sure create_astroprofil and create_affinities are called but their first line is return unless user_signed_in?.
2 options here:
Astroprofil.new and Affinities.new can be called for an unconfirmed user
Called create_astroprofil and create_affinities from ConfirmationController#show

Sweeper never triggers

I have the following usecase: I have an API and a UI which both modify the same type of object Vehicle. However, when a vehicle is modified from the UI, it must have a Vehicle Identification Number provided, while if it is modified, that number may not yet be known as it has not yet been stamped on the Engine Block (this is clearly a manufactured example).
So my model is:
class Vehicle < ActiveRecord::Base
after_initialize :set_ivars
def set_ivars
#strict_vid_validation = true
end
validate :vid, length: {maximum: 100, minimum: 30}, presence: true, if: lambda { |o| o.instance_variable_get(:#strict_vid_validation) }
validate :custom_vid_validator, unless: lambda { |o| o.instance_variable_get(:#strict_vid_validation) }
end
Two parent controllers:
class ApplicationController < ActionController::Base
before_filter :set_api_filter
private
def set_api_filter
#api = false
end
end
and an ApiController that sets #api to true
and the following vehicle controller:
class VehicleController < ApplicationController
cache_sweeper VehicleSweeper, only: [:create, :update]
def create
#vehicle = Vehicle.new
#vehicle.update_attributes(params[:vehicle])
end
end
with the following sweeper:
class VehicleSweeper < ActionController::Caching::Sweeper
observe Vehicle
def before_validation(vehicle)
if self.instance_variable_get(:#api)
vehicle.instance_variable_set(:#strict_vid_validation, false)
else
vehicle.instance_variable_set(:#strict_vid_validation, true)
end
end
And a somewhat similiar ApiVehicleController
However, this does not work. Through debugging I have discovered that:
1) The before_validation (nor do any other callbacks configured) method in the sweeper never runs
2) The more stringent ui validation is always triggered, this is due to
3) inside the lambda on the if: on the validate, the instance_variable is always true as it is never set through a callback method
Why is this not working? How can I fix it? Is there a different approach I could take if not?
In the end, the following worked:
def my_controller_action
#vehicle = Vehicle.new
#vehicle.assign_attributes(params[:vehicle])
VehicleSweeper.send(:new).pick_a_validation(#vehicle)
#vehicle.save
end
and in the sweeper removing the line
observe Vehicle
and renaming the before_validation method to pick_a_validation.
Of course this only works because I wanted to do a before_validation. If I wanted to do say an after_validation I have no idea how I would have hacked it.
In either case, I would love to hear how this is done without bypassing the callback chain entirely like I did.

Detect which method called the routing condition in Sinatra

I'm using Sinatra with namespace.
When I tried to use condition, I met a problem.
Here's the snippet of code
class MainApp < Sinatra::Base
register Sinatra::Namespace
set(:role) do |role|
condition{
### DETECT WHERE THIS IS CALLED
p role
true
}
end
namespace '/api', :role => :admin do
before do
p "before"
end
get '/hoo' do
p "hoo"
end
end
namespace '/api' do
get '/bar' do
p "bar"
end
end
end
The above code outputs following message to console when accessing /api/hoo
:admin
:admin
"before"
:admin
"hoo"
I could not understand why :admin is displayed three times. However, maybe one is from namespace, and other twos are from before and get '/hoo'.
On the other hand, accessing /api/bar shows :admin two times.
I just want to do the filtering only before get '/hoo'. Is there any idea?
NOTE: I don't wan't to change URL from /api/hoo to something like /api/baz/hoo
You can debug the steps using the caller:
http://ruby-doc.org/core-2.0/Kernel.html#method-i-caller
(Note: I wouldn't recommend to leave caller in production code unless you absolutely need it for introspection, because it's quite slow.)
Re the Sinatra filters in particular, note that you can at the very least qualify the route and conditions they apply to:
http://www.sinatrarb.com/intro#Filters
before '/protected/*' do
authenticate!
end
before :agent => /Songbird/ do
# ...
end
I can't recollect how to get the http method, but if you look at the sinatra source code you'll likely find it -- last I looked, I recollect each of get, post, etc. to forward their call to the same function, with a method parameter.

Rails routing: Giving default values for path helpers

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

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