wikify 0.0.1.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bfb7da00d14ef7a813e3fbc64869133635a7958c
4
+ data.tar.gz: f34e12fbf529cf68005815784fb03299fd6b7aad
5
+ SHA512:
6
+ metadata.gz: 1f3096e30c0f7f77e27b2217b59dad0d96942663967250e8d62a598435de1e9c6c51a478c997c093af8b43a63282b4f61c29c7409c6a4253ba1da6dc779becae
7
+ data.tar.gz: 0462d4122d9179c6648c161211a75109082dc8f86ca1f142ee3fa7f1cc82e99f3770e16ab73d7942139b2d6aaa99c95a4a9424aac98e083f9b6e4e9a348bf50d
data/.gitignore ADDED
@@ -0,0 +1,19 @@
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
18
+ .DS_STORE
19
+ *~
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - ruby-head
6
+ env:
7
+ - "rake=0.9"
8
+ script: "bundle exec rspec -c"
9
+ notifications:
10
+ email: false
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: ruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in wikify.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Adam Laycock
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,91 @@
1
+ # Wikify
2
+
3
+ Active Record model versioning.
4
+
5
+ [![Build Status](https://travis-ci.org/Arcath/Wikify.png?branch=master)](https://travis-ci.org/Arcath/Wikify) | [![Code Climate](https://codeclimate.com/github/Arcath/Wikify.png)](https://codeclimate.com/github/Arcath/Wikify) | [![Coverage Status](https://coveralls.io/repos/Arcath/Wikify/badge.png)](https://coveralls.io/r/Arcath/Wikify) | [RDoc](http://rubydoc.info/github/Arcath/Wikify/master/frames)
6
+
7
+ ## Installation
8
+
9
+ ### Adding the Gem
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'wikify'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install wikify
22
+
23
+ ### Migration
24
+
25
+ Wikify has a built in generator for creating the migration which can be run using:
26
+
27
+ $ rails g wikify:migration
28
+ $ rake db:migrate
29
+
30
+ You will now have a table for your versions, wikify contains a model for the versions which is used by default.
31
+
32
+ ## Usage
33
+
34
+ To enable wikify on your models add `wikify` to your model class:
35
+
36
+ ``` ruby
37
+ class Article < ActiveRecord::Base
38
+ wikify
39
+ end
40
+ ```
41
+
42
+ Your model now has version tracking!
43
+
44
+ For an example of how Wikify works see the [Logical Function Spec](https://github.com/Arcath/Wikify/blob/master/spec/logical_function_spec.rb) which is a commented example of all the functionality (that gets tested with every push).
45
+
46
+ ## Restoring a Version
47
+
48
+ Versions have a handy `restore` method that lets you very quickly and easily restore a version over the current/target model. A version stores the model & id when it gets created when you call `restore` Wikify finds the model with the stored id and then overwrites all its attributes with the ones in the versions cache.
49
+
50
+ ### Restoring a deleted version
51
+
52
+ You can restore a deleted versions by using the `Wikify.destroyed_versions` method. You must supply a model and optionally an id e.g.
53
+
54
+ ``` ruby
55
+ Wikify.destroyed_versions(Article)
56
+ #> An list of versions where event is "destroy" and model is "Article"
57
+ Wikify.destroyed_versions(Article, 2)
58
+ #> The destory events for the given article only
59
+ ```
60
+
61
+ ### Associations
62
+
63
+ #### Has-Many
64
+
65
+ Wikify can track the versions of child objects, for example a comments model attached to that articles model:
66
+
67
+ ``` ruby
68
+ class Article < ActiveRecord::Base
69
+ has_many :comments
70
+
71
+ wikify
72
+ end
73
+
74
+ class Comment < ActiveRecord::Base
75
+ belongs_to :article
76
+
77
+ wikify_on_parent(:article)
78
+ end
79
+ ```
80
+
81
+ This then stores versions on the parent model so that they can be tracked with it. The child model also has a `versions` method that works the same but it looks for its versions through the parent.
82
+
83
+ Again see the [Logical Function Spec](https://github.com/Arcath/Wikify/blob/master/spec/logical_function_spec.rb) for examples of this code in action.
84
+
85
+ ## Contributing
86
+
87
+ 1. Fork it
88
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
89
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
90
+ 4. Push to the branch (`git push origin my-new-feature`)
91
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ Description
2
+ Generates the database table required for wikify
3
+
4
+ Usage
5
+ rails g wikify:migration
@@ -0,0 +1,11 @@
1
+ module Wikify
2
+ module Generators
3
+ class MigrationGenerator < Rails::Generators::Base
4
+ argument :model_name, :type => :string, :default => "versions"
5
+
6
+ def generate_migration
7
+ generate "migration", "create_wikify_#{model_name}", "resource_id:integer", "resource_type", "data:text", "event", "linked_child_resource_type", "linked_child_resource_id:integer", "linked_child:boolean", "comitter_id:integer", "created_at:datetime", "updated_at:datetime"
8
+ end
9
+ end
10
+ end
11
+ end
data/lib/wikify.rb ADDED
@@ -0,0 +1,58 @@
1
+ # Version Number
2
+ require "wikify/version_number"
3
+
4
+ # System
5
+ require 'ostruct' #Required for ruby 1.9.3
6
+
7
+ # Gems
8
+ require 'active_record'
9
+ require 'active_support'
10
+
11
+ # Internal Requires
12
+ require "wikify/child_methods"
13
+ require "wikify/controller"
14
+ require "wikify/methods"
15
+ require "wikify/model"
16
+ require "wikify/version_concern"
17
+ require "wikify/version"
18
+
19
+ # Wikify
20
+ #
21
+ # Model Versioning for ActiveRecord
22
+ module Wikify
23
+ # Searches the version table for destroy events, takes an optional id when you know exactly what you are looking for.
24
+ #
25
+ # Wikify.destroyed_versions(Model, 10)
26
+ def self.destroyed_versions(model, id = nil)
27
+ hash = {"#{model.wikify_options[:assoc_as]}_type".to_sym => model.to_s, event: "destroy"}
28
+ hash["#{model.wikify_options[:assoc_as]}_id".to_sym] = id if id
29
+ model.wikify_options[:assoc_model].constantize.where(hash)
30
+ end
31
+
32
+ # Stores the current users id within the current thread (lets us get it from the controller)
33
+ def self.set_user_id(id)
34
+ thread_store.user_id = id
35
+ end
36
+
37
+ # Returns the stored id
38
+ def self.user_id
39
+ thread_store.user_id
40
+ end
41
+
42
+ private
43
+
44
+ def self.thread_store
45
+ Thread.current[:wikify_store] ||= OpenStruct.new({user_id: nil})
46
+ end
47
+ end
48
+
49
+
50
+ ActiveSupport.on_load(:active_record) do
51
+ include Wikify::Model
52
+ end
53
+
54
+ if defined?(ActionController)
55
+ ActiveSupport.on_load(:action_controller) do
56
+ include Wikify::Controller
57
+ end
58
+ end
@@ -0,0 +1,38 @@
1
+ module Wikify
2
+ # Methods included by child models
3
+ module ChildMethods
4
+ # Records an update operation against the parent
5
+ def wikify_update
6
+ new_version = new_version_instance
7
+ new_version.data = @wikify_cached_object
8
+ new_version.event = "child" + (@wikify_override[:event] || "update")
9
+ new_version.save
10
+ end
11
+
12
+ # Records a create operation against the parent
13
+ def wikify_create
14
+ new_version = new_version_instance
15
+ new_version.event = "child create"
16
+ new_version.save
17
+ @wikify_cached_object = self.attributes
18
+ end
19
+
20
+ # Records a destroy operation against the parent
21
+ def wikify_destroy
22
+ new_version = new_version_instance
23
+ new_version.data = @wikify_cached_object
24
+ new_version.event = "child destroy"
25
+ new_version.save
26
+ end
27
+
28
+ private
29
+
30
+ def new_version_instance
31
+ self.send(wikify_parent).send(wikify_options[:assoc_name]).new({linked_child: true, linked_child_resource_type: self.class.to_s, linked_child_resource_id: self.id})
32
+ end
33
+
34
+ def wikify_search_hash
35
+ {"linked_child_#{wikify_options[:assoc_as]}_id".to_sym => self.id, "linked_child_#{wikify_options[:assoc_as]}_type".to_sym => self.class.to_s, "linked_child" => true}
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ module Wikify
2
+
3
+ # Used to extend ActionController with the required methods
4
+ module Controller
5
+
6
+ # Adds the before filter trigger to ActionController (aka your ApplicationController)
7
+ def self.included(base)
8
+ if defined?(ActionController) && (base == ActionController::Base || base == ActionController::API)
9
+ base.before_filter :set_wikify_user
10
+ end
11
+ end
12
+
13
+ # Default method for finding the current user
14
+ def current_user_wikify
15
+ current_user if defined?(current_user)
16
+ end
17
+
18
+ # The before filter method that stores the user id in Wikify
19
+ def set_wikify_user
20
+ Wikify.set_user_id(current_user_wikify.id) if current_user_wikify != nil
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ module Wikify
2
+ # Methods that get included on models using wikify
3
+ module Methods
4
+ # The Wikify update operation
5
+ #
6
+ # Creates a new version, records the cached object and saves it as an update
7
+ def wikify_update
8
+ new_version = self.send(wikify_options[:assoc_name]).new
9
+ new_version.data = @wikify_cached_object
10
+ new_version.event = (@wikify_override[:event] || "update")
11
+ new_version.save
12
+ end
13
+
14
+ # The Wikify create operation
15
+ #
16
+ # Creates a new version with no data and sets the event to create
17
+ def wikify_create
18
+ new_version = self.send(wikify_options[:assoc_name]).new
19
+ new_version.event = "create"
20
+ new_version.save
21
+ @wikify_cached_object = self.attributes
22
+ end
23
+
24
+ # The Wikify destroy operation
25
+ #
26
+ # Creates a new version recods the cached object and saves it as a destroy
27
+ def wikify_destroy
28
+ new_version = self.send(wikify_options[:assoc_name]).new
29
+ new_version.data = @wikify_cached_object
30
+ new_version.event = "destroy"
31
+ new_version.save
32
+ end
33
+
34
+ # The hash used to find versions
35
+ def wikify_search_hash
36
+ {"#{wikify_options[:assoc_as]}_id".to_sym => self.id, "#{wikify_options[:assoc_as]}_type".to_sym => self.class.to_s}
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,74 @@
1
+ module Wikify
2
+ module Model
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ # Caches the model
8
+ #
9
+ # Called using the `after_find` callback in ActiveRecord
10
+ def wikify_cache
11
+ @wikify_override = {}
12
+ @wikify_cached_object = self.attributes
13
+ end
14
+
15
+ # Allows you override the wikify options for this event
16
+ def wikify_override(options)
17
+ @wikify_override = options
18
+ end
19
+
20
+ def version_at(time)
21
+ self.versions.where("created_at < ?", time).limit(1).first.next
22
+ end
23
+
24
+ module ClassMethods
25
+ # Enables the wikify functionality on the model
26
+ #
27
+ # Takes a hash of options:
28
+ #
29
+ # :assoc_name => :versions
30
+ # :assoc_as => :resource
31
+ # :assoc_model => "Wikify::Version"
32
+ #
33
+ def wikify(options = {})
34
+ # Create a class_attribute to store the wikify options hash in
35
+ class_attribute :wikify_options
36
+
37
+ # Set some Defaults
38
+ options[:assoc_name] ||= :versions
39
+ options[:assoc_as] ||= :resource
40
+ options[:assoc_model] ||= "Wikify::Version"
41
+ options[:no_include] ||= false
42
+
43
+ # Store the Options Hash
44
+ self.wikify_options = options.dup
45
+
46
+ # Register the Callbacks
47
+ after_find :wikify_cache
48
+ after_create :wikify_create
49
+ after_update :wikify_update
50
+ after_destroy :wikify_destroy
51
+
52
+ # Allow reading of the cached object
53
+ attr_reader :wikify_cached_object
54
+
55
+ # Define a method to access the versions
56
+ define_method(wikify_options[:assoc_name]){
57
+ wikify_options[:assoc_model].constantize.where(wikify_search_hash)
58
+ }
59
+
60
+ include Wikify::Methods unless options[:no_include]
61
+ end
62
+
63
+ # Takes the same options as wikify
64
+ def wikify_on_parent(parent, options = {})
65
+ wikify(options.merge({no_include: true}))
66
+
67
+ class_attribute :wikify_parent
68
+ self.wikify_parent = parent
69
+
70
+ include Wikify::ChildMethods
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,6 @@
1
+ module Wikify
2
+ class Version < ActiveRecord::Base
3
+ self.table_name_prefix = 'wikify_'
4
+ include Wikify::VersionConcern
5
+ end
6
+ end
@@ -0,0 +1,62 @@
1
+ module Wikify
2
+ module VersionConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ serialize :data, Hash
7
+
8
+ before_save :record_comitter
9
+ end
10
+
11
+ def object
12
+ OpenStruct.new(data)
13
+ end
14
+
15
+ def previous
16
+ self.class.where("created_at < ?", self.created_at).limit(1).first
17
+ end
18
+
19
+ def next
20
+ results = self.class.where("created_at > ?", self.created_at).limit(1)
21
+ return results.first if results != []
22
+ return self.restore_to
23
+ end
24
+
25
+ # Finds the model/instance to restore to and then over writes all its data.
26
+ #
27
+ # Also over rides the version event to restore so you can tell the difference between a restore and an update in your history.
28
+ def restore
29
+ unless event == "destroy"
30
+ target = restore_to
31
+ target.attributes = self.data
32
+ target.wikify_override({event: "restore"})
33
+ target.save
34
+ else
35
+ target = resource_type.constantize.new
36
+ target.attributes = self.data
37
+ target.wikify_override({event: "restore"})
38
+ target.save
39
+ end
40
+ end
41
+
42
+ # Finds the model to restore type
43
+ def restore_to
44
+ restore_type.constantize.find(restore_id)
45
+ end
46
+
47
+ def restore_type
48
+ return self.send(self.attributes.keys.select {|i| i =~ /_type/ }.first.to_sym) unless self.linked_child == true
49
+ return self.send(self.attributes.keys.select {|i| i =~ /linked_child_.*?_type/ }.first.to_sym)
50
+ end
51
+
52
+ def restore_id
53
+ return self.send(self.attributes.keys.select {|i| i =~ /_id/ }.first.to_sym) unless self.linked_child == true
54
+ return self.send(self.attributes.keys.select {|i| i =~ /linked_child_.*?_id/ }.first.to_sym)
55
+ end
56
+
57
+ # Gets the comitter from the Wikify thread store
58
+ def record_comitter
59
+ self.comitter_id = Wikify.user_id if Wikify.user_id != nil
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,4 @@
1
+ module Wikify
2
+ # Wikify's version number
3
+ VersionNumber = "0.0.1.pre"
4
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Child do
4
+ it "should have the wikify methods" do
5
+ Child._create_callbacks.length.should eq 1
6
+ Child._update_callbacks.length.should eq 1
7
+ Child._destroy_callbacks.length.should eq 1
8
+ end
9
+
10
+ it "should have the callback methods" do
11
+ child = Parent.last.children.new
12
+ child.methods.include?(:wikify_cache).should be_true
13
+ child.methods.include?(:wikify_update).should be_true
14
+ child.methods.include?(:wikify_create).should be_true
15
+ child.methods.include?(:wikify_destroy).should be_true
16
+ end
17
+
18
+ it "should write its updates to the parent" do
19
+ parent = Parent.new({name: "Parent 100"})
20
+ parent.save
21
+
22
+ parent.versions.count.should eq 1
23
+
24
+ child = parent.children.new({name: "Child 1"})
25
+ child.save
26
+
27
+ parent.versions.count.should eq 2
28
+ parent.versions.last.event.should eq "child create"
29
+ end
30
+
31
+ it "should record updates to the parent" do
32
+ child = Child.find_by_name("Child 1")
33
+
34
+ child.parent.versions.count.should eq 2
35
+
36
+ child.name = "Child 1 Updated"
37
+ child.save
38
+
39
+ child.parent.versions.count.should eq 3
40
+ end
41
+
42
+ it "Should set the restore to values" do
43
+ child = Child.find_by_name("Child 1 Updated")
44
+
45
+ child.versions.last.restore_type.should eq "Child"
46
+ child.versions.last.restore_id.should eq child.id
47
+ end
48
+
49
+ it "should restore a version do" do
50
+ child = Child.find_by_name("Child 1 Updated")
51
+ child.versions.last.restore
52
+
53
+ child = Child.find(child.id)
54
+
55
+ child.name.should eq "Child 1"
56
+ end
57
+
58
+ it "should create an event on destroy" do
59
+ child = Child.find_by_name("Child 1")
60
+ parent = child.parent
61
+
62
+ c = parent.versions.count
63
+
64
+ child.destroy
65
+
66
+ parent.versions.count.should eq c + 1
67
+ parent.versions.last.event.should eq "child destroy"
68
+ end
69
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe "User id storing" do
4
+ it "should only be for that thread" do
5
+ Wikify.user_id.should be_nil
6
+ Thread.new do
7
+ Wikify.set_user_id = 1
8
+ Wikify.user_id.should eq 1
9
+ end
10
+ Wikify.user_id.should be_nil
11
+ end
12
+
13
+ it "should get set by the controller" do
14
+ parent = Parent.new({name: "Committer 1"})
15
+ parent.save
16
+ parent
17
+ controller = Controller.new
18
+ controller.request # See this method for what it does
19
+ parent = Parent.find_by_name("Committer 1 Updated")
20
+ parent.versions.count.should eq 2
21
+ parent.versions.last.comitter_id.should eq 1
22
+ end
23
+
24
+ it "should handle the current_user return being nil" do
25
+ lambda { controller = NilController.new }.should_not raise_exception
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe "With custom config" do
4
+ it "should set the assosiation name" do
5
+ doc = Document.new
6
+ doc.revisions.count.should eq 0
7
+ end
8
+
9
+ it "should record a version for create/update" do
10
+ Document.create!({title: "Foo"})
11
+ doc = Document.find_by_title("Foo")
12
+ doc.title = "Bar"
13
+ doc.save
14
+
15
+ doc.revisions.count.should eq 2
16
+ end
17
+
18
+ it "should let you restore a version" do
19
+ doc = Document.find_by_title("Bar")
20
+ doc.revisions.last.restore
21
+
22
+ doc = Document.find_by_title("Foo")
23
+ doc.title.should eq "Foo"
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Model create" do
4
+ it "should record a create event" do
5
+ parent = Parent.new({name: "First Create Test"})
6
+ parent.save
7
+
8
+ parent.versions.count.should eq 1
9
+ parent.versions.last.event.should eq "create"
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Model Destroy" do
4
+ it "should record a destroy" do
5
+ Wikify.destroyed_versions(Parent).count.should eq 0
6
+
7
+ parent = Parent.new({name: "First Destory Test"})
8
+ parent.save
9
+
10
+ parent.destroy
11
+
12
+ Wikify.destroyed_versions(Parent).count.should eq 1
13
+
14
+ old_version = Wikify.destroyed_versions(Parent).last
15
+ old_version.data.should eq parent.attributes
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parent do
4
+ it "Should have the wikify method" do
5
+ Parent._create_callbacks.length.should eq 2
6
+ Parent._update_callbacks.length.should eq 2
7
+ Parent._destroy_callbacks.length.should eq 1
8
+ end
9
+
10
+ it "should have the callback methods" do
11
+ parent = Parent.new
12
+ parent.methods.include?(:wikify_cache).should be_true
13
+ parent.methods.include?(:wikify_update).should be_true
14
+ parent.methods.include?(:wikify_create).should be_true
15
+ parent.methods.include?(:wikify_destroy).should be_true
16
+ end
17
+
18
+ it "should have the relationship with versions" do
19
+ parent = Parent.new
20
+ parent.versions.count.should eq 0
21
+ end
22
+ end
@@ -0,0 +1,77 @@
1
+ # This is a worked example that appears in the readme except with shoulds to make sure that it works as planned
2
+ require 'spec_helper'
3
+
4
+ describe "Working logically" do
5
+ it "should behave like we expect" do
6
+ # First lets create a new object
7
+ parent = Parent.new
8
+ parent.name = "david" # Lets set the name
9
+ parent.save # And save that record
10
+
11
+ parent_id = parent.id # We will need this later
12
+
13
+ parent.versions.count.should eq 1 # We should now have 1 version
14
+ parent.versions.first.event.should eq "create" # that was created when we created the record
15
+ parent.versions.first.data.should eq Hash.new # and has an empty hash for its data
16
+
17
+ # Its a new day and we want to change "david" to "David"
18
+ david = Parent.find(parent_id)
19
+ david.name = "David"
20
+ david.save
21
+
22
+ david.versions.count.should eq 2 # We should now have a new version
23
+ david.versions.last.event.should eq "update" # that was an update
24
+ david.versions.last.data["name"].should eq "david" # and has the old name in the data hash
25
+
26
+ # Its another day and we make another change
27
+ david = Parent.find(parent_id)
28
+ david.name = "Dav1d"
29
+ david.save
30
+
31
+ david.versions.count.should eq 3 # We should now have a new version
32
+ david.versions.last.event.should eq "update" # that was an update
33
+ david.versions.last.data["name"].should eq "David" # and has the old name in the data hash
34
+
35
+ # Its another day and we want to restore the version with David
36
+ david = Parent.find(parent_id)
37
+ david.versions.last.restore # Restore the last version
38
+
39
+ david = Parent.find(parent_id)
40
+ david.versions.count.should eq 4 # We should now have a new version
41
+ david.versions.last.event.should eq "restore" # that was an update
42
+ david.versions.last.data["name"].should eq "Dav1d" # and has the old name in the data hash
43
+ david.name.should eq "David" # The name should have been restored
44
+
45
+ # David has died so its time to delete his record
46
+ david = Parent.find(parent_id)
47
+ david.destroy
48
+
49
+ # Whoops he wasn't dead just resting his heart time to restore his record
50
+ david_version = Wikify.destroyed_versions(Parent, parent_id).last
51
+ david_version.event.should eq "destroy" # The last thing to happen to david should be destroy
52
+ david_version.restore
53
+
54
+ david = Parent.find(parent_id)
55
+ david.name.should eq "David" # And now hes back! and at the same ID as before!
56
+
57
+ # David has now had a child (Like Arnie did in Junior)
58
+ david = Parent.find(parent_id)
59
+
60
+ david.versions.count.should eq 6 # David should have 6 versions at this point
61
+
62
+ jeff = david.children.new({name: "Jeff"})
63
+ jeff.save
64
+
65
+ david.versions.count.should eq 7 # Now that Jeff has been saved David should have 7 versions
66
+
67
+ # Turns out Jeff is a girl, name change needed
68
+ jeff = Child.find_by_name("Jeff")
69
+ jeff.name = "Jasmine"
70
+ jeff.save
71
+
72
+ jeff.parent.versions.count.should eq 8 # That change should be recorded in the parent
73
+
74
+ jeff.versions.count.should eq 2 # Children's versions search the parent for versions related to them.
75
+
76
+ end
77
+ end
@@ -0,0 +1,5 @@
1
+ class Child < ActiveRecord::Base
2
+ belongs_to :parent
3
+
4
+ wikify_on_parent :parent
5
+ end
@@ -0,0 +1,5 @@
1
+ class Document < ActiveRecord::Base
2
+ wikify({:assoc_name => :revisions,
3
+ :assoc_as => :thing,
4
+ assoc_model: "Revision"})
5
+ end
@@ -0,0 +1,5 @@
1
+ class Parent < ActiveRecord::Base
2
+ wikify
3
+
4
+ has_many :children
5
+ end
@@ -0,0 +1,3 @@
1
+ class Revision < ActiveRecord::Base
2
+ include Wikify::VersionConcern
3
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Restoring a Model" do
4
+ it "should have a model to restore to" do
5
+ parent = Parent.new({name: "First Restore Test"})
6
+ parent.save
7
+
8
+ parent.versions.last.restore_to.should eq parent
9
+ end
10
+
11
+ it "should restore a model" do
12
+ parent = Parent.find_by_name("First Restore Test")
13
+ parent.name = "Updated Restore Test"
14
+
15
+ parent.versions.last.restore
16
+ Parent.find(parent.id).name.should eq "First Restore Test"
17
+ end
18
+
19
+ it "after a restore the last versions action should be restored" do
20
+ parent = Parent.find_by_name("First Restore Test")
21
+ parent.name = "Updated Restore Test"
22
+
23
+ parent.versions.last.restore
24
+ Parent.find(parent.id).versions.last.event.should eq "restore"
25
+ end
26
+
27
+ it "should restore a deleted version" do
28
+ parent = Parent.find_by_name("First Restore Test")
29
+ parent.destroy
30
+ parent_id = parent.id
31
+
32
+ version = Wikify.destroyed_versions(Parent).last
33
+ version.restore
34
+
35
+ parent = Parent.find_by_name("First Restore Test")
36
+ parent.name.should eq "First Restore Test"
37
+ parent.id.should eq parent_id
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
4
+ SimpleCov::Formatter::HTMLFormatter,
5
+ Coveralls::SimpleCov::Formatter
6
+ ]
7
+ SimpleCov.start do
8
+ add_filter "spec"
9
+ end
10
+
11
+ # Controller
12
+ require 'support/controller'
13
+
14
+ # Require Gem
15
+ require 'wikify'
16
+
17
+ ActionController::Base.load
18
+
19
+ # Require Models
20
+ require 'models/parent'
21
+ require 'models/document'
22
+ require 'models/revision'
23
+ require 'models/child'
24
+
25
+ # Configure Active Record
26
+ require 'sqlite3'
27
+ require 'support/database'
@@ -0,0 +1,45 @@
1
+ module ActionController
2
+ API = true
3
+
4
+ class Base
5
+ def self.load
6
+ ActiveSupport.run_load_hooks(:action_controller, self)
7
+ end
8
+
9
+ def self.before_filter(sym)
10
+ true
11
+ end
12
+ end
13
+ end
14
+
15
+ class Controller < ActionController::Base
16
+ def initialize
17
+ set_wikify_user
18
+ end
19
+
20
+ def current_user
21
+ OpenStruct.new({id: 1})
22
+ end
23
+
24
+ def request
25
+ parent = Parent.find_by_name("Committer 1")
26
+ parent.name = "Committer 1 Updated"
27
+ parent.save
28
+ end
29
+ end
30
+
31
+ class NilController < ActionController::Base
32
+ def initialize
33
+ set_wikify_user
34
+ end
35
+
36
+ def current_user
37
+ nil
38
+ end
39
+
40
+ def request
41
+ parent = Parent.find_by_name("Committer 1")
42
+ parent.name = "Committer 1 Updated"
43
+ parent.save
44
+ end
45
+ end
@@ -0,0 +1,73 @@
1
+ ActiveRecord::Base.establish_connection(
2
+ :adapter => 'sqlite3',
3
+ :database => ':memory:'
4
+ )
5
+
6
+ puts "---------------"
7
+ puts "Database Creation"
8
+ puts "---------------"
9
+
10
+ ActiveRecord::Schema.define do
11
+ create_table :parents do |table|
12
+ table.column :name, :string
13
+
14
+ table.timestamps
15
+ end
16
+
17
+ create_table :children do |table|
18
+ table.column :name, :string
19
+ table.column :parent_id, :integer
20
+
21
+ table.timestamps
22
+ end
23
+
24
+ create_table :wikify_versions do |table|
25
+ table.column :resource_id, :integer
26
+ table.column :resource_type, :string
27
+ table.column :data, :text
28
+ table.column :linked_child_resource_type, :string
29
+ table.column :linked_child_resource_id, :integer
30
+ table.column :linked_child, :boolean, default: false
31
+ table.column :event, :string
32
+ table.column :comitter_id, :integer
33
+
34
+ table.timestamps
35
+ end
36
+
37
+ create_table :revisions do |table|
38
+ table.column :thing_id, :integer
39
+ table.column :thing_type, :string
40
+ table.column :linked_child_thing_id, :integer
41
+ table.column :linked_child_thing_type, :string
42
+ table.column :linked_child, :boolean, default: false
43
+ table.column :data, :text
44
+ table.column :event, :string
45
+ table.column :comitter_id, :integer
46
+
47
+ table.timestamps
48
+ end
49
+
50
+ create_table :documents do |table|
51
+ table.column :title, :string
52
+
53
+ table.timestamps
54
+ end
55
+ end
56
+
57
+ puts "---------------"
58
+ puts "Begin Tests"
59
+ puts "---------------"
60
+
61
+ describe "The Database" do
62
+ it "should be empty" do
63
+ Parent.all.count.should eq 0
64
+ Child.all.count.should eq 0
65
+ end
66
+
67
+ it "should work" do
68
+ parent = Parent.new
69
+ parent.name = "Hello World"
70
+ parent.save
71
+ Parent.last.name.should eq "Hello World"
72
+ end
73
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Model Update" do
4
+ it "should create a version on update and set the event to :update" do
5
+ parent = Parent.new({name: "First Update Test"})
6
+ parent.save
7
+
8
+ ffound = Parent.find_by_name("First Update Test")
9
+ ffound.name = "Updated First Update Test"
10
+ ffound.save
11
+
12
+ sfound = Parent.find_by_name("Updated First Update Test")
13
+ sfound.versions.count.should eq 2
14
+ sfound.versions.last.data["name"].should eq "First Update Test"
15
+ sfound.versions.last.event.should eq "update"
16
+ end
17
+
18
+ it "should create a second version after the first" do
19
+ ffound = Parent.find_by_name("Updated First Update Test")
20
+ ffound.name = "Second Update Test"
21
+ ffound.save
22
+
23
+ sfound = Parent.find_by_name("Second Update Test")
24
+ sfound.versions.count.should eq 3
25
+ sfound.versions[1].data["name"].should eq "First Update Test"
26
+ sfound.versions.last.data["name"].should eq "Updated First Update Test"
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Wikify::Version do
4
+ it "should create a struct for your data" do
5
+ version = Wikify::Version.last
6
+ version.object.should be_a OpenStruct
7
+ end
8
+
9
+ it "should have a link to the previous version" do
10
+ version = Wikify::Version.last
11
+ version.previous.should be_a Wikify::Version
12
+ end
13
+
14
+ it "should have a link to the next version" do
15
+ version = Wikify::Version.last
16
+ prev = version.previous
17
+ prev.next.should be_a Wikify::Version
18
+ end
19
+
20
+ it "should return the current model for next if it is the latest version" do
21
+ version = Wikify::Version.last
22
+ version.next.should be_a version.restore_type.constantize
23
+ end
24
+
25
+ it "should find the version at a specified time" do
26
+ parent = Parent.new({name: 'Time Test'})
27
+ parent.save
28
+ sleep 3
29
+ parent = Parent.find(parent.id)
30
+ parent.name = "Time Test 1"
31
+ parent.save
32
+ sleep 1
33
+ version = parent.version_at(Time.now - 3)
34
+ version.should be_a Wikify::Version
35
+ version.object.name.should eq "Time Test"
36
+ end
37
+ end
data/wikify.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'wikify/version_number'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "wikify"
8
+ spec.version = Wikify::VersionNumber
9
+ spec.authors = ["Adam Laycock"]
10
+ spec.email = ["adam@arcath.net"]
11
+ spec.description = %q{Active Record Model history/versioning with restore functionality.}
12
+ spec.summary = %q{Active Record Model history/versioning with restore functionality.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", "~> 4.0"
22
+ spec.add_dependency "activesupport", "~> 4.0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency 'rspec'
27
+ spec.add_development_dependency 'sqlite3'
28
+ spec.add_development_dependency 'coveralls'
29
+ end
metadata ADDED
@@ -0,0 +1,194 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wikify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.pre
5
+ platform: ruby
6
+ authors:
7
+ - Adam Laycock
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: coveralls
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Active Record Model history/versioning with restore functionality.
112
+ email:
113
+ - adam@arcath.net
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .travis.yml
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - lib/generators/wikify/migration/USAGE
125
+ - lib/generators/wikify/migration/migration_generator.rb
126
+ - lib/wikify.rb
127
+ - lib/wikify/child_methods.rb
128
+ - lib/wikify/controller.rb
129
+ - lib/wikify/methods.rb
130
+ - lib/wikify/model.rb
131
+ - lib/wikify/version.rb
132
+ - lib/wikify/version_concern.rb
133
+ - lib/wikify/version_number.rb
134
+ - spec/children_spec.rb
135
+ - spec/comitter_spec.rb
136
+ - spec/config_spec.rb
137
+ - spec/create_spec.rb
138
+ - spec/destroy_spec.rb
139
+ - spec/included_spec.rb
140
+ - spec/logical_function_spec.rb
141
+ - spec/models/child.rb
142
+ - spec/models/document.rb
143
+ - spec/models/parent.rb
144
+ - spec/models/revision.rb
145
+ - spec/restore_spec.rb
146
+ - spec/spec_helper.rb
147
+ - spec/support/controller.rb
148
+ - spec/support/database.rb
149
+ - spec/update_spec.rb
150
+ - spec/version_spec.rb
151
+ - wikify.gemspec
152
+ homepage: ''
153
+ licenses:
154
+ - MIT
155
+ metadata: {}
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - '>='
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - '>'
168
+ - !ruby/object:Gem::Version
169
+ version: 1.3.1
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 2.0.0
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: Active Record Model history/versioning with restore functionality.
176
+ test_files:
177
+ - spec/children_spec.rb
178
+ - spec/comitter_spec.rb
179
+ - spec/config_spec.rb
180
+ - spec/create_spec.rb
181
+ - spec/destroy_spec.rb
182
+ - spec/included_spec.rb
183
+ - spec/logical_function_spec.rb
184
+ - spec/models/child.rb
185
+ - spec/models/document.rb
186
+ - spec/models/parent.rb
187
+ - spec/models/revision.rb
188
+ - spec/restore_spec.rb
189
+ - spec/spec_helper.rb
190
+ - spec/support/controller.rb
191
+ - spec/support/database.rb
192
+ - spec/update_spec.rb
193
+ - spec/version_spec.rb
194
+ has_rdoc: