Skip to content

Instantly share code, notes, and snippets.

@thhermansen
Created May 28, 2011 19:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thhermansen/997143 to your computer and use it in GitHub Desktop.
Save thhermansen/997143 to your computer and use it in GitHub Desktop.
Give Devise support for MassiveRecord
#
# lib/devise/orm/massive_record.rb
#
# This adds support for Devise so Devise can define
# attributes in a MassiveRecord class. By default
# attributes will be persisted in column family
# "devise", but you can change by setting
# column_family_name_for_devise to something else.
#
module Devise
module Orm
module MassiveRecord
module Schema
include Devise::Schema
#
# Devise calls this method when it wants to define
# attributes / schema in models.
#
def apply_devise_schema(field_name, type, options = {})
add_field_to_column_family(column_family_name_for_devise, field_name, devise_class_to_massive_record_type(type), :default => options[:default])
end
private
def devise_class_to_massive_record_type(klass)
if klass == DateTime
:time
else
klass.to_s.downcase.to_sym
end
end
end
module Hook
def devise_modules_hook!
extend Schema
yield
return unless Devise.apply_schema
devise_modules.each { |m| send(m) if respond_to?(m, true) }
end
end
end
end
end
MassiveRecord::ORM::Base.class_eval do
extend Devise::Models
extend Devise::Orm::MassiveRecord::Hook
#
# Column family which we'll use to store Devise's fields in.
#
class_attribute :column_family_name_for_devise
self.column_family_name_for_devise = :devise
end
class UniquenessValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
raise "You have to give a table you want to validate uniqueness via!" unless options.has_key? :via
if found_record = options[:via].find(value) rescue nil
if options.has_key? :retrieve_record_by
found_record = found_record.send(options[:retrieve_record_by])
end
compare_with = options.has_key?(:compare_with) ? record.send(options[:compare_with]) : record
record.errors.add(attribute, :taken) unless found_record == compare_with
end
end
end
class User < MassiveRecord::ORM::Table
DEVISE_TOKENS = %w(remember_token reset_password_token)
#
# Class methods overridden for making Devise work as expected
#
class << self
#
# This is where the fun part begins. Devise sends in
# :conditions to the finder, but since MassiveRecord
# does not support that we need to intercept such calls
# and do the find calls a bit differently...
#
# Its kinda hackish
#
def do_find(*args)
options = args.dup.extract_options!.to_options
return super unless conditions = options.delete(:conditions)
conditions = conditions.to_hash.stringify_keys # Sometimes is HashWithIndifferentAccess, sometimes a hash.. ensures that we know what we are working with
if finder = conditions.keys & DEVISE_TOKENS and finder.length == 1
finder = finder.first
begin
user = UserAuthenticationIndex.find_by_devise_token(finder, conditions[finder]).user
user if !conditions.has_key?('id') || user.id.to_s == conditions['id'].to_s
rescue MassiveRecord::ORM::RecordNotFound
end
elsif conditions.has_key? 'email'
find_by_email(conditions['email'])
elsif conditions.has_key? 'id'
super(conditions['id'])
else
raise "Sorry, don't know how to handle: #{conditions}"
end
end
#
# Devise's validatable module needs validates_uniqueness_of and I found
# it hard to inject options it sends to the validates_uniqueness_of
# validator. Thus this small local implementation fixes it.
#
def validates_uniqueness_of(*attr_names)
validates_with UniquenessValidator, _merge_attributes(attr_names).merge(:via => UserAuthenticationIndex, :retrieve_record_by => :user)
end
#
# Generates a token for incomming column
# Checks against the UserAuthenticationIndex to figure out if token is unique
#
def generate_token(column)
loop do
token = Devise.friendly_token
break token unless UserAuthenticationIndex.devise_token_exists?(column, token)
end
end
#
# Find user records by email, via the UserAuthenticationIndex
#
def find_by_email(email)
begin
UserAuthenticationIndex.find((email)).user
rescue MassiveRecord::ORM::RecordNotFound
end
end
end
#
# Set up Devise.
#
# Devise will store it's attributes to column family whatever
# column_family_name_for_devise returns. Defaults to :devise.
#
# Modules available are:
#
# :token_authenticatable, :confirmable, :lockable and :timeoutable
#
# Please remember that you might have to maintain some class methods
# to make method used by Devise modules (like finders) to use for instance the UserAuthenticationIndex.
# You also might have to maintain such indexes by updating the array defined in update_devise_tokens_in_index
#
devise :database_authenticatable, :validatable, :registerable, :trackable, :rememberable, :recoverable, :omniauthable
#
# We need to maintain UserAuthenticationIndex table
# so lets do so by some callbacks
#
before_save :update_indexes
after_destroy :update_indexes
private
def update_indexes
update_email_in_index
update_devise_tokens_in_index
end
def update_email_in_index
if (persisted? && email_changed?) || destroyed?
UserAuthenticationIndex.find(email_was).destroy
end
UserAuthenticationIndex.create!(email, :user_id => id) if !destroyed? && !persisted? || email_changed?
end
def update_devise_tokens_in_index
DEVISE_TOKENS.each do |token_name|
if destroyed? || (persisted? && send("#{token_name}_changed?"))
begin
token = destroyed? ? read_attribute(token_name) : send("#{token_name}_was")
UserAuthenticationIndex.find_by_devise_token(token_name, token).destroy if token
rescue MassiveRecord::ORM::RecordNotFound
end
end
UserAuthenticationIndex.create_devise_token!(token_name, self) unless destroyed? || read_attribute(token_name).blank?
end
end
end
class UserAuthenticationIndex < MassiveRecord::ORM::Table
references_one :user, :store_in => :base
#
# Generates a key for given Devise token name and token
#
def self.key_for_devise_token(token_name, token)
["devise", token_name, token].join("_")
end
def self.devise_token_exists?(token_name, token)
exists? key_for_devise_token(token_name, token)
end
def self.find_by_devise_token(token_name, token)
find key_for_devise_token(token_name, token)
end
def self.create_devise_token!(token_name, user)
token = user.send(token_name)
create! key_for_devise_token(token_name, token), :user_id => user.id
end
#
# Generates a key for given Omniauth provider name and user id
#
def self.key_for_connected_account(provider, uid)
["omniauth", provider, uid].join("-")
end
def self.connected_account_exists?(provider, uid)
exists? key_for_connected_account(provider, uid)
end
def self.find_by_connected_account(provider, uid)
find key_for_connected_account(provider, uid)
end
def self.create_connected_account!(connected_account)
create! key_for_connected_account(connected_account.provider, connected_account.uid), :user => connected_account.user
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment