mongoid-publishable 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.rvmrc +12 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +178 -0
- data/Rakefile +12 -0
- data/lib/mongoid/.DS_Store +0 -0
- data/lib/mongoid/publishable.rb +98 -0
- data/lib/mongoid/publishable/callback.rb +23 -0
- data/lib/mongoid/publishable/callback_collection.rb +11 -0
- data/lib/mongoid/publishable/callbacks.rb +50 -0
- data/lib/mongoid/publishable/queue.rb +51 -0
- data/lib/mongoid/publishable/queuing.rb +27 -0
- data/lib/mongoid/publishable/unpublished_error.rb +7 -0
- data/lib/mongoid/publishable/unpublished_object.rb +88 -0
- data/lib/mongoid/publishable/version.rb +5 -0
- data/lib/mongoid_publishable.rb +2 -0
- data/mongoid_publishable.gemspec +26 -0
- data/spec/.DS_Store +0 -0
- data/spec/mongoid/publishable/callback_collection_spec.rb +17 -0
- data/spec/mongoid/publishable/callback_spec.rb +46 -0
- data/spec/mongoid/publishable/callbacks_spec.rb +72 -0
- data/spec/mongoid/publishable/queue_spec.rb +106 -0
- data/spec/mongoid/publishable/queuing_spec.rb +25 -0
- data/spec/mongoid/publishable/unpublished_object_spec.rb +133 -0
- data/spec/mongoid/publishable_spec.rb +215 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/env.rb +8 -0
- data/spec/support/models/callbackable_object.rb +6 -0
- data/spec/support/models/publishable_object.rb +8 -0
- data/spec/support/models/publisher.rb +3 -0
- data/spec/support/models/queuing_controller.rb +8 -0
- metadata +173 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
if ! rvm list | grep -q ruby-1.9.3-p194 ; then
|
4
|
+
rvm install 1.9.3-p194
|
5
|
+
fi
|
6
|
+
|
7
|
+
rvm 1.9.3-p194@ryantownsend_mongoid_publishable --create
|
8
|
+
|
9
|
+
if ! gem list | grep -q bundler ; then
|
10
|
+
gem install --no-ri --no-rdoc bundler
|
11
|
+
bundle install --without production
|
12
|
+
fi
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Ryan Townsend
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
# Mongoid::Publishable
|
2
|
+
|
3
|
+
Ever wanted to allow your users to create something (or somethings) before authenticating. For example, you might want to let them write a review, before you ask them to login and publish it. This is what Mongoid::Publishable handles.
|
4
|
+
|
5
|
+
[![Build Status][2]][1]
|
6
|
+
|
7
|
+
[1]: http://travis-ci.org/ryantownsend/mongoid-publishable
|
8
|
+
[2]: https://secure.travis-ci.org/ryantownsend/mongoid-publishable.png?branch=master
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem "mongoid-publishable"
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install mongoid-publishable
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
Include the module in any models you want to be publishable:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class Review
|
32
|
+
include Mongoid::Document
|
33
|
+
include Mongoid::Publishable
|
34
|
+
# ...
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
It will use the `user_id` column by default, but you can override that by using `publisher_column`. It also assumes you're using the `id` attribute of the publisher, again you can override it using `publisher_foreign_key`, here's an example:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class ChatMessage
|
42
|
+
include Mongoid::Document
|
43
|
+
include Mongoid::Publishable
|
44
|
+
|
45
|
+
belongs_to :author
|
46
|
+
|
47
|
+
publisher_column :author_id
|
48
|
+
publisher_foreign_key :username
|
49
|
+
# ...
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
In your controllers, or anywhere you save the objects, you can swap out your `save` calls for `persist_and_publish!` calls, this method accepts an optional user. If none is passed, or the object that you do pass is nil, it'll raise an exception, so you can handle your authentication there:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class ReviewsController < ApplicationController
|
57
|
+
include Mongoid::Publishable::Queuing
|
58
|
+
|
59
|
+
def create
|
60
|
+
# create the review
|
61
|
+
@review = Review.new(params[:review])
|
62
|
+
# if persisted and published
|
63
|
+
if @review.persist_and_publish!(current_user)
|
64
|
+
# redirect as normal
|
65
|
+
redirect_to @review, notice: "Review created!"
|
66
|
+
# validation failed
|
67
|
+
else
|
68
|
+
render :new
|
69
|
+
end
|
70
|
+
# persisted, but publishing failed
|
71
|
+
rescue Mongoid::Publishable::UnpublishedError => exception
|
72
|
+
# the error actually contains the object
|
73
|
+
publishing_queue << exception.model
|
74
|
+
# send the user to the login page
|
75
|
+
redirect_to new_user_session_path
|
76
|
+
end
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
An alternative without the exception handling would be:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class ReviewsController < ApplicationController
|
84
|
+
include Mongoid::Publishable::Queuing
|
85
|
+
|
86
|
+
def create
|
87
|
+
# create the review
|
88
|
+
@review = Review.new(params[:review])
|
89
|
+
@review.publish_via(current_user)
|
90
|
+
# if persisted and published
|
91
|
+
if @review.save && @review.published?
|
92
|
+
# redirect as normal
|
93
|
+
redirect_to @review, notice: "Review created!"
|
94
|
+
# persisted, but publishing failed
|
95
|
+
elsif @review.persisted?
|
96
|
+
# the error actually contains the object
|
97
|
+
publishing_queue << @review
|
98
|
+
# send the user to the login page
|
99
|
+
redirect_to new_user_session_path
|
100
|
+
# validation failed
|
101
|
+
else
|
102
|
+
render :new
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
The advantage to the former style (using exceptions) is that you can handle them globally in your ApplicationController using this code:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
class ApplicationController < ActionController::Base
|
112
|
+
include Mongoid::Publishable::Queuing
|
113
|
+
|
114
|
+
rescue_from Mongoid::Publishable::UnpublishedError, with: :authenticate_to_publish
|
115
|
+
|
116
|
+
protected
|
117
|
+
def authenticate_to_publish(exception)
|
118
|
+
# add the object on to the queue
|
119
|
+
publishing_queue << exception.model
|
120
|
+
# send the user to the login page
|
121
|
+
redirect_to new_user_session_path
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
The publishing queue is stored in the user's session. After authentication, you'll want to call `publish_via` on the queue, which will then publish all the objects it contains. Here's an example:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
class UserSessionsController < ApplicationController
|
130
|
+
def create
|
131
|
+
@user = User.authenticate(params[:user])
|
132
|
+
if @user
|
133
|
+
|
134
|
+
# this is the key line:
|
135
|
+
publishing_queue.publish_via(@user)
|
136
|
+
|
137
|
+
session[:user_id] = @user.id
|
138
|
+
redirect_to root_path, notice: "Login successful!"
|
139
|
+
else
|
140
|
+
render :new
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# ...
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
Models are also provided with an `after_publish` callback that can be used like any other ActiveModel-style callback.
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
class Review
|
152
|
+
include Mongoid::Document
|
153
|
+
include Mongoid::Publishable
|
154
|
+
|
155
|
+
# it accepts a symbol referencing a method
|
156
|
+
after_publish :notify_item_owner
|
157
|
+
# it also accepts a block
|
158
|
+
after_publish do
|
159
|
+
puts "Mongoid::Publishable and it's creator are awesome!"
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def notify_item_owner
|
164
|
+
# do something
|
165
|
+
end
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
## Contributing
|
170
|
+
|
171
|
+
1. Fork it
|
172
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
173
|
+
3. Run the test suite (`rake`), ensure all specs pass
|
174
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
175
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
176
|
+
6. Create new Pull Request
|
177
|
+
|
178
|
+
After running specs, you can find a test coverage report at coverage/index.html
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
desc "Runs all the specs"
|
6
|
+
task default: %w(spec)
|
7
|
+
|
8
|
+
desc "Run specs"
|
9
|
+
RSpec::Core::RakeTask.new do |t|
|
10
|
+
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
11
|
+
# Put spec opts in a file named .rspec in root
|
12
|
+
end
|
Binary file
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "mongoid"
|
2
|
+
require "mongoid/publishable/callbacks"
|
3
|
+
require "mongoid/publishable/unpublished_error"
|
4
|
+
|
5
|
+
module Mongoid
|
6
|
+
module Publishable
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
base.send :include, InstanceMethods
|
10
|
+
base.send :include, Callbacks
|
11
|
+
base.class_eval do
|
12
|
+
# allow overwriting of the columns on an instance level
|
13
|
+
attr_writer :publisher_column, :publisher_foreign_key
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# gets/sets the column that stores the user_id
|
19
|
+
def publisher_column(name = nil)
|
20
|
+
@publisher_column = name if name
|
21
|
+
@publisher_column || :user_id
|
22
|
+
end
|
23
|
+
|
24
|
+
def publisher_foreign_key(name = nil)
|
25
|
+
@publisher_foreign_key = name if name
|
26
|
+
@publisher_foreign_key || :id
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module InstanceMethods
|
31
|
+
# saves to the db, and publishes if possible
|
32
|
+
def persist_and_publish(publisher = nil)
|
33
|
+
publish_via(publisher) && save
|
34
|
+
end
|
35
|
+
|
36
|
+
# saves to the db, and publishes if possible
|
37
|
+
# raises an UnpublishedError if unable to publish
|
38
|
+
def persist_and_publish!(publisher = nil)
|
39
|
+
# attempt save / publish
|
40
|
+
persist_and_publish(publisher)
|
41
|
+
# if it was saved to the DB
|
42
|
+
if persisted?
|
43
|
+
# return true if published, raise exception if not
|
44
|
+
published? || raise_unpublished_error
|
45
|
+
# if the save failed
|
46
|
+
else
|
47
|
+
# return false to allow traditional validation
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# delegate publisher column, allow overriding
|
53
|
+
def publisher_column
|
54
|
+
@publisher_column || self.class.publisher_column
|
55
|
+
end
|
56
|
+
|
57
|
+
# delegate foreign key, allow overriding
|
58
|
+
def publisher_foreign_key
|
59
|
+
@publisher_foreign_key || self.class.publisher_foreign_key
|
60
|
+
end
|
61
|
+
|
62
|
+
# publishes this instance using the id provided
|
63
|
+
def publish_via(publisher)
|
64
|
+
# ensure this isn't published and we have a publisher
|
65
|
+
if !published? && publisher
|
66
|
+
# load the publisher's foreign key
|
67
|
+
value = publisher.send(publisher_foreign_key)
|
68
|
+
# update this instance with the key
|
69
|
+
self.send("#{publisher_column}=", value)
|
70
|
+
# mark as just published
|
71
|
+
run_after_publish_callbacks
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# publishes this instance using the id provided
|
76
|
+
# and persists the publishing
|
77
|
+
def publish_via!(publisher)
|
78
|
+
publish_via(publisher) && save
|
79
|
+
end
|
80
|
+
|
81
|
+
# returns boolean of whether this instance has been published
|
82
|
+
def published?
|
83
|
+
persisted? && send(publisher_column)
|
84
|
+
end
|
85
|
+
|
86
|
+
# returns whether this instance needs publishing (persisted, not published)
|
87
|
+
def requires_publishing?
|
88
|
+
persisted? && !send(publisher_column)
|
89
|
+
end
|
90
|
+
|
91
|
+
# raises an UnpublishedError containing this object as a reference
|
92
|
+
def raise_unpublished_error
|
93
|
+
raise UnpublishedError.new.tap { |e| e.model = self }, "Unable to publish this #{self.class.name}"
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Publishable
|
3
|
+
class Callback
|
4
|
+
def initialize(*args, &block)
|
5
|
+
if block_given?
|
6
|
+
@method = block
|
7
|
+
elsif args.length == 1 && args[0].kind_of?(Symbol)
|
8
|
+
@method = args[0]
|
9
|
+
else
|
10
|
+
raise ArgumentError, "after_publish only allows a block or a symbol method reference as arguments"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def process(object)
|
15
|
+
if @method.respond_to?(:yield)
|
16
|
+
@method.yield(object)
|
17
|
+
else
|
18
|
+
object.send(@method)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "mongoid/publishable/callback"
|
2
|
+
require "mongoid/publishable/callback_collection"
|
3
|
+
|
4
|
+
module Mongoid
|
5
|
+
module Publishable
|
6
|
+
|
7
|
+
module Callbacks
|
8
|
+
def self.included(base)
|
9
|
+
base.extend ClassMethods
|
10
|
+
base.send :include, InstanceMethods
|
11
|
+
base.class_eval do
|
12
|
+
# handle after_publish callbacks
|
13
|
+
after_save :process_after_publish_callbacks, if: :run_after_publish_callbacks?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# returns the list of callbacks
|
19
|
+
def after_publish_callbacks
|
20
|
+
@after_publish_callbacks ||= CallbackCollection.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# adds a callback to the list
|
24
|
+
def after_publish(*args, &block)
|
25
|
+
Callback.new(*args, &block).tap do |callback|
|
26
|
+
after_publish_callbacks << callback
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
# process the callbacks
|
33
|
+
def process_after_publish_callbacks
|
34
|
+
self.class.after_publish_callbacks.process(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
# set to run the callbacks after save
|
38
|
+
def run_after_publish_callbacks
|
39
|
+
@run_after_publish_callbacks = true
|
40
|
+
end
|
41
|
+
|
42
|
+
# returns whether the object has just been published
|
43
|
+
def run_after_publish_callbacks?
|
44
|
+
!!@run_after_publish_callbacks
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|