Created
May 28, 2011 19:31
-
-
Save thhermansen/997143 to your computer and use it in GitHub Desktop.
Give Devise support for MassiveRecord
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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