Skip to content

Commit

Permalink
Merge pull request #18 from johnnyshields/multi-user
Browse files Browse the repository at this point in the history
User Relation + Multi-User + Thread Safety
  • Loading branch information
johnnyshields committed Aug 9, 2014
2 parents 5829396 + 14d4b18 commit 8cc8c69
Show file tree
Hide file tree
Showing 25 changed files with 867 additions and 343 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## [0.4.0](https://github.com/tbpro/mongoid_userstamp/releases/tag/v0.4.0) - 2014-02-24

* Improvement
* BREAKING: Change userstamp keys to use Mongoid relations. This will change the underlying database field names, and as such will require a migration.
* BREAKING: Do not include Mongoid::Userstamp in "User" class by default.
* Mongoid::Userstamp config initializer is now optional.
* Add support for multiple user classes.
* Add class-level config override capability for both users and userstamped model classes.
* Add automatic support for `RequestStore` gem as drop-in replacement for `Thread.current`
* Refactor
* DEPRECATION: `created_column` config is now `created_name`
* DEPRECATION: `created_updated` config is now `created_updated`
* DEPRECATION: `user_model` config is no longer used. Instead, include Mongoid::Userstamp::User in your user model.
* Substantial refactoring of all classes and test cases. Among other changes, all access to `Thread.current` variables is now done in the Mongoid::Userstamp module singleton.

## [0.3.2](https://github.com/tbpro/mongoid_userstamp/releases/tag/v0.3.2) - 2014-01-12

* Fix bad gem release
Expand Down
142 changes: 104 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# MongoidUserstamp [![Build Status](https://secure.travis-ci.org/tbpro/mongoid_userstamp.png)](https://travis-ci.org/tbpro/mongoid_userstamp) [![Code Climate](https://codeclimate.com/github/tbpro/mongoid_userstamp.png)](https://codeclimate.com/github/tbpro/mongoid_userstamp)
# Mongoid::Userstamp [![Build Status](https://secure.travis-ci.org/tbpro/mongoid_userstamp.png)](https://travis-ci.org/tbpro/mongoid_userstamp) [![Code Climate](https://codeclimate.com/github/tbpro/mongoid_userstamp.png)](https://codeclimate.com/github/tbpro/mongoid_userstamp)

MongoidUserstamp adds stamp columns for created by and updated by
Mongoid::Userstamp adds stamp columns for created by and updated by
information within Rails applications using Mongoid ORM.

## Version Support

MongoidUserstamp is tested on the following versions:
Mongoid::Userstamp is tested on the following versions:

* Ruby 1.9.3 and 2.0.0
* Rails 3
Expand All @@ -19,59 +19,124 @@ MongoidUserstamp is tested on the following versions:

## Usage

Mongoid::Userstamp does the following:
* Defines Mongoid `belongs_to` relations to the user class for `created_by` and `updated_by` on each class where `Mongoid::Userstamp` is included
* Automatically tracks the current user via a `before_filter` (see Rails Integration below)
* Sets the `created_by` and `updated_by` values in `before_save` and `before_update` callbacks respectively on the target models.
* Adds methods to the user class to check for the current user.

```ruby
# Default config
# Default config (optional unless you want to customize the values)
Mongoid::Userstamp.config do |c|

# Default config values

c.user_reader = :current_user
c.user_model = :user

c.created_column = :created_by
c.created_accessor = :creator

c.updated_column = :updated_by
c.updated_accessor = :updater

# Optional config values

# c.created_alias = :c
# c.updated_alias = :u
c.created_name = :created_by
c.updated_name = :updated_by
end

# Example model
class Person
# Example model class
class Product
include Mongoid::Document
include Mongoid::Userstamp

# optional class-level config override
# mongoid_userstamp user_model: 'MyUser',
# created_name: :creator,
# updated_name: :updater,
end

# Example user class (you can skip this step you have a single user class is named "User")
class MyUser
include Mongoid::Document
include Mongoid::Userstamp::User

# optional class-level config override
# mongoid_userstamp_user reader: :current_my_user
end

# Create instance
p = Person.create
p = Product.create

# Updater ObjectID or nil
p.updated_by
# Creator ObjectID | Updater ObjectID
p.created_by_id | p.updated_by_id
# => BSON::ObjectId('4f7c719f476da850ba000039')

# Updater instance or nil
p.updater
# Creator instance | Updater instance
p.created_by | p.updated_by
# => <User _id: 4f7c719f476da850ba000039>

# Set updater manually (usually not required)
p.updater = my_user # can be a Mongoid::Document or a BSON::ObjectID
# => sets updated_by to my_user's ObjectID
# Set creator/updater manually (usually not required)
p.created_by = MyUser.where(name: 'Will Smith')
p.updated_by = MyUser.where(name: 'DJ Jazzy Jeff')
```

# Creator ObjectID or nil
p.created_by
# => BSON::ObjectId('4f7c719f476da850ba000039')

# Creator instance or nil
p.creator
# => <User _id: 4f7c719f476da850ba000039>
## Rails Integration

Popular Rails authentication frameworks such as Devise and Sorcery make a `current_user` method available in
your Controllers. Mongoid::Userstamp will automatically use this to set its user reference in a `before_filter`
on each request. (You can set an alternative method name via the `user_reader` config.)

*Gotcha:* If you have special controller actions which change/switch the current user to a new user, you will
need to set `User.current = new_user` after the switch occurs.


## Thread Safety

Mongoid::Userstamp stores all-related user variables in `Thread.current`. If the
[RequestStore](https://github.com/steveklabnik/request_store) gem is installed, Mongoid::Userstamp
will automatically store variables in the `RequestStore.store` instead. RequestStore is recommended
for threaded web servers like Thin or Puma.


## Advanced Usage: Multiple User Classes

Most Rails apps use a single user model. However, Mongoid::Userstamp supports using multiple user models
at once, and will track a separate current_user for each class.

Please note that each model may subscribe to only one user type for its userstamps, set via the
`:user_model` option.

```ruby
class Admin
include Mongoid::Document
include Mongoid::Userstamp::User

mongoid_userstamp_user reader: :current_admin
end

class Customer
include Mongoid::Document
include Mongoid::Userstamp::User

mongoid_userstamp_user reader: :current_customer
end

class Album
include Mongoid::Document
include Mongoid::Userstamp

mongoid_userstamp user_model: 'Customer'
end

class Label
include Mongoid::Document
include Mongoid::Userstamp

mongoid_userstamp user_model: 'Admin'
end

# Set current user for each type
Admin.current = Admin.where(name: 'Biz Markie')
Customer.current = Customer.where(name: 'Sir Mix-A-Lot')

# In your Controller action
album = Album.new('Baby Got Back Single')
album.save!
album.created_by.name #=> 'Sir Mix-A-Lot'

# Set creator manually (usually not required)
p.creator = my_user # can be a Mongoid::Document or a BSON::ObjectID
# => sets created_by to my_user._id
label = Label.new('Cold Chillin Records')
label.save!
label.created_by.name #=> 'Biz Markie'
```

## Contributing
Expand All @@ -84,6 +149,7 @@ Please use Ruby 1.9.3 hash syntax, as Mongoid 3 requires Ruby >= 1.9.3

* [Thomas Boerger](http://www.tbpro.de)
* [John Shields](https://github.com/johnnyshields)
* [Bharat Gupta](https://github.com/Bharat311)

## Copyright

Expand Down
93 changes: 41 additions & 52 deletions lib/mongoid/userstamp.rb
Original file line number Diff line number Diff line change
@@ -1,88 +1,77 @@
# -*- encoding : utf-8 -*-

module Mongoid

module Userstamp
extend ActiveSupport::Concern

included do
field Userstamp.config.updated_column, Userstamp.field_opts(Userstamp.config.updated_column_opts)
field Userstamp.config.created_column, Userstamp.field_opts(Userstamp.config.created_column_opts)
Mongoid::Userstamp.add_model_class(self)
end

before_save :set_updater
before_create :set_creator
module ClassMethods

define_method Userstamp.config.updated_accessor do
Userstamp.find_user self.send(Userstamp.config.updated_column)
def mongoid_userstamp(opts = {})
mongoid_userstamp_config(opts)
self.send(:include, Mongoid::Userstamp::Model) unless self.included_modules.include?(Mongoid::Userstamp::Model)
end

define_method Userstamp.config.created_accessor do
Userstamp.find_user self.send(Userstamp.config.created_column)
def mongoid_userstamp_config(opts = {})
@mongoid_userstamp_config ||= Mongoid::Userstamp::ModelConfig.new(opts)
end
end

define_method "#{Userstamp.config.updated_accessor}=" do |user|
self.send("#{Userstamp.config.updated_column}=", Userstamp.extract_bson_id(user))
end
class << self

define_method "#{Userstamp.config.created_accessor}=" do |user|
self.send("#{Userstamp.config.created_column}=", Userstamp.extract_bson_id(user))
def config(&block)
@config ||= Mongoid::Userstamp::GemConfig.new(&block)
end

protected
# @deprecated
def configure(&block)
warn 'Mongoid::Userstamp.configure is deprecated. Please use Mongoid::Userstamp.config instead'
config(&block)
end

def set_updater
return if !Userstamp.has_current_user?
self.send("#{Userstamp.config.updated_accessor}=", Userstamp.current_user)
def current_user(user_class = nil)
user_class ||= user_classes.first
store[userstamp_key(user_class)]
end

def set_creator
return if !Userstamp.has_current_user? || self.send(Userstamp.config.created_column)
self.send("#{Userstamp.config.created_accessor}=", Userstamp.current_user)
def current_user=(value)
set_current_user(value)
end
end

class << self
def config(&block)
if block_given?
@@config = Userstamp::Config.new(&block)
else
@@config ||= Userstamp::Config.new
end
# It is better to provide the user class, in case the value is nil.
def set_current_user(value, user_class = nil)
user_class ||= value ? value.class : user_classes.first
store[userstamp_key(user_class)] = value
end

# DEPRECATED
def configure(&block)
warn 'Mongoid::Userstamp.configure is deprecated. Please use Mongoid::Userstamp.config instead'
config(block)
def model_classes
(@model_classes || []).map{|c| c.is_a?(Class) ? c : c.to_s.classify.constantize }
end

def field_opts(opts)
{type: ::Moped::BSON::ObjectId}.reverse_merge(opts || {})
def add_model_class(model)
@model_classes ||= []
@model_classes << model
end

def has_current_user?
config.user_model.respond_to?(:current)
def user_classes
(@user_classes || []).map{|c| c.is_a?(Class) ? c : c.to_s.classify.constantize }
end

def current_user
config.user_model.try(:current)
def add_user_class(user)
@user_classes ||= []
@user_classes << user
end

def extract_bson_id(value)
if value.respond_to?(:_id)
value.try(:_id)
elsif value.present?
::Moped::BSON::ObjectId.from_string(value.to_s)
else
nil
end
def userstamp_key(model)
"mongoid_userstamp/#{model.to_s.underscore}".to_sym
end

def find_user(user_id)
begin
user_id ? Userstamp.config.user_model.unscoped.find(user_id) : nil
rescue Mongoid::Errors::DocumentNotFound => e
nil
end
def store
defined?(RequestStore) ? RequestStore.store : Thread.current
end
end
end
Expand Down
30 changes: 0 additions & 30 deletions lib/mongoid/userstamp/config.rb

This file was deleted.

Loading

0 comments on commit 8cc8c69

Please sign in to comment.