I am trying to route a user to a custom welcome page after they confirm their account via devise's confirmable. The custom page is located at /districts/setup/, reachable by districts_setup_path.
To do this I added the custom route,
devise_for :users, :controllers => { :registrations => 'registrations', :confirmations => 'confirmations' }
and created my own controller. Then I had to overload the confirmations_controller.rb and now have:
(app/controllers/confirmations_controller.rb)
class ConfirmationsController | Devise::ConfirmationsController
# GET /resource/confirmation?confirmation_token=abcdef
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
if resource.errors.empty?
set_flash_message(:notice, :confirmed) if is_navigational_format?
sign_in(resource_name, resource)
redirect_to districts_setup_path
else
render_with_scope :new
# not:
# respond_with_navigational(resource.errors, :status => :unprocessable_entity){
end
end
end
This works well, but I am nervous that I am not doing this in the mostideal and robust way. In particular, I just deleted the respond_with_navigational( ... ) lines which I really don't understand.
I am hoping to write this all up in a how-to for the Devise wiki, and am just looking for feedback being fairly new to rails and even newer to Devise/engines/warden.
Taking a look on Devise's ConfirmationsController you can spot the protected method after_confirmation_path_for(resource_name, resource). Overriding it (rather than the whole action) will produce the same results with less effort.
Generally speaking, there is no problem with overriding Devise's controller since they represent the default behavior which doesn't always suit the application needs. That being said you must take a few things before overriding Devise's code:
Devise is not just another component of the system - it handles user authentication, which is a very sensitive issue. Make sure you don't break anything important before you commit it. You can do so by forking the Devise project from github, making your changes, and running the tests.
As you make changes to devise and override its code, it will become harder to upgrade to newer version, which might be incompatible with your changes.
If you do decide to make a change, look for the smallest change possible to achieve your goal. In most of the cases Devise's team has already foreseen the need for customization in certain places and left methods (like the one above) which are dedicated just for it. Again, going over the file's code on Devise's GitHub would give you a good idea as for what is the best way to customize its behavior to your needs.
Related
I have an ActiveRecord Model, PricePackage. That has a before_create call back. This call back uses a 3rd party API to make a remote connection. I am using factory girl and would like to stub out this api so that when new factories are built during testing the remote calls are not made.
I am using Rspec for mocks and stubs. The problem i'm having is that the Rspec methods are not available within my factories.rb
model:
class PricePackage < ActiveRecord::Base
has_many :users
before_create :register_with_3rdparty
attr_accessible :price, :price_in_dollars, :price_in_cents, :title
def register_with_3rdparty
return true if self.price.nil?
begin
3rdPartyClass::Plan.create(
:amount => self.price_in_cents,
:interval => 'month',
:name => "#{::Rails.env} Item #{self.title}",
:currency => 'usd',
:id => self.title)
rescue Exception => ex
puts "stripe exception #{self.title} #{ex}, using existing price"
plan = 3rdPartyClass::Plan.retrieve(self.title)
self.price_in_cents = plan.amount
return true
end
end
factory:
#PricePackage
Factory.define :price_package do |f|
f.title "test_package"
f.price_in_cents "500"
f.max_domains "20"
f.max_users "4"
f.max_apps "10"
f.after_build do |pp|
#
#heres where would like to mock out the 3rd party response
#
3rd_party = mock()
3rd_party.stub!(:amount).price_in_cents
3rdPartyClass::Plan.stub!(:create).and_return(3rd_party)
end
end
I'm not sure how to get the rspec mock and stub helpers loaded into my factories.rb and this might not be the best way to handle this.
As the author of the VCR gem, you'd probably expect me to recommend it for cases like these. I do indeed recommend it for testing HTTP-dependent code, but I think there's an underlying problem with your design. Don't forget that TDD (test-driven development) is meant to be a design discipline, and when you find it painful to easily test something, that's telling you something about your design. Listen to your tests' pain!
In this case, I think your model has no business making the 3rd party API call. It's a pretty significant violation of the single responsibility principle. Models should be responsible for the validation and persistence of some data, but this is definitely beyond that.
Instead, I would recommend you move the 3rd party API call into an observer. Pat Maddox has a great blog post discussing how observers can (and should) be used to loosely couple things without violating the SRP (single responsibility principle), and how that makes testing, much, much easier, and also improves your design.
Once you've moved that into an observer, it's easy enough to disable the observer in your unit tests (except for the specific tests for that observer), but keep it enabled in production and in your integration tests. You can use Pat's no-peeping-toms plugin to help with this, or, if you're on rails 3.1, you should check out the new functionality built in to ActiveModel that allows you to easily enable/disable observers.
Checkout the VCR gem (https://www.relishapp.com/myronmarston/vcr). It will record your test suite's HTTP interactions and play them back for you. Removing any requirement to actually make HTTP connections to 3rd party API's. I've found this to be a much simpler approach than mocking the interaction out manually. Here's an example using a Foursquare library.
VCR.config do |c|
c.cassette_library_dir = 'test/cassettes'
c.stub_with :faraday
end
describe Checkin do
it 'must check you in to a location' do
VCR.use_cassette('foursquare_checkin') do
Skittles.checkin('abcd1234') # Doesn't actually make any HTTP calls.
# Just plays back the foursquare_checkin VCR
# cassette.
end
end
end
Although I can see the appeal in terms of encapsulation, the 3rd party stubbing doesn't have to happen (and in some ways perhaps shouldn't happen) within your factory.
Instead of encapsulating it in the factory you can simply define it at the start of your RSpec tests. Doing this also ensures that the assumptions of your tests are clear and stated at the start (which can be very helpful when debugging)
Before any tests that use PricePlan, setup the desired response and then return it from the 3rd party .create method:
before(:all) do
3rd_party = mock('ThirdParty')
3rdPartyClass::Plan.stub(:create).and_return(true)
end
This should allow you to call the method but will head off the remote call.
*It looks like your 3rd Party stub has some dependencies on the original object (:price_in_cents) however without knowing more about the exact dependency I can't guess what would be the appropriate stubbing (or if any is necessary)*
FactoryGirl can stub out an object's attributes, maybe that can help you:
# Returns an object with all defined attributes stubbed out
stub = FactoryGirl.build_stubbed(:user)
You can find more info in FactoryGirl's rdocs
I had the same exact issue. Observer discussion aside (it might be the right approach), here is what worked for me (it's a start and can/should be improved upon):
add a file 3rdparty.rb to spec/support with these contents:
RSpec.configure do |config|
config.before do
stub(3rdPartyClass::Plan).create do
[add stuff here]
end
end
end
And make sure that your spec_helper.rb has this:
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
Well, first, you're right that 'mock and stub' are not the language of Factory Girl
Guessing at your model relationships, I think you'll want to build another object factory, set its properties, and then associate them.
#PricePackage
Factory.define :price_package do |f|
f.title "test_package"
f.price_in_cents "500"
f.max_domains "20"
f.max_users "4"
f.max_apps "10"
f.after_build do |pp|
f.3rdClass { Factory(:3rd_party) }
end
Factory.define :3rd_party do |tp|
tp.price_in_cents = 1000
end
Hopefully I didn't mangle the relationship illegibly.
I'm trying to create some mongo mapper observer.
I found a class exit for that : http://rubydoc.info/gems/danielharan-mongo_mapper/0.6.5/MongoMapper/Observer
The question is how to activate them.
I create an app/observers/admin_observer.rb
class AdminObserver < MongoMapper::Observer
observe :admin # my admin model
# call backs ...
end
The question now is how to active them ?
The active record way is :
module MyApp
class Application < Rails::Application
config.active_record.observers = :admin
# other config
end
end
But with MongoMapper no active_record. Quite naive, I tried config.mongo_mapper.observers = :admin, but their is no observers in mongo_mapper configuration ...
I dunno what to try else and my google research didn't give me a clue.
That appears to be a really old, un-maintained fork of MongoMapper--using a 2-year-old gem is asking for trouble.
If you find it's really better to use an observer rather than just implement the callbacks directly in your model, your best bet may be to fork MongoMapper and add the functionality you want (MM's code is pretty clean), or better yet make a gem that extends MongoMapper with an observer functionality.
So in Rails3 Engines come with their own models/controllers/views and of course routes. Now the question is: How do you ensure that Engine routes will be loaded before (or after) application routes and all other Engines that are present?
Here's an example of my Rails app routes:
match '*(path)', :to => 'foo_controller#bar_action'
And my Engine:
match '/news', :to => 'bar_controller#foo_action'
So by default Engines routes will be loaded after the application ones. This means that Engine routes are inaccessible due to that catch-all route in my app. How can force Engine routes to be loaded first (or last)?
What you're trying to do is kinda difficult. As has been mentioned engine routes are loaded after the app routes and overriding this behaviour may be problematic. I can think of several things that you can try.
Use An Initializer After Routing Paths Initializer
There is an initializer in engine.rb inside the rails source, one way to accomplish what you're after is to try to hook into the functionality that it deals with. The initializer looks like this by default:
initializer :add_routing_paths do |app|
paths.config.routes.to_a.each do |route|
app.routes_reloader.paths.unshift(route) if File.exists?(route)
end
end
Essentially, this should take the paths to all the routes files that Rails knows about and try and add them to the routes reloader (the thing that reloades your routes file automagically for you if it is changed). You can define another initializer to be executed right after this one, you will then inspect the paths stored in the routes reloader, pull out the path that belongs to your engine, remove it from the paths array and insert it back, but at the end of the paths array. So, in your config/application.rb:
class Application < Rails::Application
initializer :munge_routing_paths, :after => :add_routing_paths do |app|
engine_routes_path = app.routes_reloader.paths.select{|path| path =~ /<regex that matches path to my engine>/}.first
app.routes_reloader.paths.delete(engine_routes_path)
app.routes_reloader.paths << engine_routes_path
end
end
This may or may not work, either way I don't really recommend it, it's not particularly elegant (i.e. ugly hack playing with the guts of rails).
Use Rails 3.1
This may not be an option, but if it is, I'd probably go with this one. In Rails 3.1 you can have 2 different types of Engines, full and mountable (here is an SO question talking about some of the differences). But in essence you would alter your Engine to be a mountable engine, the routes in a mountable engine are namespaced and you can explicitly include them in the routes file of your main app e.g.:
Rails.application.routes.draw do
mount MyEngine::Engine => "/news"
end
You can also scope your mounted engine routes and do all sorts of other fancy routy things (more info here). Long story short, if you can go to 3.1 then this is the approach to use.
Dynamically Insert The Routes From Your Engine Into Your Main App
One of the most well known Rails engines around at the moment is Devise. Now, devise is an engine that will potentially add quite a number of routes to your app, but if you'll look at the devise source you'll see that it doesn't actually have a config/routes.rb file at all! This is because devise dynamically adds its routing goodness to your main app's routes.rb file.
When you run the model generator that comes with devise, one of the things the generator will do is add a line such as devise_for :model at the top of your routes.rb file, right after the Rails.application.routes.draw do line. So your route.rb looks similar to this after you execute a generator to create a User model:
Rails.application.routes.draw do
devise_for :users
...
end
Now, devise_for is a magical method that comes as part of devise (in lib/devise/rails/routes.rb), but in essence it will create a bunch of regular routes that we all know based on the model that you generated.
The thing we need to know, is how devise insert this line in the apps routes.rb file, then we can write a generator in our engine that will insert any of our routes at the top of the main apps routes.rb file. For this we look at lib/generators/devise/devise_generator.rb. In the add_devise_routes method the last line is route devise_route. Route is a Thor action which inserts the string passed to it into the main app's routes.rb file. So we can write a generator of our own and do something similar e.g.:
class MyCrazyGenerator < Rails::Generators::NamedBase
...
def add_my_crazy_routes
my_route = "match '/news', :to => 'bar_controller#foo_action'"
route my_route
end
end
Of course you would need to make sure all the generator infrastructure is in place but that's the essence of it. Devise is written by some very smart rails dudes and used by quite a lot of people, emulating what they do is likely a pretty good way to go. Out of the 3 things that I suggested this one would be the way I would handle your issue (given that moving to rails 3.1 is probably not an option).
Having the same issue right now. One other not particularly elegant but safe solution I came up with is adding another engine gem at the end of your gem file, just containing the catch all route, nothing else.
Edit: Actually, counterintuitively, the routing gem needs to be listed before all other engine gems for its routes to be loaded last.
You can write a method in your engine that can be called to configure your engine's routes:
module MyEngine
class Engine < Rails::Engine
def self.configure_routes(mapper)
mapper.match '/news', :to => 'news_controller#index'
end
end
end
This allows you to insert your engine's routes before a catch-all in my_application/config/routes.rb:
Rails.application.routes.draw do
# routes
MyEngine::Engine.configure_routes(self)
# more routes
# catch-all route
end
Then in the engine's my_engine/config/routes.rb file:
unless Rails.application.routes.named_routes.key?(:news)
Rails.application.routes.draw do
MyEngine::Engine.configure_routes self
end
end
If you're mounting the engine and don't include the explicit route configuration method, the engine routes will be added as usual to the end of the application routes.
I'm not an expert, but I was playing with sferik/rails_admin yesterday when I came across their routing solution. With their rails_admin:install rake task, they directly modify your config/routes.rb file by adding mount RailsAdmin::Engine => '/admin', :as => 'rails_admin' at the beginning of the file so they are sure their routes always have the highest priority. That mount method refers to their own routes.rb :
RailsAdmin::Engine.routes.draw do
# Prefix route urls with "admin" and route names with "rails_admin_"
scope "history", :as => "history" do
controller "history" do
[...]
end
end
end
Could this be your solution?
I am doing a check on a user model to determine whether s/he has one or more task_list, if she has more than one task_list only then she is allowed to delete it, otherwise an exception is thrown. I basically have an method called delete_list in the user model to allow for short hand deletions such as user1.delete_list(list1)
I am debating whether to put the check in CanCan where it would be apply as a before filter on the controller or whether to have it in the user model as well . What is the recommended practice?
I think a good DRY approach to this would be to create a method in your model that tests whether a delete is allowed. Then use that method from your controller or from ability.rb. IMHO I think having complicated permission/business logic decoupled from CanCan is better when there is a chance you might change to a different permission system in the future.
In your model:
def can_destroy_list(list)
... Do check here ....
end
In ability.rb
can :destroy, List do |list|
user.can_destroy_list(list)
end
Your controller and views can then also use can_destroy_list directly on the model instance if nessary or use: if can? :destroy, #list
I am attempting to secure a Rails3 controller using declarative_authorization.
The controller has the 7, RESTful actions, three custom member actions (activate, deactivate, copy), and one custom collection action (public). The 'public' action only returns one record, however.
Only the custom collection action (public) should be available to authenticated users; the remainder are only available to the current_user.
has_permission_on :foos, :to => :public
has_permission_on :foos, :to => [:full_control, :copy, :activate, :deactivate] do
if_attribute :user => is {user}
end
privilege :full_control, :includes => [:index, :show, :new, :create, :edit, :update, :destroy]
The 4 custom actions are defined in the routes.rb file:
resources :users do
resources :foos do
collection do
get :public
end
member do
post :activate, :copy, :deactivate
end
end
end
A User :has_many Foos; A Foo :belongs_to a User.
The 'standard' access control (filter_resource_access :nested_in => :user), as defined in the FoosController seems to control access to the 7, RESTful actions, but fails to control access to the other 4 (as expected).
When I change the FooController to:
filter_access_to :all, :nested_in => :users, :attribute_check => true
I get an error that reads "Couldn't find Foo without an ID".
Questions:
The documentation seems to suggest that a :before_filter will be called automatically to load the Foo model when filter_access_to is used. Am I mistaken? Do I need additional configuration of the filter_access_to? Do I need to manually configure a :before_filter?
Do I also need to add using_access_control to the model for my purposes? I'm a little unclear when one needs to add access control to the model when there is access control in the controller.
The documentation describes a privilege named 'create'--it is defined as: privilege :create, :includes => :new. In addition, to the :new action, does this privilege automatically include the :create action as a consequence of its name?
If the authentication_rules.rb file is changed, does the server need to be restarted for the new rules to be applied?
I think that automatic before filter, if it exists, is pretty limited. I've always had to write my own before filters as appropriate to the context. Eg--for the the index view unless permission is the same for all instances of the model, you'll need to have a model instance appropriate the the current context. Like a user viewing an index of posts, you would want to make a dummy new post for the user, or load their first but dummy is safer since first might not exist. Generally I have a dummy constructor for index and everything else can test whatever is actually to be seen (or touched).
Controller has been good enough for me so far, so model level is certainly not REQUIRED. That's not to say it wouldn't add some extra safety, but I'm not expert in when exactly that would become important. I'd hypothesize it would be when you have a model touched by many controllers (eg modeless controllers) and you want to be sure nothing sneaks by.
I haven't used privileges so I'm not sure, but I would guess that magic inheritance you describe doesn't happen. Creating permissions that aren't specifically requested seems like it would be a very sloppy approach.
No, no restart required--at least not in development mode.