• Home

  • Custom Ecommerce
  • Application Development
  • Database Consulting
  • Cloud Hosting
  • Systems Integration
  • Legacy Business Systems
  • Security & Compliance
  • GIS

  • Expertise

  • About Us
  • Our Team
  • Clients
  • Careers

  • Blog

  • EpiTrax

  • VisionPort

  • Contact
  • Our Blog

    Ongoing observations by End Point Dev people

    Devise on Rails: Prepopulating Form Data

    Steph Skardal

    By Steph Skardal
    June 8, 2012

    I recently had a unique (but reasonable) request from a client: after an anonymous/guest user had completed checkout, they requested that a “Create Account” link be shown on the receipt page which would prepopulate the user form data with the user’s checkout billing address. Their application is running on Ruby on Rails 3.2 and uses devise. Devise is a user authentication gem that’s popular in the Rails community.

    A customer request was to include a link on the receipt page that would autopopulate the user create account form with checkout data.

    Because devise is a Rails engine (self-contained Rails functionality), the source code is not included in the main application code repository. While using bundler, the version information for devise is stored in the application’s Gemfile.lock, and the engine source code is stored depending on bundler configuration. Because the source code does not live in the main application, modifying the behavior of the engine is not quite as simple as editing the source code. My goal here was to find an elegant solution to hook into the devise registration controller to set the user parameters.

    ActiveSupport::Concern

    To start off, I set up a devise_registrations_controller_decorator.rb module in my application lib/ directory which extends ActiveSupport::Concern, shown below. ActiveSupport::Concern is a tool to elegantly extend or override core models and controllers. In this case, Devise::RegistrationsController was set up to be decorated.

    module DeviseRegistrationsControllerDecorator
      extend ActiveSupport::Concern
    end
    
    Devise::RegistrationsController.send(:include, DeviseRegistrationsControllerDecorator)
    

    Adding an InstanceMethods method

    Next, I added a custom_new method that took into account a parameter that would be passed in the url (with_user=1). If this parameter exists and the user has a recent order stored in their session, the recent order is referenced to set the resource (User instance) values:

    module DeviseRegistrationsControllerDecorator
      extend ActiveSupport::Concern
    
      module InstanceMethods
        def custom_new
          resource = build_resource({})
          if params.has_key?(:with_user) && session.has_key?(:last_order)
            last_order = Piggybak::Order.find(session[:last_order])
            resource.email = last_order.email
            resource.phone = last_order.phone
            [:firstname, :lastname, :address1, :address2, :institution, :city, :state_id, :zip, :country_id].each do |field|
              resource.send("#{field}=", last_order.billing_address.send("#{field}"))
            end
          end
          respond_with resource
        end
      end
    end
    
    Devise::RegistrationsController.send(:include, DeviseRegistrationsControllerDecorator)
    

    Alias and Redefinition

    Finally, I updated the module so that when it was included, an alias for the core new method, and an override of the core new method with the custom_new method:

    module DeviseRegistrationsControllerDecorator
      extend ActiveSupport::Concern
    
      included do
        alias :devise_new :new
        def new; custom_new; end
      end
    
      module InstanceMethods
        def custom_new
          ...
        end
      end
    end
    
    Devise::RegistrationsController.send(:include, DeviseRegistrationsControllerDecorator)
    

    config/environments/development.rb

    Because classes are not cached in development, and library modules are not automatically reloaded, I added the following call to config/environments/development.rb to force a reload of the module to ensure that the devise controller would always be extended:

    config.to_prepare do
      Devise::RegistrationsController.send(:include, DeviseRegistrationsControllerDecorator)
    end
    

    Conclusion

    In this case, ActiveSupport::Concern was used to easily override the core devise method without requiring hacking at the source code. One disadvantage to this implementation is that if significant changes are made to the core method, those changes also need to be applied to the custom controller if necessary.

    Autopopulate success!

    rails


    Comments