Mongoid::Publishable

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.

Build Status Code Climate

Installation

Add this line to your application's Gemfile:

gem "mongoid-publishable"

And then execute:

$ bundle

Or install it yourself as:

$ gem install mongoid-publishable

Usage

Include the module in any models you want to be publishable:

class Review
  include Mongoid::Document
  include Mongoid::Publishable
  # ...
end

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:

class ChatMessage
  include Mongoid::Document
  include Mongoid::Publishable

  belongs_to :author

  publisher_column :author_id
  publisher_foreign_key :username
  # ...
end

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:

class ReviewsController < ApplicationController
  include Mongoid::Publishable::Queuing

  def create
    # create the review
    @review = Review.new(params[:review])
    # if persisted and published
    if @review.persist_and_publish!(current_user)
      # redirect as normal
      redirect_to @review, notice: "Review created!"
    # validation failed
    else
      render :new
    end
  # persisted, but publishing failed
  rescue Mongoid::Publishable::UnpublishedError => exception
    # the error actually contains the object
    publishing_queue << exception.model
    # send the user to the login page
    redirect_to new_user_session_path
  end
end

An alternative without the exception handling would be:

class ReviewsController < ApplicationController
  include Mongoid::Publishable::Queuing

  def create
    # create the review
    @review = Review.new(params[:review])
    @review.publish_via(current_user)
    # if persisted and published
    if @review.save && @review.published?
      # redirect as normal
      redirect_to @review, notice: "Review created!"
    # persisted, but publishing failed
    elsif @review.persisted?
      # the error actually contains the object
      publishing_queue << @review
      # send the user to the login page
      redirect_to new_user_session_path
    # validation failed
    else
      render :new
    end
  end
end

The advantage to the former style (using exceptions) is that you can handle them globally in your ApplicationController using this code:

class ApplicationController < ActionController::Base
  include Mongoid::Publishable::Queuing

  rescue_from Mongoid::Publishable::UnpublishedError, with: :authenticate_to_publish

  protected
  def authenticate_to_publish(exception)
    # add the object on to the queue
    publishing_queue << exception.model
    # send the user to the login page
    redirect_to new_user_session_path
  end
end

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:

class UserSessionsController < ApplicationController
  include Mongoid::Publishable::Queuing

  def create
    @user = User.authenticate(params[:user])
    if @user

      # this is the key line:
      publishing_queue.publish_via(@user)

      session[:user_id] = @user.id
      redirect_to root_path, notice: "Login successful!"
    else
      render :new
    end
  end

  # ...
end

Models are also provided with an after_publish callback that can be used like any other ActiveModel-style callback.

class Review
  include Mongoid::Document
  include Mongoid::Publishable

  # it accepts a symbol referencing a method
  after_publish :notify_item_owner
  # it also accepts a block
  after_publish do
    puts "Mongoid::Publishable and it's creator are awesome!"
  end

  private
  def notify_item_owner
    # do something
  end
end

You can define custom publishing requirements:

class Tweet
  include Mongoid::Document
  include Mongoid::Publishable

  publishing_conditions do |tweet|
    tweet.user.has_twitter_account?
  end # => should return true / false
end

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Run the test suite (rake), ensure all specs pass
  4. Commit your changes (git commit -am 'Add some feature')
  5. Push to the branch (git push origin my-new-feature)
  6. Create new Pull Request

After running specs, you can find a test coverage report at coverage/index.html.

Note: you can run the test suite automatically by starting Guard, do this with the following command:

$ bundle exec guard start