Giter VIP home page Giter VIP logo

mongoid-history's Introduction

Mongoid History

Gem Version Build Status Code Climate

Mongoid History tracks historical changes for any document, including embedded ones. It achieves this by storing all history tracks in a single collection that you define. Embedded documents are referenced by storing an association path, which is an array of document_name and document_id fields starting from the top most parent document and down to the embedded document that should track history.

This gem also implements multi-user undo, which allows users to undo any history change in any order. Undoing a document also creates a new history track. This is great for auditing and preventing vandalism, but is probably not suitable for use cases such as a wiki (but we won't stop you either).

Version Support

Mongoid History supports the following dependency versions:

  • Ruby 2.3+
  • Mongoid 3.1+
  • Recent JRuby versions

Earlier Ruby versions may work but are untested.

Install

gem 'mongoid-history'

Usage

Create a history tracker

Create a new class to track histories. All histories are stored in this tracker. The name of the class can be anything you like. The only requirement is that it includes Mongoid::History::Tracker

# app/models/history_tracker.rb
class HistoryTracker
  include Mongoid::History::Tracker
end

Set default tracker class name (optional)

Mongoid::History will use the first loaded class to include Mongoid::History::Tracker as the default history tracker. If you are using multiple Tracker classes, you should set a global default in a Rails initializer:

# config/initializers/mongoid_history.rb
# initializer for mongoid-history
# assuming HistoryTracker is your tracker class
Mongoid::History.tracker_class_name = :history_tracker

Create trackable classes and objects

class Post
  include Mongoid::Document
  include Mongoid::Timestamps

  # history tracking all Post documents
  # note: tracking will not work until #track_history is invoked
  include Mongoid::History::Trackable

  field           :title
  field           :body
  field           :rating
  embeds_many     :comments

  # telling Mongoid::History how you want to track changes
  # dynamic fields will be tracked automatically (for MongoId 4.0+ you should include Mongoid::Attributes::Dynamic to your model)
  track_history   :on => [:title, :body],       # track title and body fields only, default is :all
                  :modifier_field => :modifier, # adds "belongs_to :modifier" to track who made the change, default is :modifier, set to nil to not create modifier_field
                  :modifier_field_inverse_of => :nil, # adds an ":inverse_of" option to the "belongs_to :modifier" relation, default is not set
                  :modifier_field_optional => true, # marks the modifier relationship as optional (requires Mongoid 6 or higher)
                  :version_field => :version,   # adds "field :version, :type => Integer" to track current version, default is :version
                  :track_create  => true,       # track document creation, default is true
                  :track_update  => true,       # track document updates, default is true
                  :track_destroy => true        # track document destruction, default is true
end

class Comment
  include Mongoid::Document
  include Mongoid::Timestamps

  # declare that we want to track comments
  include Mongoid::History::Trackable

  field             :title
  field             :body
  embedded_in       :post, :inverse_of => :comments

  # track title and body for all comments, scope it to post (the parent)
  # also track creation and destruction
  track_history     :on => [:title, :body], :scope => :post, :track_create => true, :track_destroy => true

  # For embedded polymorphic relations, specify an array of model names or its polymorphic name
  # e.g. :scope => [:post, :image, :video]
  #      :scope => :commentable

end

# the modifier class
class User
  include Mongoid::Document
  include Mongoid::Timestamps

  field             :name
end

user = User.create(:name => "Aaron")
post = Post.create(:title => "Test", :body => "Post", :modifier => user)
comment = post.comments.create(:title => "test", :body => "comment", :modifier => user)
comment.history_tracks.count # should be 1

comment.update_attributes(:title => "Test 2")
comment.history_tracks.count # should be 2

track = comment.history_tracks.last

track.undo! user # comment title should be "Test"

track.redo! user # comment title should be "Test 2"

# undo comment to version 1 without save
comment.undo nil, from: 1, to: comment.version

# undo last change
comment.undo! user

# undo versions 1 - 4
comment.undo! user, :from => 4, :to => 1

# undo last 3 versions
comment.undo! user, :last => 3

# redo versions 1 - 4
comment.redo! user, :from => 1, :to => 4

# redo last 3 versions
comment.redo! user, :last => 3

# redo version 1
comment.redo! user, 1

# delete post
post.destroy

# undelete post
post.undo! user

# disable tracking for comments within a block
Comment.disable_tracking do
  comment.update_attributes(:title => "Test 3")
end

# disable tracking for comments by default
Comment.disable_tracking!

# enable tracking for comments within a block
Comment.enable_tracking do
  comment.update_attributes(:title => "Test 3")
end

# renable tracking for comments by default
Comment.enable_tracking!

# globally disable all history tracking within a block
Mongoid::History.disable do
  comment.update_attributes(:title => "Test 3")
  user.update_attributes(:name => "Eddie Van Halen")
end

# globally disable all history tracking by default
Mongoid::History.disable!

# globally enable all history tracking within a block
Mongoid::History.enable do
  comment.update_attributes(:title => "Test 3")
  user.update_attributes(:name => "Eddie Van Halen")
end

# globally renable all history tracking by default
Mongoid::History.enable!

You may want to track changes on all fields.

class Post
  include Mongoid::Document
  include Mongoid::History::Trackable

  field           :title
  field           :body
  field           :rating

  track_history   :on => [:fields] # all fields will be tracked
end

You can also track changes on all embedded relations.

class Post
  include Mongoid::Document
  include Mongoid::History::Trackable

  embeds_many :comments
  embeds_one  :content

  track_history   :on => [:embedded_relations] # all embedded relations will be tracked
end

Include embedded objects attributes in parent audit

Modify above Post and Comment classes as below:

class Post
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::History::Trackable

  field           :title
  field           :body
  field           :rating
  embeds_many     :comments

  track_history   :on => [:title, :body, :comments],
                  :modifier_field => :modifier,
                  :modifier_field_inverse_of => :nil,
                  :version_field => :version,
                  :track_create   =>  true,     # track create on Post
                  :track_update   =>  true,
                  :track_destroy  =>  false
end

class Comment
  include Mongoid::Document
  include Mongoid::Timestamps

  field             :title
  field             :body
  embedded_in       :post, :inverse_of => :comments
end

user = User.create(:name => "Aaron")
post = Post.create(:title => "Test", :body => "Post", :modifier => user)
comment = post.comments.build(:title => "test", :body => "comment", :modifier => user)
post.save
post.history_tracks.count # should be 1

comment.respond_to?(:history_tracks) # should be false

track = post.history_tracks.first
track.original # {}
track.modified # { "title" => "Test", "body" => "Post", "comments" => [{ "_id" => "575fa9e667d827e5ed00000d", "title" => "test", "body" => "comment" }], ... }

Whitelist the tracked attributes of embedded relations

If you don't want to track all the attributes of embedded relations in parent audit history, you can whitelist the attributes as below:

class Book
  include Mongoid::Document
  ...
  embeds_many :pages
  track_history :on => { :pages => [:title, :content] }
end

class Page
  include Mongoid::Document
  ...
  field :number
  field :title
  field :subtitle
  field :content
  embedded_in :book
end

It will now track only _id (Mandatory), title and content attributes for pages relation.

Retrieving the list of tracked static and dynamic fields

class Book
  ...
  field             :title
  field             :author
  field             :price
  track_history     :on => [:title, :price]
end

Book.tracked_fields           #=> ["title", "price"]
Book.tracked_field?(:title)   #=> true
Book.tracked_field?(:author)  #=> false

Retrieving the list of tracked relations

class Book
  ...
  track_history :on => [:pages]
end

Book.tracked_relation?(:pages)    #=> true
Book.tracked_embeds_many          #=> ["pages"]
Book.tracked_embeds_many?(:pages) #=> true

Skip soft-deleted embedded objects with nested tracking

Default paranoia field is deleted_at. You can use custom field for each class as below:

class Book
  include Mongoid::Document
  include Mongoid::History::Trackable
  embeds_many :pages
  track_history on: :pages
end

class Page
  include Mongoid::Document
  include Mongoid::History::Trackable
  ...
  embedded_in :book
  history_settings paranoia_field: :removed_at
end

This will skip the page documents with removed_at set to a non-blank value from nested tracking

Formatting fields

You can opt to use a proc or string interpolation to alter attributes being stored on a history record.

class Post
  include Mongoid::Document
  include Mongoid::History::Trackable

  field           :title
  track_history   on: :title,
                  format: { title: ->(t){ t[0..3] } }

This also works for fields on an embedded relations.

class Book
  include Mongoid::Document
  include Mongoid::History::Trackable

  embeds_many :pages
  track_history on: :pages,
                format: { pages: { number: 'pg. %d' } }
end

class Page
  include Mongoid::Document
  include Mongoid::History::Trackable

  field :number, type: Integer
  embedded_in :book
end

Displaying history trackers as an audit trail

In your Controller:

# Fetch history trackers
@trackers = HistoryTracker.limit(25)

# get change set for the first tracker
@changes = @trackers.first.tracked_changes
  #=> {field: {to: val1, from: val2}}

# get edit set for the first tracker
@edits = @trackers.first.tracked_edits
  #=> { add: {field: val},
  #     remove: {field: val},
  #     modify: { to: val1, from: val2 },
  #     array: { add: [val2], remove: [val1] } }

In your View, you might do something like (example in HAML format):

%ul.changes
  - (@edits[:add]||[]).each do |k,v|
    %li.remove Added field #{k} value #{v}

  - (@edits[:modify]||[]).each do |k,v|
    %li.modify Changed field #{k} from #{v[:from]} to #{v[:to]}

  - (@edits[:array]||[]).each do |k,v|
    %li.modify
      - if v[:remove].nil?
        Changed field #{k} by adding #{v[:add]}
      - elsif v[:add].nil?
        Changed field #{k} by removing #{v[:remove]}
      - else
        Changed field #{k} by adding #{v[:add]} and removing #{v[:remove]}

  - (@edits[:remove]||[]).each do |k,v|
    %li.remove Removed field #{k} (was previously #{v})

Adding Userstamp on History Trackers

To track the User in the application who created the HistoryTracker, add the Mongoid::Userstamp gem to your HistoryTracker class. This will add a field called created_by and an accessor creator to the model (you can rename these via gem config).

class MyHistoryTracker
  include Mongoid::History::Tracker
  include Mongoid::Userstamp
end

Setting Modifier Class Name

If your app will track history changes to a user, Mongoid History looks for these modifiers in the User class by default. If you have named your 'user' accounts differently, you will need to add that to your Mongoid History config:

The following examples set the modifier class name using a Rails initializer:

If your app uses a class Author:

# config/initializers/mongoid-history.rb
# initializer for mongoid-history

Mongoid::History.modifier_class_name = 'Author'

Or perhaps you are namespacing to a module:

Mongoid::History.modifier_class_name = 'CMS::Author'

Conditional :if and :unless options

The track_history method supports :if and :unless options which will skip generating the history tracker unless they are satisfied. These options can take either a method Symbol or a Proc. They behave identical to how :if and :unless behave in Rails model callbacks.

  track_history on: [:ip],
                if: :should_i_track_history?,
                unless: ->(obj){ obj.method_to_skip_history }

Using an alternate changes method

Sometimes you may wish to provide an alternate method for determining which changes should be tracked. For example, if you are using embedded documents and nested attributes, you may wish to write your own changes method that includes changes from the embedded documents.

Mongoid::History provides an option named :changes_method which allows you to do this. It defaults to :changes, which is the standard changes method.

Note: Specify additional fields that are provided with a custom changes_method with the :on option.. To specify current fields and additional fields, use fields.keys + [:custom]

Example:

class Foo
  include Mongoid::Document
  include Mongoid::History::Trackable

  attr_accessor :ip

  track_history on: [:ip], changes_method: :my_changes

  def my_changes
    unless ip.nil?
      changes.merge(ip: [nil, ip])
    else
      changes
    end
  end
end

Example with embedded & nested attributes:

class Foo
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::History::Trackable

  field      :bar
  embeds_one :baz
  accepts_nested_attributes_for :baz

  # use changes_with_baz to include baz's changes in this document's
  # history.
  track_history   on: fields.keys + [:baz], changes_method: :changes_with_baz

  def changes_with_baz
    if baz.changed?
      changes.merge(baz: summarized_changes(baz))
    else
      changes
    end
  end

  private
  # This method takes the changes from an embedded doc and formats them
  # in a summarized way, similar to how the embedded doc appears in the
  # parent document's attributes
  def summarized_changes obj
    obj.changes.keys.map do |field|
      next unless obj.respond_to?("#{field}_change")
      [ { field => obj.send("#{field}_change")[0] },
        { field => obj.send("#{field}_change")[1] } ]
    end.compact.transpose.map do |fields|
      fields.inject({}) {|map,f| map.merge(f)}
    end
  end
end

class Baz
  include Mongoid::Document
  include Mongoid::Timestamps

  embedded_in :foo
  field :value
end

For more examples, check out spec/integration/integration_spec.rb.

Multiple Trackers

You can have different trackers for different classes like so.

class First
  include Mongoid::Document
  include Mongoid::History::Trackable

  field :text, type: String
  track_history on: [:text],
                tracker_class_name: :first_history_tracker
end

class Second
  include Mongoid::Document
  include Mongoid::History::Trackable

  field :text, type: String
  track_history on: [:text],
                tracker_class_name: :second_history_tracker
end

class FirstHistoryTracker
  include Mongoid::History::Tracker
end

class SecondHistoryTracker
  include Mongoid::History::Tracker
end

Note that if you are using a tracker for an embedded object that is different from the parent's tracker, redos and undos will not work. You have to use the same tracker for these to work across embedded relationships.

If you are using multiple trackers and the tracker_class_name parameter is not specified, Mongoid::History will use the default tracker configured in the initializer file or whatever the first tracker was loaded.

Dependent Restrict Associations

When dependent: :restrict is used on an association, a call to destroy on the model will raise Mongoid::Errors::DeleteRestriction when the dependency is violated. Just be aware that this gem will create a history track document before the destroy call and then remove if an error is raised. This applies to all persistence calls: create, update and destroy.

See spec/integration/validation_failure_spec.rb for examples.

Thread Safety

Mongoid::History stores the tracking enable/disable flag in Thread.current. If the RequestStore gem is installed, Mongoid::History will automatically store variables in the RequestStore.store instead. RequestStore is recommended for threaded web servers like Thin or Puma.

Contributing

You're encouraged to contribute to Mongoid History. See CONTRIBUTING.md for details.

Copyright

Copyright (c) 2011-2020 Aaron Qian and Contributors.

MIT License. See LICENSE.txt for further details.

mongoid-history's People

Contributors

acant avatar aq1018 avatar bensymonds avatar cedricfung avatar cgriego avatar dblock avatar dgknght avatar edejong avatar erlingwl avatar flash73 avatar gottfrois avatar hzmangel avatar jnfeinstein avatar joelnordell avatar johnnyshields avatar matekb avatar matsprea avatar mikwat avatar msaffitz avatar nofxx avatar ojbucao avatar sarcilav avatar sbrocher avatar sivagollapalli avatar stephenhmarsh avatar vasil-nesterov avatar yads avatar zambot avatar zeitnot avatar zlw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mongoid-history's Issues

NoMethodError: undefined method `nil=' when using custom field

I recently customised mongoid-history so that a 'commit-msg' could be added to each history_track (see #95). This all worked well, though I seem to have discovered a bug with this custom behaviour.

@book = Book.new(:name => 'My Story', :description => 'A story about me')
@book.update_attributes(:description => 'This is a story about me and my life') #=> true
@book.history_tracks.last.undo! User.first #=> true
@book.update_attributes(:description => 'A story about me and my dog') #=> true
@book.history_tracks.last.undo! User.first
=> NoMethodError: undefined method `nil=' for #<User:0x007fd76dcbb2a8>

The error occurs when I have already successfully called an undo! on the document. With the removal of attr_accessor :commit_msg from my model, everything worked fine. I know that this custom behaviour is outside of the context of the app, but can anyone identify what is going wrong here?

undefined method `create!' for Object:Class

I have a standard update action in my controller (copy-paste from the instructions), and it's throwing the following error:

Using mongoid (2.1.8)
Using mongoid-history (0.1.3)

# apartments_controller.rb
@apartment.update_attributes(params[:apartment])
# apartment.rb
include Mongoid::History::Trackable
track_history :on => :price
NoMethodError in ApartmentsController#update

undefined method `create!' for Object:Class

mongoid-history (0.1.3) lib/mongoid/history/trackable.rb:190:in `track_update'
activesupport (3.0.9) lib/active_support/callbacks.rb:415:in `_run_update_callbacks'
activesupport (3.0.9) lib/active_support/callbacks.rb:94:in `run_callbacks'
mongoid (2.1.8) lib/mongoid/persistence/modification.rb:24:in `block in prepare'
activesupport (3.0.9) lib/active_support/callbacks.rb:426:in `_run_save_callbacks'
activesupport (3.0.9) lib/active_support/callbacks.rb:94:in `run_callbacks'
mongoid (2.1.8) lib/mongoid/persistence/modification.rb:23:in `prepare'
mongoid (2.1.8) lib/mongoid/persistence/operations/update.rb:43:in `persist'
mongoid (2.1.8) lib/mongoid/persistence.rb:82:in `update'
mongoid (2.1.8) lib/mongoid/persistence.rb:144:in `upsert'
mongoid (2.1.8) lib/mongoid/persistence.rb:111:in `update_attributes'
app/controllers/apartments_controller.rb:103:in `block in update'

Move common mongoid-history code to lib

I am using mongoid-history to track several models, thus I have common code across multiple models. I am trying to move this common code into the lib directory:

# lib/history.rb
module History
  include Mongoid::History::Trackable
  track_history   :on => [:content]
end
# models/my_model.rb
Class MyModel
  include Mongoid::Document
  include History
end
undefined method `collection_name' for History:Module

Is there anyway to achieve this?

Why isn't mongoid-history good for use on a wiki

I note that the readme states

This is great for auditing and preventing vandalism, but is probably not suitable for use cases such as a wiki.

I would like to know why mongoid-history would not be suitable for use in a wiki?

Track some field only if condition

Hello,

is possible to track some field only if?

For example I want to track Order model, field state. I've got some states (cart, sent, in_process...) and I want to track it only if state is sent, in_process and not when it is cart.

Is possible something like this?

updates no longer versioned.

I just noticed today that for the last 7 days versioning has stopped. I started to investigate to find out what went wrong but couldn't find anything. It works 100% perfectly on my local dev machine, but on heroku it is no longer recording new versions.

I have it tracking 2 different models and both fail to record versions.

I'm not even sure how to debug this? It works fine on development and I downloaded the fresh copy of the remote database. The only thing I upgraded was a version bump to the latest mongoid 3.0.13 from 0.6 or 0.8... I didn't upgrade mongoid-history, on 0.3.0... tried 0.3.1 now, same results. ;-(

Any theories? Or things I should try?

class Review
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::History::Trackable
  include Mongoid::Alize
  include Mongoid::FullTextSearch

  track_history :on => [:content, :excerpt], :track_create => true, :track_destroy => true

history_tracker.rb

class HistoryTracker
  include Mongoid::History::Tracker
end

initializer

Mongoid::History.tracker_class_name = :history_tracker
Mongoid::History.current_user_method = :current_user

Get current_user in Sinatra?

Is it possibile to grab request details from a request in Sinatra. The sweeper observer doesn't seem to work for sinatra.

I have an api which receives an id of the user. I am adding an additional field to tracker, and I want a way to access the request to pull out that info.

Any help would be great!

Using STI to store versions instead of home made association

Hi!

I was wondering why you are using a single value array containing a Hash to store the historic able object (id and type) rather than using STI ?

We could then just have an 'id' and '_type' attribute referring to historic able object. It could be easy to add a dependent destroy on our models to automatically delete historic with the referenced object.

update_all support

Right now update_all seems to ignore all history tracking. Is support for tracking update_all planned? or is this a known limitation that could be documented?

class Post
  include Mongoid::Document
  include Mongoid::History::Trackable
  field           :title
  track_history
end

Post.create!(title: 'First Title')
Post.first.title #=> 'First Title'
Post.first.history_tracks.count #=> 0

Post.update_all(title: 'Second Title')
Post.first.title #=> 'Second Title'
Post.first.history_tracks.count #=> 0

Remove memoization

I think a lot of the memoization (at least in Trackable class) should be removed. Given that Mongoid already caches queries, I don't think it actually reduces db hits, and moreover it seems to be cleared frequently. I think it mainly adds complexity and potential for bugs. Was there a real use case for implementing it?

weird problem caused by save modifier_id

I had a problem that caused all my older records to get "modified" by the same user regardless of the current_user. After looking around at why only older records had this behavior and new records were recording the correct modifier I discovered that the old records all had a saved "modifier_id" on the root document itself (not the history tracks, those should have it) and the new ones had nil.

That seemed to fix it. Should there be a modifier_id? I'm assuming it's not suppose to have a permanent value on the root document itself (just the history tracks)?

I got to the ' raise "This should never happen. Please report bug." ' part.

So this is how our database is set up:

Batch -> embeds_many Analyte
Analyte -> embeds_one RegressionEquation

We have our tracker class set up, and our association chain has the following structure (created via fabricator):

Fabricate(:calibration_audit, :association_chain => [{"name" => "Batch", "id" => @batch._id},
                                                     {"name" => "analytes", "id" => analyte._id},
                                                     {"name" => "regression_equations", "id" => "id"}])

I think it hit that line because RegressionEquation class is not embedded in the Batch class. And the current setup and tracker.rb does not handle nested embeds.

This setup worked for us when we were on 0.1.7, and we are trying to upgrade from 0.1.7 to 0.2.3, and this error popped up.

Proposal for Better HistoryTracker storage schema

I have a use case to build a human-readable audit trail. I find the association chain syntax to be difficult to work with. I'd propose to combine :modified, :action, and :association_chain into one tree structure.

Assuming we have these models:

class Post
  ...
  field :p1
  field :p2
  embeds_many :comments
  embeds_one :section
end

class Comment
  ...
  field :c1
  field :c2
  embeds_many :links 
end

class Link
  ...
  field :l1
end

class Section
  ...
  field :s1
end

Doing an edit might generate:

modified: {
  _action: :update,
  _version: 2,
  _id: '#P1',
  p1: ___,
  p2: ___,
  comments: [{
    _id: '#C1',
    _action: :update,
    _version: 2,
    c1: ___,
    c2: ___
  },{
    _id: '#C2',
    _action: :create,
    _version: 1,
    c1: ___,
    links: [{
      _id: '#L1',
      _action: :create,
      _version: 1,
      l1: ___
    }]
  }],
  section: {
    _id: '#S2',
    _action: :delete,
    _version: 2,
    s1: ___
  }
}

If I modify only an embedded model, the audit should still save from the parent downward:

modified: {
  _action: :update,
  _id: '#P1',
  _version: 3,
  comment: [{
    _id: '#C2',
    _action: :update,
    _version: 2,
    links: [{
      _id: '#L1',
      _action: :update,
      _version: 2,
      l1: ___
    }]
  }]
}

A drawback is that this structure makes it slightly difficult to follow history on embedded models. We may want to support "breakout mode" to where you can force embedded updates to be stored on a separately from their parents (i.e. the current structure).

Mongoid-history compatibility issue with Rails 4.1.0.rc1

Hi, bundle install cannot solve the following dependency.

Bundler could not find compatible versions for gem "activemodel":
In Gemfile:

mongoid-history (>= 0) ruby depends on
  activemodel (~> 3.0) ruby

rails (~> 4.1.0.rc1) ruby depends on
  activemodel (4.1.0.rc1)

uninitialized Mongoid::Observer

Hi,

I'm getting the following error when I try to launch after implementing mongoid history:

/Users/asda/.rvm/gems/ruby-1.9.2-p290/gems/mongoid-history-0.2.1/lib/mongoid/history/sweeper.rb:2:in `module:History': uninitialized constant Mongoid::Observer (NameError)

My model:

class Item
  include Mongoid::Document
  include Mongoid::History::Trackable

  field :box_number, :type => Integer
  field :item_number, :type => Integer

  track_history :on => :all
...
end

###config/initializers/mongoid-history.rb
Mongoid::History.tracker_class_name = :history_tracker
Mongoid::History.current_user_method = :current_user

Any thoughts on what I may be missing?
Thanks!

Changes to Tracker / Polymorphism

Before all, pardon my naiveness but why #association_chain ? Looks like just rewritting mongoid. If we just belongs_to polymorphic in the tracker, trackable just need a simple has_many, and we could also use the tracker class to implement scopes and whatnot ;)

Also, I changed modifier to be polymorphic too, we have distinct User and Client in various apps. And this way we don't even need to worry about getting the modifier_class! It'll just polymorphic work.

has_many :tracks, as: :modifier

README correct?

According to README.rdoc:

track = comment.history_tracks.last

track.undo! # comment title should be "Test"

track.redo! # comment title should be "Test 2"

But when I try it I get:

ruby-1.9.2-p180 :004 > track.undo!
ArgumentError: wrong number of arguments (0 for 1)
from /home/reed/.rvm/gems/ruby-1.9.2-p180@clinical-trials/gems/mongoid-history-0.1.4/lib/mongoid/history/tracker.rb:20:in `undo!'

Feature Request: Make the modifier link to ActiveRecord Users

Hi,

We have a hybrid Rails app where are users are stored in ActiveRecord in Mysql. This caused some issues because when it was trying to load the user it was using _id instead of id. Which is normal if it were stored in Mongo DB

So what i have done (in my fork) is convert the modifier in tracker to a Integer field, and then the seeker use the current_user.id to store the value.

It might break in a few other places but at the moment thats all I needed to store who made the change.

I am not sure how many people are in the same position as me, but here is my fork if anyone is interested https://github.com/mark-ellul/mongoid-history.git I have added the changes to both master and 2.4 stable branches.

This ticket is just an idea, so you can close when you have read.

Default version field name should be "_version".

Hi !

As version field is a meta-field, I think this field's name should defaults to _version, the same way Mongoid adds its own meta-fields (_id, _type).

I know this field name can be overridden, but, I think, default values should respect best conventions.

Regards,
Gauthier.

Embed all versions inside the object and Preview

Hello

Thanks for this great gem.

To make this gem more attractable is by using the same way that mongoid versioning system works which is embedding all versions inside the object.

This will increase the performance of retrieving all objects with its' versions currently what I did is duplicating the versions and embedding them inside the object but I faced a problem which I lost the ability to not use :on => :all

So, my I think you can add some features to the gem which can benefits the project is adding :excepts=>[all unversioned fields] and storing the versions inside the object itself

Also, Adding a preview option before undo or redo will be great

thanks

Global disabled history

Hi ,

i want to clone a model and i like to disable history in this case.
I try to use "disable_tracker" but this method is just for a Model . I want disable for global (model parent and embeds model).

How can i do this ?

Thx ,
Michael

Cannot undo creating attributes for the first time

It is not possible to undo setting a property of a document for the first time.

Document before
{
id:1
}

Document after
{
id:1
name:"foo"
}

If undoing the last change the name attribute remains in the document when it should be destroyed.

Setting :track_create to true fails if tracked class is a subclass of class including Trackable

I want to use STI to have several types of Elements. I want all types of elements to be tracked. Thus:

class Element
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::History::Trackable

  field :body

  track_history :on => [:body], :track_create => true, :track_destroy => true
end

class Prompt < Element
end

This fails because the track_history only includes the current class and does not search and add any subclasses. I can fix this in my app by adding this to my Element class:

def self.history_trackable_options
  super || self.superclass.send(:history_trackable_options)
end

Is this expected behavior? If not, I'll gladly work on a patch with specs.

Mongoid Version 3.1 compatibility issue

I have tried mongoid-history with mongoid 3.1.2 but it complain about its dependency on mongoid 3.0 !

Bundler could not find compatible versions for gem "mongoid":
In Gemfile:
mongoid-history (>= 0) java depends on
mongoid (~> 3.0.4) java

mongoid (3.1.2)

Readme mistake

Mongoid::History.tracker_class.all.each do |tracker|
  tracker.association_chain[1,-1].each do |node|
    node['name'] = node['name'].tableize
  end
  tracker.save!
end

A lil mistake here: tracker.association_chain[1,-1]. It should be tracker.association_chain[start, length] or tracker.association_chain[start..end]. In both cases i do not understand, what should happens. All the names or all names except first should be tableized? )

Support Mongoid 3.0

Hi
I would love to use it, but I followed the instructions and I can't get it to work, I added the class HistoryTracker, and I added the includes, basically I copied an pasted what the instructions show.
When i try i've : "undefined method `referenced_in' for HistoryTracker:Class"

Could you help please?

Does this works with Mongoid 2.1.7?

I would love to use it, but I followed the instructions and I can't get it to work, I added the class HistoryTracker, and I added the includes, basically I copied an pasted what the instructions show, when I call "source.history_tracks" in console, I get

NoMethodError: undefined method `where' for Object:Class

Could you help please?

My updates are not versioned (creates is working fine)

I have configured mongoid_history to track creates, updates, and deletions.

Creations are being tracked but updates are not.

I have determined that modified_attributes_for_update is always returning false.

I am tracking everything (:all) with nothing being excluded.

Doesn't work with CarrierWave fields

It would be awesome to be able to keep a history of files that has been uploaded to S3 with carrierwave. At the moment this doens't work out of the box, but I might have a go at solving it. It would mean duplicating/copying files on S3 etc. Here's a sample stacktrace if you try to include a carrierwave field in the track_history :on => [:some_carrierwave_file_field]

BSON::InvalidDocument - Cannot serialize an object of class SomeUploader into BSON.:
/Users/erlingwl/.rvm/gems/ruby-1.9.2-p290/gems/bson-1.3.1/lib/bson/bson_c.rb:24:in serialize' /Users/erlingwl/.rvm/gems/ruby-1.9.2-p290/gems/bson-1.3.1/lib/bson/bson_c.rb:24:inserialize'
......
/Users/erlingwl/.rvm/gems/ruby-1.9.2-p290/bundler/gems/mongoid-history-71394cfa4ff6/lib/mongoid/history/trackable.rb:194:in `track_update'

"Undo" and "redo" changes do not persist

I'm not sure if this is a bug, or if I'm not using this correctly. Basically, when I call @object.undo!, the appropriate fields get reverted, but the changes do not persist on @object. That is, the effects of the undo! command don't actually last.

I tried calling @object.save! after @object.undo!, but that doesn't seem to help either.

useful new feature

I miss a very useful feature to get all versions and iterate through them.

 attributes = @doc.attributes 
 arr = @doc.history_tracks.desc(:version).drop(1) 
 arr.each_with_index do |h,index| 
         arr[index + 1].nil? ? modified_keys = [] : modified_keys = arr[index + 1].modified.keys 
         attributes.update(h.original)
     print (attributes["a1"] << modified_keys.include?("a1") # show which attribute was modified.   
 end

Also I would like to filter versions of embedded documents for every parent version. It would be a good if I could define track_history with custom additional parameters and a setter.

Retrieve field that has not been modified

I am trying to show pages for different versions of a document. For example I have a page that has been changed 5 times. It has a title and a content field. If I want to see what the document was like at version 2 I go to the version 2 page. However, the version 2 update only changed the content, not the title, thus the history tracker for version 2 does not have a field for title. How can I show the title of the document when it was at version 2?

Is there a way to save every attribute to the history tracker when just one of then changes?

Modifier only changes when defined explicitly

Hi,

Let's imagine we're using mogoid-history for History Tracking on Articles and also using devise which implements current_user for the controllers.
Every time we create a new article, we need to define the modifier explicitly, for example:

def create
  @article = Article.create(params[:article].merge(modifier: current_user))
end

Now, every time we update an article, we need something similar to this:

def update
  @article = Article.find(params[:id])
  @article.update_attributes(params[:article]) # without merging current_user explicitly
end

The documentation says we don't need to define explicitly the modifier when updating attributes if we have the current_user available.
But looking at mongoid-history code itself, this is the before_create callback:

def before_create(track)
  modifier_field = track.trackable.history_trackable_options[:modifier_field]
  modifier = track.trackable.send modifier_field
  track.modifier = current_user unless modifier # this condition will always prevent a different current_user if there's a modifier already defined from the model creation
end

Is this a bug, or am I missing something?

Expected ... to define error (in Mongoid 3.x support)

Hi everybody

i always get the following error...

Expected xxx/app/models/core/models/base.rb to define Core::Models::Base

The file exists and looks like

module Core
  module Models
    class Base
      include Mongoid::Document

      # @!attribute created_at
      # @!attribute updated_at
      include Mongoid::Timestamps

      after_initialize :defaults

      private
      def defaults

      end
    end
  end
end

Any ideas?

"should undo last version when no parameter is specified" sample is not passing

I downloaded the head version, run rake and I'm getting the following failure

  1. Mongoid::History track trackables undo should undo last version when no parameter is specified
    Failure/Error: @comment.title.should == "Test3"
    expected: "Test3",
    got: "Test4" (using ==)

    ./spec/integration/integration_spec.rb:352:in `block (5 levels) in <top (required)>'

I'm using ruby 1.9.2p180 and the Gemfile.lock is https://gist.github.com/1054535

Localized fields

When I destroy and restore an object with localized fields, I get something like this in them:

{"en"=>"{\"en\"=>\"SNCB RailwayNetwork\"}"}

history_tracks doesn't allow method chaining

@model.history_tracks doesn't allow method chaining because it's an array. Thus I can't do @model.history_tracks.find_by_version(params[:id]). A simple workaround is by including a custom method in my trackable model:

def versions
HistoryTracker.where(:scope => history_trackable_options[:scope], :association_chain => triverse_association_chain)
end

Looking at the source files I'm not sure why it's returning an array instead of a model to allow method chaining.

undo / redo broken on embeds_one relationships (+ bug fix)

I forked the project:

https://github.com/sbrocher/mongoid-history

Added failing spec to show the bug:

sbrocher@b8faca9

And did a quick bug fix:

sbrocher@0d8a0cd

Everything is under the embeds_one branch.

My fix feels a little hackish, there may be a better way to fix this. Also, I may not have added all of the necessary tests. At any rate, I hope this helps and that the bug wasn't triggered by something else I missed.

Thanks for such an amazing gem. Please let me know if you have any questions!

Trackable #original and #modified are flip-flopped for :destroy action

In trackable.rb"

      def modified_attributes_for_create
        @modified_attributes_for_create ||= attributes.inject({}) do |h,(k,v)|
          h[k] = [nil, v]
          ...
      end

      def modified_attributes_for_destroy
        @modified_attributes_for_destroy ||= attributes.inject({}) do |h,(k,v)|
          h[k] = [nil, v]
          ...
       end

modified_attributes_for_create correctly sets original: nil, modified: some_value, however, modified_attributes_for_destroy incorrectly does the same thing; I would expect it to be original: some_value, modified: nil

Bug in modifier and trackable relation

When between the modifier model and trackable model you have more than one relation, when you try to write in the 'modifier' mongoid is returning the wrong relation.
I found that in mongoid/relations/bindings/referenced/in.rb the bind method is making something unexpected, it is returning for the modifier the second relation defined between the models, no the one that is created by mongoid-history in https://github.com/sarcilav/mongoid-history/blob/fixing-bug-when-trackable-modifier-has-more-than-one-relation/lib/mongoid/history/trackable.rb#L36 , I think that the problem is in the modifier you don't have to specify the relation, and it creates a weak relation.
I re-wrote two models in the integration test of mongoid-history to double check that the problem is on mongoid-history https://github.com/sarcilav/mongoid-history/blob/fixing-bug-when-trackable-modifier-has-more-than-one-relation/spec/integration/integration_spec.rb

Support an :as option on track_history

track_history should support an :as option which will control the alias of the trackable field. This is useful if you move/rename classes, you don't have to migrate your database data.

For example:

module MyModule
  class MyClass
     include Mongoid::History::Trackable

     track_history as: 'my_class'
  end
end

I will implement this in the future (unless someone else wants to)

modifier doesn't get set when tracking creates

Using: mongoid-history-0.1.7 an mongoid 2.4.0

When creating a document the track modifier doesn't get set as the controller hasn't been setup in the sweeper (observer).

In sweeper the before sweeper method sets the controller(in turn used to set cal the current_user) however the before_create(track) gets called before the before callback.

Any thoughts on how to track the modifier when creating a document?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.