mongoid-publishable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|