changed 0.0.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9811b29b82154768e261bd90c16604ab7cb1c741eb45ad91cc4a4fe963e23be
4
- data.tar.gz: 8c8b3993c740fc868e200842fda818710dc74f05a8f38668582ae4026c833f8f
3
+ metadata.gz: 53782f67f91f94fb6371707c6898b36e531ca972ce8d9fe404089b9983d91740
4
+ data.tar.gz: d7d9968239f4e0a1c857abf6fd1aeb8938630d04a814927f7213de8cdceb2282
5
5
  SHA512:
6
- metadata.gz: 8b5007282b58dbc1c4d36b96f73d4369b126e392b6950fbcc327ebbbf2841005232ee0f70b453cb52f4dc85c2a627119f299e12fd70d54b2a91ed9afe13e7eec
7
- data.tar.gz: 22405f5a0d3e2b96f5e3b77823cc4725fe622cf0b888811a317cfde55c887e45f9e144a505be092de7317cd24c2547044e3d648033955ebfd4c8b31bc85a64d1
6
+ metadata.gz: 06473d2413c6f76544c63eced68807389cae2ce1dbea1024fc5b9e8a2c7856088ed0b90074b8d8a528b08a70be24c1ba34cc652397e07fcb37c317787026f331
7
+ data.tar.gz: e3d4a686dc92c98adcd378f66948645aca010e68555ff282e3d9891c6a91cc8065983b3e23462980fbd1af2af40823f06d33337ea71ccc1591c0a75bbe9449f6
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2018 Clutter
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Changed
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/changed`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ [![Gem Version](https://badge.fury.io/rb/changed.svg)](https://badge.fury.io/rb/changed)
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ A gem for tracking what **changed** when.
6
6
 
7
7
  ## Installation
8
8
 
@@ -14,22 +14,105 @@ gem 'changed'
14
14
 
15
15
  And then execute:
16
16
 
17
- $ bundle
17
+ ```bash
18
+ $ bundle
19
+ ```
18
20
 
19
21
  Or install it yourself as:
20
22
 
21
- $ gem install changed
23
+ ```bash
24
+ $ gem install changed
25
+ ```
26
+
27
+ After installing the gem run the following to setup:
28
+
29
+ ```bash
30
+ rails changed:install:migrations
31
+ rails db:migrate
32
+ ```
22
33
 
23
34
  ## Usage
24
35
 
25
- TODO: Write usage instructions here
36
+ This gem is designed to integrate with active record objects:
37
+
38
+ ```ruby
39
+ class Employee
40
+ include Changed::Auditable
41
+ belongs_to :company
42
+
43
+ audited :name, :email, :eid, :company, transformations: { eid: 'Employee ID' }
44
+ end
45
+ ```
46
+
47
+ To ensure the proper 'changer' is tracked, add the following code to your application controller:
48
+
49
+ ```ruby
50
+ before_action :configure_audit_changer
51
+
52
+ protected
53
+
54
+ def configure_audit_changer
55
+ Changed.changer = User.current
56
+ end
57
+ ```
26
58
 
27
- ## Development
59
+ To execute code with a different timestamp or changer, use the following:
60
+
61
+ ```ruby
62
+ employee = Employee.find_by(name: "...")
63
+ Changed.perform(changer: User.current, timestamp: Time.now) do
64
+ employee.name = "..."
65
+ employee.save!
66
+ end
67
+ ```
68
+
69
+ ### Fields
70
+
71
+ Fields (i.e. name, email, phone, etc) are tracked inside the `changeset` key of a generated audit. They can be queried using:
72
+
73
+ ```sql
74
+ SELECT
75
+ "audits"."timestamp",
76
+ "audits"."changeset"->'name'->>0 AS "was",
77
+ "audits"."changeset"->'name'->>1 AS "now",
78
+ "changers"."name" AS "changer"
79
+ FROM "audits"
80
+ JOIN "users" AS "changers" ON "audits"."changer_id" = "changers"."id" AND "audits"."changer_type" = 'User'
81
+ WHERE "audits"."changeset"->>'name' IS NOT NULL
82
+ ```
83
+
84
+ ### Associations
85
+
86
+ Associations (i.e. user, favourites, etc) are tracked by the `associations` table. They can be queried using:
87
+
88
+ ```sql
89
+ SELECT
90
+ "audits"."timestamp",
91
+ "changers"."name" AS "changer",
92
+ CASE "associations"."kind"
93
+ WHEN '0' THEN 'ADD'
94
+ WHEN '1' THEN 'REMOVE'
95
+ END AS "kind",
96
+ "users"."name" AS "user"
97
+ FROM "audits"
98
+ JOIN "associations" ON "associations"."audit_id" = "audits"."id"
99
+ JOIN "users" ON "associations"."associated_id" = "users"."id" AND "associations"."associated_type" = 'User'
100
+ JOIN "users" AS "changers" ON "audits"."changer_id" = "changers"."id" AND "audits"."changer_type" = 'User'
101
+ WHERE "associations"."name" = 'user'
102
+ ```
103
+
104
+ ## Configuration
105
+
106
+ Specifying `default_changer_proc` gives a changer if one cannot be inferred otherwise:
107
+
108
+ ```ruby
109
+ Changed.config.default_changer_proc = ->{ User.system }
110
+ ```
28
111
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
112
+ ## Status
30
113
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
114
+ [![CircleCI](https://circleci.com/gh/clutter/changed.svg?style=svg&circle-token=77cf2fadb88cfc6b16bf85643826152305dac75f)](https://circleci.com/gh/clutter/changed)
32
115
 
33
- ## Contributing
116
+ ## License
34
117
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/changed.
118
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,6 +1,12 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
6
+
7
+ load 'rails/tasks/engine.rake'
8
+ load 'rails/tasks/statistics.rake'
3
9
 
4
10
  RSpec::Core::RakeTask.new(:spec)
5
11
 
6
- task :default => :spec
12
+ task default: :spec
@@ -0,0 +1,5 @@
1
+ module Changed
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module Changed
2
+ class Association < ApplicationRecord
3
+ belongs_to :audit, class_name: 'Changed::Audit'
4
+ belongs_to :associated, polymorphic: true
5
+
6
+ enum kind: %i[add remove]
7
+
8
+ validates :name, presence: true
9
+
10
+ scope :ordered, -> { order(id: :desc) }
11
+ end
12
+ end
@@ -0,0 +1,109 @@
1
+ module Changed
2
+ class Audit < ApplicationRecord
3
+ module Event
4
+ CREATE = 'create'.freeze
5
+ UPDATE = 'update'.freeze
6
+ end
7
+
8
+ EVENTS = [
9
+ Event::CREATE,
10
+ Event::UPDATE,
11
+ ].freeze
12
+
13
+ belongs_to :changer, polymorphic: true, required: false
14
+ belongs_to :audited, polymorphic: true
15
+ has_many :associations, dependent: :destroy, class_name: 'Changed::Association'
16
+
17
+ scope :optimized, -> { preload(:changer, :audited, associations: :associated) }
18
+ scope :ordered, -> { order(id: :desc) }
19
+ scope :creates, -> { where(event: Event::CREATE) }
20
+ scope :updates, -> { where(event: Event::UPDATE) }
21
+
22
+ validates :event, inclusion: { in: EVENTS }
23
+ validates :audited, presence: true
24
+
25
+ scope :for, ->(audited) { where(audited: audited).ordered }
26
+
27
+ after_initialize -> { self.timestamp ||= (Changed.timestamp || Time.now) }
28
+
29
+ def fields
30
+ changeset.map do |name, value|
31
+ was, now = value
32
+ Field.new(was, now, transform(name))
33
+ end
34
+ end
35
+
36
+ def relationships
37
+ memo = {}
38
+ associations.each do |association|
39
+ memo[association.name] ||= Set.new
40
+ memo[association.name] << association
41
+ end
42
+ memo.map do |name, associations|
43
+ Relationship.new(associations, transform(name))
44
+ end
45
+ end
46
+
47
+ def track(event, fields)
48
+ self.changer = Changed.changer
49
+
50
+ fields.each do |attribute|
51
+ attribute = String(attribute)
52
+ if audited.saved_change_to_attribute?(attribute)
53
+ changeset[attribute] = audited.saved_change_to_attribute(attribute)
54
+ end
55
+ end
56
+
57
+ self.event = event
58
+ end
59
+
60
+ def anything?
61
+ changeset.any? || associations.any?
62
+ end
63
+
64
+ def changed?(key)
65
+ fields.map(&:name).include?(key)
66
+ end
67
+
68
+ # The 'change' provided needs to be a block, lambda, or proc that executes
69
+ # the changes. The other provided block is yielded pre and post the change.
70
+ def track_attribute_change(attribute, change)
71
+ attribute_was = yield
72
+ change.call
73
+ attribute_now = yield
74
+
75
+ return if attribute_was == attribute_now
76
+
77
+ changeset[String(attribute)] = [
78
+ attribute_was,
79
+ attribute_now,
80
+ ]
81
+ end
82
+
83
+ def track_association_change(name, change)
84
+ association_was = yield
85
+ change.call
86
+ association_now = yield
87
+ return if association_was == association_now
88
+
89
+ (association_was - association_now).each do |associated|
90
+ associations.build(name: name, associated: associated, kind: :remove)
91
+ end
92
+ (association_now - association_was).each do |associated|
93
+ associations.build(name: name, associated: associated, kind: :add)
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def transform(name)
100
+ (transformations[name] if transformations) || name
101
+ end
102
+
103
+ def transformations
104
+ @transformations = audited.class.auditable[:transformations] unless defined?(@transformations)
105
+ @transformations
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,13 @@
1
+ class CreateChangedAudits < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :changed_audits do |t|
4
+ t.references :changer, polymorphic: true, null: true, index: true
5
+ t.references :audited, polymorphic: true, null: false, index: true
6
+ t.jsonb :changeset, default: {}, null: false
7
+ t.string :event, null: false
8
+ t.datetime :timestamp, null: false
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ class CreateChangedAssociations < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :changed_associations do |t|
4
+ t.references :audit, null: false, index: true
5
+ t.references :associated, null: false, polymorphic: true, index: true
6
+ t.string :name, null: false
7
+ t.integer :kind, null: false
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ add_foreign_key :changed_associations, :changed_audits, column: :audit_id
13
+ end
14
+ end
@@ -1,5 +1,88 @@
1
- require "changed/version"
1
+ require 'request_store'
2
+
3
+ require 'changed/auditable'
4
+ require 'changed/builder'
5
+ require 'changed/config'
6
+ require 'changed/engine'
2
7
 
3
8
  module Changed
4
- # Your code goes here...
9
+ Field = Struct.new(:was, :now, :name)
10
+ Relationship = Struct.new(:associations, :name)
11
+
12
+ # Access the library configuration.
13
+ #
14
+ # ==== Examples
15
+ #
16
+ # Changed.config.default_changer_proc = ->{ User.system }
17
+ def self.config
18
+ @config ||= Config.new
19
+ end
20
+
21
+ # Access the timestamp (this value is set as the timestamp within an audit and defaults to now).
22
+ def self.timestamp
23
+ options[:timestamp] || Time.now
24
+ end
25
+
26
+ # Customize the timestamp (uses a request store to only change lifeycle event).
27
+ #
28
+ # ==== Attributes
29
+ #
30
+ # * +timestamp+ - A timestamp to use.
31
+ #
32
+ # ==== Examples
33
+ #
34
+ # Changed.timestamp = 2.hours.ago
35
+ def self.timestamp=(timestamp)
36
+ options[:timestamp] = timestamp
37
+ end
38
+
39
+ # Access the changer (this value is set as the changer within an audit and defaults to config).
40
+ def self.changer
41
+ options[:changer] || config.default_changer_proc&.call
42
+ end
43
+
44
+ # Customize the changer (uses a request store to only change lifeycle event).
45
+ #
46
+ # ==== Attributes
47
+ #
48
+ # * +changer+ - A changer to use.
49
+ #
50
+ # ==== Examples
51
+ #
52
+ # Changed.changer = User.current
53
+ def self.changer=(changer)
54
+ options[:changer] = changer
55
+ end
56
+
57
+ # Perform a block with custom override options.
58
+ #
59
+ # ==== Attributes
60
+ #
61
+ # * +options+ - Values for the changer and / or timestamp.
62
+ # * +block+ - Some code to run with the new options.
63
+ #
64
+ # ==== Examples
65
+ #
66
+ # Changed.perform(changer: User.system, timestamp: 2.hours.ago) do
67
+ # widget.name = "Sprocket"
68
+ # widget.save!
69
+ # end
70
+ def self.perform(options = {}, &block)
71
+ backup = self.options
72
+ self.options = options
73
+ block.call
74
+ ensure
75
+ self.options = backup
76
+ end
77
+
78
+ OPTIONS_REQUEST_STORE_KEY = :changed_options
79
+ private_constant :OPTIONS_REQUEST_STORE_KEY
80
+
81
+ def self.options=(options)
82
+ RequestStore.store[OPTIONS_REQUEST_STORE_KEY] = options
83
+ end
84
+
85
+ def self.options
86
+ RequestStore.store[OPTIONS_REQUEST_STORE_KEY] ||= {}
87
+ end
5
88
  end
@@ -0,0 +1,51 @@
1
+ require 'active_support/concern'
2
+
3
+ module Changed
4
+ module Auditable
5
+ extend ActiveSupport::Concern
6
+
7
+ # ==== Overview
8
+ #
9
+ # A helper that caches an audit between operations. Once an audit is persisted this method handles the generation
10
+ # of a new audit, thus ensuring that each transaction is audited separately.
11
+ def audit
12
+ @audit = Audit.new(audited: self) if @audit.nil? || @audit.persisted?
13
+ @audit
14
+ end
15
+
16
+ included do
17
+ has_many :audits, -> { ordered }, as: :audited, class_name: 'Changed::Audit', dependent: :destroy
18
+
19
+ # ==== Overview
20
+ #
21
+ # A helper for setting up options.
22
+ #
23
+ def self.auditable
24
+ @auditable ||= {}
25
+ end
26
+
27
+ # ==== Overview
28
+ #
29
+ # A concern for setting up auditable for a model. An audited model can track the changes to attributes
30
+ # (ints, bools, strings, dates, times) or associations (`has_many`, `belongs_to`, `has_and_belongs_to_many`).
31
+ #
32
+ # The `audit` call needs to be placed after all `has_many`, `belongs_to`, `has_and_belongs_to_many` declarations
33
+ # in order for the association reflection to work. Multiple inclusions of `audit` are not supported.
34
+ #
35
+ # ==== Options
36
+ #
37
+ # * +:keys:+ - An array of symbols for the attributes or associations that are tracked with each audit.
38
+ # * +:transformations:+ - A hash of of attribute name mappings (i.e. 'number' to '#' or 'user' to 'rep').
39
+ #
40
+ # ==== Usage
41
+ #
42
+ # audit(:number, :scheduled, :region, :address, :items, transformations: { number: "#", user: "rep" })
43
+ #
44
+ def self.audited(*keys, transformations: nil)
45
+ auditable[:transformations] = ActiveSupport::HashWithIndifferentAccess.new(transformations) if transformations
46
+ Builder.build(self, *keys)
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,117 @@
1
+ module Changed
2
+ class Builder
3
+ class ArgumentError < ::ArgumentError
4
+ end
5
+
6
+ ARGUMENT_ERROR_EMPTY_KEYS_MESSAGE = 'audited requires specifying a splat of keys'.freeze
7
+
8
+ def self.build(*args)
9
+ new(*args).build
10
+ end
11
+
12
+ def initialize(klass, *keys)
13
+ raise ArgumentError, ARGUMENT_ERROR_EMPTY_KEYS_MESSAGE if keys.empty?
14
+
15
+ @klass = klass
16
+ @keys = keys
17
+ end
18
+
19
+ def build
20
+ define_callbacks_for_associations
21
+ define_after_create_callback
22
+ define_after_update_callback
23
+ end
24
+
25
+ private
26
+
27
+ def define_callbacks_for_associations
28
+ @keys.each do |key|
29
+ association = @klass.reflect_on_association(key)
30
+ case association
31
+ when ActiveRecord::Reflection::HasManyReflection,
32
+ ActiveRecord::Reflection::HasAndBelongsToManyReflection,
33
+ ActiveRecord::Reflection::ThroughReflection
34
+ define_callbacks_for_has_many(association)
35
+ when ActiveRecord::Reflection::BelongsToReflection
36
+ define_callbacks_for_belongs_to(association)
37
+ when ActiveRecord::Reflection::HasOneReflection
38
+ define_callbacks_for_has_one(association)
39
+ end
40
+ end
41
+ end
42
+
43
+ def after_create_or_update_for_belongs_to_callback(key, foreign_key, foreign_type, class_name)
44
+ proc do |resource|
45
+ was_associated_id, now_associated_id = resource.saved_change_to_attribute(foreign_key)
46
+ was_associated_type, now_associated_type = resource.saved_change_to_attribute(foreign_type)
47
+ associated_type = resource[foreign_type] || class_name
48
+
49
+ if was_associated_id
50
+ resource.audit.associations.build(
51
+ name: key,
52
+ kind: :remove, associated_id: was_associated_id, associated_type: was_associated_type || associated_type
53
+ )
54
+ end
55
+
56
+ if now_associated_id
57
+ resource.audit.associations.build(
58
+ name: key, kind: :add,
59
+ associated_id: now_associated_id, associated_type: now_associated_type || associated_type
60
+ )
61
+ end
62
+ end
63
+ end
64
+
65
+ def define_callbacks_for_belongs_to(association)
66
+ callback = after_create_or_update_for_belongs_to_callback(
67
+ association.name,
68
+ association.foreign_key,
69
+ association.foreign_type,
70
+ association.class_name
71
+ )
72
+
73
+ @klass.after_update callback
74
+ @klass.after_create callback
75
+ end
76
+
77
+ def before_add_for_has_many_callback(name)
78
+ proc do |_method, resource, associated|
79
+ resource.audit.associations.build(name: name, associated: associated, kind: :add)
80
+ end
81
+ end
82
+
83
+ def before_remove_for_has_many_callback(name)
84
+ proc do |_method, resource, associated|
85
+ resource.audit.associations.build(name: name, associated: associated, kind: :remove)
86
+ end
87
+ end
88
+
89
+ def define_callbacks_for_has_many(association)
90
+ name = association.name
91
+ @klass.send(:"before_add_for_#{association.name}") << before_add_for_has_many_callback(name)
92
+ @klass.send(:"before_remove_for_#{association.name}") << before_remove_for_has_many_callback(name)
93
+ end
94
+
95
+ def define_callbacks_for_has_one(association)
96
+ raise ArgumentError, "unsupported reflection '#{association.name}'"
97
+ end
98
+
99
+ def define_after_create_callback
100
+ keys = @keys
101
+ @klass.after_create do |resource|
102
+ audit = resource.audit
103
+ audit.track(Audit::Event::CREATE, keys)
104
+ audit.save! if audit.anything?
105
+ end
106
+ end
107
+
108
+ def define_after_update_callback
109
+ keys = @keys
110
+ @klass.after_update do |resource|
111
+ audit = resource.audit
112
+ audit.track(Audit::Event::UPDATE, keys)
113
+ audit.save! if audit.anything?
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,5 @@
1
+ module Changed
2
+ class Config
3
+ attr_accessor :default_changer_proc
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ module Changed
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Changed
4
+
5
+ config.generators do |generator|
6
+ generator.test_framework :rspec
7
+ generator.fixture_replacement :factory_bot, dir: 'spec/factories'
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module Changed
2
- VERSION = "0.0.1"
2
+ VERSION = '1.2.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,77 +1,207 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: changed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-15 00:00:00.000000000 Z
11
+ date: 2020-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: request_store
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: brakeman
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
13
55
  - !ruby/object:Gem::Dependency
14
56
  name: bundler
15
57
  requirement: !ruby/object:Gem::Requirement
16
58
  requirements:
17
- - - "~>"
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: byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
18
74
  - !ruby/object:Gem::Version
19
- version: '1.16'
75
+ version: '0'
20
76
  type: :development
21
77
  prerelease: false
22
78
  version_requirements: !ruby/object:Gem::Requirement
23
79
  requirements:
24
- - - "~>"
80
+ - - ">="
25
81
  - !ruby/object:Gem::Version
26
- version: '1.16'
82
+ version: '0'
27
83
  - !ruby/object:Gem::Dependency
28
- name: rake
84
+ name: factory_bot_rails
29
85
  requirement: !ruby/object:Gem::Requirement
30
86
  requirements:
31
- - - "~>"
87
+ - - ">="
32
88
  - !ruby/object:Gem::Version
33
- version: '10.0'
89
+ version: '0'
34
90
  type: :development
35
91
  prerelease: false
36
92
  version_requirements: !ruby/object:Gem::Requirement
37
93
  requirements:
38
- - - "~>"
94
+ - - ">="
39
95
  - !ruby/object:Gem::Version
40
- version: '10.0'
96
+ version: '0'
41
97
  - !ruby/object:Gem::Dependency
42
- name: rspec
98
+ name: pg
43
99
  requirement: !ruby/object:Gem::Requirement
44
100
  requirements:
45
- - - "~>"
101
+ - - ">="
46
102
  - !ruby/object:Gem::Version
47
- version: '3.0'
103
+ version: '0'
48
104
  type: :development
49
105
  prerelease: false
50
106
  version_requirements: !ruby/object:Gem::Requirement
51
107
  requirements:
52
- - - "~>"
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec_junit_formatter
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
53
116
  - !ruby/object:Gem::Version
54
- version: '3.0'
55
- description:
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec-rails
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: shoulda-matchers
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ description: "⏱"
56
182
  email:
57
- - kevin@ksylvest.com
183
+ - kevin@clutter.com
58
184
  executables: []
59
185
  extensions: []
60
186
  extra_rdoc_files: []
61
187
  files:
62
- - ".gitignore"
63
- - ".rspec"
64
- - ".travis.yml"
65
- - Gemfile
188
+ - LICENSE
66
189
  - README.md
67
190
  - Rakefile
68
- - bin/console
69
- - bin/setup
70
- - changed.gemspec
191
+ - app/models/changed/application_record.rb
192
+ - app/models/changed/association.rb
193
+ - app/models/changed/audit.rb
194
+ - db/migrate/20180213015838_create_changed_audits.rb
195
+ - db/migrate/20180213015849_create_changed_associations.rb
71
196
  - lib/changed.rb
197
+ - lib/changed/auditable.rb
198
+ - lib/changed/builder.rb
199
+ - lib/changed/config.rb
200
+ - lib/changed/engine.rb
72
201
  - lib/changed/version.rb
73
- homepage:
74
- licenses: []
202
+ homepage: https://github.com/clutter/changed
203
+ licenses:
204
+ - MIT
75
205
  metadata: {}
76
206
  post_install_message:
77
207
  rdoc_options: []
@@ -79,18 +209,17 @@ require_paths:
79
209
  - lib
80
210
  required_ruby_version: !ruby/object:Gem::Requirement
81
211
  requirements:
82
- - - ">="
212
+ - - ">"
83
213
  - !ruby/object:Gem::Version
84
- version: '0'
214
+ version: 2.6.0
85
215
  required_rubygems_version: !ruby/object:Gem::Requirement
86
216
  requirements:
87
217
  - - ">="
88
218
  - !ruby/object:Gem::Version
89
219
  version: '0'
90
220
  requirements: []
91
- rubyforge_project:
92
- rubygems_version: 2.7.6
221
+ rubygems_version: 3.0.3
93
222
  signing_key:
94
223
  specification_version: 4
95
- summary: A simple gem for change tracking.
224
+ summary: Provides insights into what changed.
96
225
  test_files: []
data/.gitignore DELETED
@@ -1,11 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
@@ -1,5 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.5.1
5
- before_install: gem install bundler -v 1.16.1
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
-
5
- # Specify your gem's dependencies in changed.gemspec
6
- gemspec
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "changed"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,24 +0,0 @@
1
-
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "changed/version"
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "changed"
8
- spec.version = Changed::VERSION
9
- spec.authors = ["Kevin Sylvestre"]
10
- spec.email = ["kevin@ksylvest.com"]
11
-
12
- spec.summary = %q{A simple gem for change tracking.}
13
-
14
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
15
- f.match(%r{^(test|spec|features)/})
16
- end
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
20
-
21
- spec.add_development_dependency "bundler", "~> 1.16"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency "rspec", "~> 3.0"
24
- end