Question

Authlogic requires many Active Record features that are not available in Active Model and thus not available in Mongoid. How can Authlogic be used with Mongoid?

Was it helpful?

Solution

It is possible for Authlogic to be used with Mongoid by monkey patching Authlogic. YMMV.

First, add both Authlogic and Mongoid to the Gemfile.

gem 'mongoid', github: 'mongoid/mongoid' # Currently required for Rails 4
gem 'authlogic', '~> 3.3.0'

Next monkey patch Authlogic. First make the directory lib/modules and create file lib/modules/login.rb

# lib/modules/login.rb
module Authlogic
  module ActsAsAuthentic
    module Login
      module Config
        def find_by_smart_case_login_field(login)
          # Case insensitivity for my project means downcase.
          login = login.downcase unless validates_uniqueness_of_login_field_options[:case_sensitive] 

          if login_field
            where({ login_field.to_sym => login }).first
          else
            where({ email_field.to_sym => login }).first
          end
        end
      end
    end
  end
end

Tell rails to load your new module by editing config/application.rb

# config/application.rb
module YourApp
  class Application < Rails::Application
    config.autoload_paths += %W(#{config.root}/lib) # ADD THIS
  end
end

Tell rails to require your module by creating config/initializers/authlogic.rb and add one line:

# config/initializers/authlogic.rb
require 'modules/login.rb'

The next step is to make a concern for being authenticable. Concerns are only available by default in Rails 4. You will need to add this directly to your authenticable class in Rails 3.

# app/models/concerns/authenticable.rb
module Authenticable
  extend ActiveSupport::Concern

  included do
    include Authlogic::ActsAsAuthentic::Base
    include Authlogic::ActsAsAuthentic::Email
    include Authlogic::ActsAsAuthentic::LoggedInStatus
    include Authlogic::ActsAsAuthentic::Login
    include Authlogic::ActsAsAuthentic::MagicColumns
    include Authlogic::ActsAsAuthentic::Password
    include Authlogic::ActsAsAuthentic::PerishableToken
    include Authlogic::ActsAsAuthentic::PersistenceToken
    include Authlogic::ActsAsAuthentic::RestfulAuthentication
    include Authlogic::ActsAsAuthentic::SessionMaintenance
    include Authlogic::ActsAsAuthentic::SingleAccessToken
    include Authlogic::ActsAsAuthentic::ValidationsScope
  end

  def readonly?
    false
  end

  module ClassMethods
    def <(klass)
      return true if klass == ::ActiveRecord::Base
      super(klass)
    end

    def column_names
      fields.map &:first
    end

    def quoted_table_name
      self.name.underscore.pluralize
    end

    def default_timezone
      :utc
    end

    def primary_key
      if caller.first.to_s =~ /(persist|session)/
        :_id
      else
        @@primary_key
      end
    end

    def find_by__id(*args)
      find *args
    end

    def find_by_persistence_token(token)
      where(persistence_token: token).first
    end

    # Replace this with a finder to your login field
    def find_by_email(email)
      where(email: email).first
    end

    def with_scope(query)
      query = where(query) if query.is_a?(Hash)
      yield query
    end
  end
end

That is basically it. Here is a sample User class.

# app/models/user.rb
class User
  include Mongoid::Document
  include Mongoid::Timestamps

  include Authenticable

  field :name
  field :email
  field :crypted_password
  field :password_salt
  field :persistence_token
  field :single_access_token
  field :perishable_token

  field :login_count,        type: Integer, default: 0
  field :failed_login_count, type: Integer, default: 0
  field :last_request_at,    type: DateTime
  field :last_login_at,      type: DateTime
  field :current_login_at,   type: DateTime
  field :last_login_ip
  field :current_login_ip

  field :role, type: Symbol, default: :user

  index({ name: 1 })
  index({ email: 1 }, { unique: true })
  index({ persistence_token: 1}, { unique: true })
  index({ last_login_at: 1})

  validates :name, presence: true
  validates :email, presence: true, uniqueness: true
  validates :crypted_password, presence: true
  validates :password_salt, presence: true

  acts_as_authentic do |config|
    config.login_field = 'email' # Remember to add a finder for this
  end
end

The UserSession would then look like this:

# app/models/user_session.rb
class UserSession < Authlogic::Session::Base
  def to_key
    new_record? ? nil : [self.send(self.class.primary_key)]
  end

  def to_partial_path
    self.class.name.underscore
  end
end

The application controller helpers are the same as always.

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  helper_method :current_user

  private
  def current_user_session
    @current_user_session = UserSession.find unless 
      defined?(@current_user_session)
    @current_user_session
  end

  def current_user
    @current_user = current_user_session && 
      current_user_session.user unless defined?(@current_user)
    @current_user
  end
end

That is it. Everything else should work exactly as it used to.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top