Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Datamapper support #472

Merged
merged 10 commits into from
Jan 6, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ gemfile:
- Gemfile
- Gemfile.rails4

before_script:
- mysql -e 'create database sorcery_test;'

script:
- "SORCERY_ORM=active_record bundle exec rake spec SPEC=spec/active_record"
- "SORCERY_ORM=mongoid bundle exec rake spec SPEC=spec/mongoid"
- "SORCERY_ORM=mongo_mapper bundle exec rake spec SPEC=spec/mongo_mapper"
- "SORCERY_ORM=datamapper bundle exec rake spec SPEC=spec/datamapper"
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ group :mongoid do
gem 'mongoid', "~> 2.4.4"
gem 'bson_ext'
end

group :datamapper do
gem 'mysql2'
gem 'data_mapper'
gem 'dm-mysql-adapter'
end
6 changes: 6 additions & 0 deletions Gemfile.rails4
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ group :mongoid do
gem 'bson_ext'
end

group :datamapper do
gem 'mysql2'
gem 'data_mapper'
gem 'dm-mysql-adapter'
end

gem 'rails', '~> 4.0.1'
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,19 @@ In short, an app that works with x.3.1 should be able to upgrade to x.3.2 with
no code changes. The same cannot be said about upgrading to x.4.0 and above,
however.

## DataMapper Support

Important notes:

* Expected to work with DM adapters: dm-mysql-adapter,
dm-redis-adapter.
* Submodules DM adapter dependent: activity_logging (dm-mysql-adapter)
* Usage: include DataMapper::Resource in user model, follow sorcery
instructions (remember to add property id, validators and accessor
attributes such as password and password_confirmation)
* Option downcase__username_before_authenticating and dm-mysql,
http://datamapper.lighthouseapp.com/projects/20609/tickets/1105-add-support-for-definingchanging-default-collation

## Upgrading

Important notes while upgrading:
Expand Down
5 changes: 5 additions & 0 deletions lib/sorcery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Adapters
autoload :ActiveRecord, 'sorcery/model/adapters/active_record'
autoload :Mongoid, 'sorcery/model/adapters/mongoid'
autoload :MongoMapper, 'sorcery/model/adapters/mongo_mapper'
autoload :DataMapper, 'sorcery/model/adapters/datamapper'
end
module Submodules
autoload :UserActivation, 'sorcery/model/submodules/user_activation'
Expand Down Expand Up @@ -69,5 +70,9 @@ module Internal
MongoMapper::Document.send(:plugin, Sorcery::Model::Adapters::MongoMapper)
end

if defined?(DataMapper)
DataMapper::Model.append_inclusions(Sorcery::Model, Sorcery::Model::Adapters::DataMapper)
end

require 'sorcery/engine' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
end
6 changes: 3 additions & 3 deletions lib/sorcery/controller/submodules/external.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def add_provider_to_user(provider_name)
# first check to see if user has a particular authentication already
unless (current_user.send(config.authentications_class.name.underscore.pluralize).send("find_by_#{config.provider_attribute_name}_and_#{config.provider_uid_attribute_name}", provider_name, @user_hash[:uid].to_s))
user = current_user.send(config.authentications_class.name.underscore.pluralize).build(config.provider_uid_attribute_name => @user_hash[:uid], config.provider_attribute_name => provider_name.to_s)
user.save(:validate => false)
user.sorcery_save(:validate => false)
else
user = false
end
Expand All @@ -159,7 +159,7 @@ def create_and_validate_from(provider_name)
session[:incomplete_user] = {
:provider => {config.provider_uid_attribute_name => @user_hash[:uid], config.provider_attribute_name => provider_name},
:user_hash => attrs
} unless user.save
} unless user.sorcery_save

return user
end
Expand Down Expand Up @@ -196,7 +196,7 @@ def create_from(provider_name)
return false unless yield @user
end

@user.save(:validate => false)
@user.sorcery_save(:validate => false)
user_class.sorcery_config.authentications_class.create!({config.authentications_user_id_attribute_name => @user.id, config.provider_attribute_name => provider_name, config.provider_uid_attribute_name => @user_hash[:uid]})
end
@user
Expand Down
36 changes: 36 additions & 0 deletions lib/sorcery/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def authenticates_with_sorcery!

init_mongoid_support! if defined?(Mongoid) and self.ancestors.include?(Mongoid::Document)
init_mongo_mapper_support! if defined?(MongoMapper) and self.ancestors.include?(MongoMapper::Document)
init_datamapper_support! if defined?(DataMapper) and self.ancestors.include?(DataMapper::Resource)

init_orm_hooks!

Expand Down Expand Up @@ -75,8 +76,26 @@ def init_mongo_mapper_support!
end
end

# defines datamapper fields on the model class
def init_datamapper_support!
self.class_eval do
sorcery_config.username_attribute_names.each do |username|
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README (https://github.com/NoamB/sorcery/pull/472/files#diff-cb3e0f2c76a671c083e8f001970f4631R232) says to add user properties manually, but here I see that these properties are declared automatically. Did I miss something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it works similar to the method that defines fields for mongoid, the idea was to inform users that they should declare the 'id' property and take care of adding validators, going to change the wording a bit.

property username, String, :length => 255
end
unless sorcery_config.username_attribute_names.include?(sorcery_config.email_attribute_name)
property sorcery_config.email_attribute_name, String, :length => 255
end
property sorcery_config.crypted_password_attribute_name, String, :length => 255
property sorcery_config.salt_attribute_name, String, :length => 255
end
end

# add virtual password accessor and ORM callbacks.
def init_orm_hooks!
if defined?(DataMapper) and self.ancestors.include?(DataMapper::Resource)
init_datamapper_hooks!
return
end
self.class_eval do
attr_accessor @sorcery_config.password_attribute_name
#attr_protected @sorcery_config.crypted_password_attribute_name, @sorcery_config.salt_attribute_name
Expand All @@ -88,6 +107,23 @@ def init_orm_hooks!
}
end
end

def init_datamapper_hooks!
self.class_eval do
attr_accessor @sorcery_config.password_attribute_name
before :valid? do
if self.send(sorcery_config.password_attribute_name).present?
encrypt_password
end
end
after :save do
if self.send(sorcery_config.password_attribute_name).present?
clear_virtual_password
end
end
end
end

end
end
end
Expand Down
5 changes: 5 additions & 0 deletions lib/sorcery/model/adapters/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ def update_many_attributes(attrs)
def update_single_attribute(name, value)
update_many_attributes(name => value)
end

def sorcery_save(options = {})
mthd = options.delete(:raise_on_failure) ? :save! : :save
self.send(mthd, options)
end
end

module ClassMethods
Expand Down
123 changes: 123 additions & 0 deletions lib/sorcery/model/adapters/datamapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
module Sorcery
module Model
module Adapters
module DataMapper
def self.included(klass)
klass.extend ClassMethods
klass.send(:include, InstanceMethods)
end

module InstanceMethods
def increment(attr)
self[attr] ||= 0
self[attr] += 1
self
end

def update_many_attributes(attrs)
attrs.each do |name, value|
value = value.utc if value.is_a?(ActiveSupport::TimeWithZone)
self.send(:"#{name}=", value)
end
self.class.get(self.id).update(attrs)
end

def update_single_attribute(name, value)
update_many_attributes(name => value)
end

def sorcery_save(options = {})
if options.key?(:validate) && ! options[:validate]
save!
else
save
end
end
end

module ClassMethods
def find(id)
get(id)
end

def delete_all
destroy
end

# NOTE
# DM Adapter dependent
# DM creates MySQL tables case insensitive by default
# http://datamapper.lighthouseapp.com/projects/20609-datamapper/tickets/1105
def find_by_credentials(credentials)
credential = credentials[0].dup
credential.downcase! if @sorcery_config.downcase_username_before_authenticating
@sorcery_config.username_attribute_names.each do |name|
@user = first(name => credential)
break if @user
end
!!@user ? get(@user.id) : nil
end

def find_by_provider_and_uid(provider, uid)
@user_klass ||= ::Sorcery::Controller::Config.user_class.to_s.constantize
user = first(@user_klass.sorcery_config.provider_attribute_name => provider, @user_klass.sorcery_config.provider_uid_attribute_name => uid)
!!user ? get(user.id) : nil
end

def find_by_id(id)
find(id)
rescue ::DataMapper::ObjectNotFoundError
nil
end

def find_by_activation_token(token)
user = first(sorcery_config.activation_token_attribute_name => token)
!!user ? get(user.id) : nil
end

def find_by_remember_me_token(token)
user = first(sorcery_config.remember_me_token_attribute_name => token)
!!user ? get(user.id) : nil
end

def find_by_username(username)
user = nil
sorcery_config.username_attribute_names.each do |name|
user = first(name => username)
break if user
end
!!user ? get(user.id) : nil
end

def transaction(&blk)
tap(&blk)
end

def find_by_sorcery_token(token_attr_name, token)
user = first(token_attr_name => token)
!!user ? get(user.id) : nil
end

def find_by_email(email)
user = first(sorcery_config.email_attribute_name => email)
!!user ? get(user.id) : nil
end

# NOTE
# DM Adapter dependent
def get_current_users
unless self.repository.adapter.is_a?(::DataMapper::Adapters::MysqlAdapter)
raise 'Unsupported DataMapper Adapter'
end
config = sorcery_config
ret = all(config.last_logout_at_attribute_name => nil) |
all(config.last_activity_at_attribute_name.gt => config.last_logout_at_attribute_name)
ret = ret.all(config.last_activity_at_attribute_name.not => nil)
ret = ret.all(config.last_activity_at_attribute_name.gt => config.activity_timeout.seconds.ago.utc)
ret
end
end
end
end
end
end
5 changes: 5 additions & 0 deletions lib/sorcery/model/adapters/mongo_mapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ def save!(options = {})
save(options)
end

def sorcery_save(options = {})
mthd = options.delete(:raise_on_failure) ? :save! : :save
self.send(mthd, options)
end

def update_many_attributes(attrs)
update_attributes(attrs)
end
Expand Down
5 changes: 5 additions & 0 deletions lib/sorcery/model/adapters/mongoid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ def update_many_attributes(attrs)
def update_single_attribute(name, value)
update_many_attributes(name => value)
end

def sorcery_save(options = {})
mthd = options.delete(:raise_on_failure) ? :save! : :save
self.send(mthd, options)
end
end

module ClassMethods
Expand Down
24 changes: 24 additions & 0 deletions lib/sorcery/model/submodules/activity_logging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ def self.included(base)
end

base.sorcery_config.after_config << :define_activity_logging_mongoid_fields if defined?(Mongoid) and base.ancestors.include?(Mongoid::Document)
if defined?(DataMapper) and base.ancestors.include?(DataMapper::Resource)
# NOTE raise exception if data-store is not supported
unless base.repository.adapter.is_a?(DataMapper::Adapters::MysqlAdapter)
raise 'Unsupported DataMapper Adapter'
end
base.sorcery_config.after_config << :define_activity_logging_datamapper_fields
end
end

module ClassMethods
Expand All @@ -46,6 +53,23 @@ def define_activity_logging_mongoid_fields
field sorcery_config.last_activity_at_attribute_name, :type => Time
field sorcery_config.last_login_from_ip_address_name, :type => String
end

def define_activity_logging_datamapper_fields
property sorcery_config.last_login_at_attribute_name, Time
property sorcery_config.last_logout_at_attribute_name, Time
property sorcery_config.last_activity_at_attribute_name, Time
property sorcery_config.last_login_from_ip_address_name, String
# Workaround local timezone retrieval problem NOTE dm-core issue #193
[sorcery_config.last_login_at_attribute_name,
sorcery_config.last_logout_at_attribute_name,
sorcery_config.last_activity_at_attribute_name].each do |sym|
alias_method "orig_#{sym}", sym
define_method(sym) do
t = send("orig_#{sym}")
t && Time.new(t.year, t.month, t.day, t.hour, t.min, t.sec, 0)
end
end
end
end
end
end
Expand Down
16 changes: 16 additions & 0 deletions lib/sorcery/model/submodules/brute_force_protection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def self.included(base)
if defined?(MongoMapper) and base.ancestors.include?(MongoMapper::Document)
base.sorcery_config.after_config << :define_brute_force_protection_mongo_mapper_fields
end
if defined?(DataMapper) and base.ancestors.include?(DataMapper::Resource)
base.sorcery_config.after_config << :define_brute_force_protection_datamapper_fields
end
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
end
Expand All @@ -63,6 +66,19 @@ def define_brute_force_protection_mongo_mapper_fields
key sorcery_config.lock_expires_at_attribute_name, Time
key sorcery_config.unlock_token_attribute_name, String
end

def define_brute_force_protection_datamapper_fields
property sorcery_config.failed_logins_count_attribute_name, Integer, :default => 0
property sorcery_config.lock_expires_at_attribute_name, Time
property sorcery_config.unlock_token_attribute_name, String
[sorcery_config.lock_expires_at_attribute_name].each do |sym|
alias_method "orig_#{sym}", sym
define_method(sym) do
t = send("orig_#{sym}")
t && Time.new(t.year, t.month, t.day, t.hour, t.min, t.sec, 0)
end
end
end
end

module InstanceMethods
Expand Down
Loading