mongoid-publishable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
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
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ services:
5
+ - mongodb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mongoid_publishable.gemspec
4
+ gemspec
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,11 @@
1
+ module Mongoid
2
+ module Publishable
3
+ class CallbackCollection < Array
4
+ def process(object)
5
+ each do |callback|
6
+ callback.process(object)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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