Add it to your project's Gemfile
To set up has_moderated in your project, use
rails generate has_moderated:install rake db:migrate
This will generate a Moderation model and a migration for it.
When upgrading, rerun the generator
rails generate has_moderated:install
If there is a new migration file and you have filename conflicts, remove the old one and apply the new one, in case the schema changed.
Why use this instead of something like Papertrail?
Papertrail and vestal_versions for example, are designed not for moderation, but for tracking changes on models. If you use Papertrail for moderating, you will always have the “newest” version in the database, while previous changes will be recorded in a special table. The problem with this for moderation is that when you are showing records, you have to read from both the model table and the “changes” table in the database, and do additional processing to combine them. This will impact performance quite a bit.
In contrast, with this gem the database will always represent the last accepted/confirmed version of the model, and not necessarily the newest as with Papertrail. A “changes” table like in Papertrail is used here too, but it is used to store “suggested” (to-be-accepted) changes. Therefore you have no performance hit when querying for the last accepted version of the model, like you would have with Papertrail. You only have a performance hit when accepting a moderation, but this is irrelevant. Also this way all changes submitted are relative to the currently last accepted version and not to the newest version like in Papertrail.
So Papertrail and vestal_versions are designed for model versioning and changes tracking, while this gem is designed for moderation. The database will not be changed until a moderation is actually accepted.
Note that because of this, handling associations is quite a bit harder, so this gem is a little bit more complex. Therefore is recommended you test moderated associations before deploying to production, to avoid any possible bugs.
To moderate one or more attributes, add
has_moderated :attr1, :attr2
to your model.
If you want to moderate the creation of a record (for example, you don't want new blog posts to show up until they are accepted by a moderator), use
You can also specify associations that need to be saved for moderation as well (if moderating the creation of new records) - for example, if a Post has_many :links, and you want to submit these links to moderation as well (note, if you don't, they will be discarded), use
has_moderated_create :with_associations => [:links]
in your Post model (post.rb). This only matters when you use has_moderated_create. You can also use
has_moderated_create :with_associations => :all
to include all associations, but I recommend you explicitly specify them if possible. By default, no associations are included.
To moderate destruction of records, use
Warning! Always put has_many etc. BEFORE has_moderated calls in your model!
Moderating associations on existing records
There is an automatic way to moderate associations on already-created records. Use:
has_moderated_association :links, :comments
to moderate the links and comments associations. You can use :all to moderate all associations, but I recommend you explicitly specify them.
You can also manually add associations to moderation like so
post.add_associations_moderated(:comments => [new_comment], :scores => [new_score])
The values can be either new records (not in the database), existing records, or Fixnum (numerical) IDs. Please note in this case you should not use .build to create new records, because if you call save on the parent model it will automatically create the record (this only applies if you use the manual way).
Moderation for removing associated records from existing records was added in version 0.0.32 (automatic).
To see pending moderations, simply call
You can also see moderations for a specific record. For example, if you have Post model, you can call moderations on it.
post = Post.first post.moderations
Moderation is a normal ActiveRecord model, you can inspect it in rails console to see what it holds. Data (attr_value) is serialized in YAML format and can be deserialized by calling
To accept a moderation, call
to discard (destroy) it, call
To bypass moderation (e.g. for admin users), wrap your code into moderatable_updating like so:
record.moderatable_updating(current_user.is_admin?) record.update_attributes(...) end
If the parameter is false, then it will have no effect. If the parameter is true, then moderations will be disabled for code inside the passed block. The parameter is optional and defaults to true.
Special data attached to moderations
If you need any special data attached to the Moderation model, you can use the moderation_creating hook.
Attaching user ID to moderations
For example you have a Comment model, and it is moderated. But your visitors are logged in when they post comments, so you want to add the user ID to the moderation.
first create a migration to add user_id into Moderation
rails g migration AddUserIdToModerations user_id:integer
run rake db:migrate
in models/comment.rb, add something like this:
attr_accessor :moderation_user_id moderation_creating do |m| m.user_id = self.moderation_user_id end
This is just one example on how to do it. You need the attr_accessor here because we are going to pass the user ID from the controller. In the hook you have access to the Moderation model just before it is saved, so you can modify it like any other model. Now just set moderation_user_id on the model before you save it:
c = Comment.new c.moderation_user_id = current_user.id c.save
There is support for CarrierWave uploads to be moderated. You must put this line into the model that has a CarrierWave uploader mounted:
Right now *you must use the field name “photo” for the upload filename* because it is currently hardcoded into this module. If you do this, then moderation for the photo should work correctly.
It does not matter if this model has any moderation itself or if you just have an association to it from some other model that is moderated. You must include this module in either case, because it ensures proper serialization of the photo information. If you want to moderate changes to the photo on this model itself (e.g. not only when its associated to something else), add also
If you need some more customization look at this module in lib/has_moderated/carrier_wave.rb and just copy the methods into your model and customize them (with some care when you do this, some methods should be class methods).
I've tested this project using RSpec. You can find the tests in
You can run the tests by running
in the root directory of this gem (so you have to clone it first).
to moderate has_many :through assoc, must add both associations to moderation
This is just for my personal todo list…
hm maybe has_moderated_association could be just a has_moderated_create on the assoc? just check stuff like if task_id is not nil or something. could be much easier to implement then
also maybe use dirty github.com/rails/rails/blob/master/activemodel/lib/active_model/dirty.rb
and provide the same interface to show changes before accepting a moderation (just overwrite the dirty methods on the instance, also overwrite save at a low level to throw exception since its only a preview)
to make it possible to support other than activerecord, use activemodel when its possible AND try to make it so its possible to call the last step before activerecord (e.g. a func that reads all the values of current attributes and figures out which ones changed) - and use this in activerecord.
use YARD for docs check again railscasts episodes for gems (e.g. railties) -
make hasone, hasmany etc. each a separate “extension” (like carrierwave). for tests just use 2 models (task, subtask) and dont define has_many in model but in test
Amend moderations… Eg if you create a new record and save it, then change something additionally and save again. Preview method which gives changed object but doesnt save it.
If you have problems open an issue here on Github.
You may encounter problems with models that have some sort of non-serializable attributes. This might be something like file attachments, you'll have to try it to see. If you have a problem like that you can extract the problematic attributes into a seperate has_one association. If you moderate create, save that model without the foreign key first, and then use has_moderated_create :with_associations => [:association_name] and add the association (to the existing associated model) before saving the moderated model. If you have questions about this or don't understand what I mean, open an issue here at GitHub and I will explain it further.
It is also possible you will encounter problems with some heavily customized associations, or with has_many :through associations (although I have tested this for a simple, ordinary case and it works). If that happens open an issue here on Github.
This project rocks and uses MIT-LICENSE.