obscured-timeline 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e0e1584c8287352150fb7ec886112dadb191da1dda84f12221c17d02b9ea3b56
4
+ data.tar.gz: 6db5ea73e99f2d7ee20bbbe14d9e2ff36752dd32e8f4487345fd252966c7bd34
5
+ SHA512:
6
+ metadata.gz: d447fec014fe51a9d58397bccbc79b0754fccf8102e80fe3a36a4ec2b111101a3958b11baa6415c237f9724f8c00727d0bedd075987894b2f8b38653eba3801f
7
+ data.tar.gz: a457a5e1aac0222ab6c3b144294faf4c64b0621f11fed394cdd2a069aaf1c59b6535e45bfe186a023922e5ad34c0a8f5ee0d6ee3ce69bd75797d901aab752fbc
@@ -0,0 +1,28 @@
1
+ version: "2"
2
+ checks:
3
+ complex-logic:
4
+ config:
5
+ threshold: 10
6
+ file-lines:
7
+ enabled: false
8
+ method-complexity:
9
+ config:
10
+ threshold: 15
11
+ method-lines:
12
+ config:
13
+ threshold: 250
14
+ similar-code:
15
+ enabled: false
16
+ plugins:
17
+ rubocop:
18
+ enabled: true
19
+ channel: rubocop-0-71
20
+ coffeelint:
21
+ enabled: true
22
+ eslint:
23
+ enabled: true
24
+ csslint:
25
+ enabled: true
26
+ exclude_patterns:
27
+ - "tests/"
28
+ - "spec/"
@@ -0,0 +1,3 @@
1
+ .idea
2
+ .idea/
3
+ coverage/
@@ -0,0 +1,14 @@
1
+ Metrics/AbcSize:
2
+ Max: 50
3
+ Metrics/ClassLength:
4
+ Enabled: false
5
+ Metrics/CyclomaticComplexity:
6
+ Max: 15
7
+ Metrics/LineLength:
8
+ Enabled: false
9
+ Metrics/MethodLength:
10
+ Enabled: false
11
+ Metrics/PerceivedComplexity:
12
+ Max: 15
13
+ Naming/FileName:
14
+ Enabled: false
@@ -0,0 +1 @@
1
+ obscured-timeline
@@ -0,0 +1 @@
1
+ 2.6.3
@@ -0,0 +1,4 @@
1
+ SimpleCov.start do
2
+ add_filter 'version'
3
+ add_filter '/spec/'
4
+ end
@@ -0,0 +1,17 @@
1
+ env:
2
+ global:
3
+ - CC_TEST_REPORTER_ID=2a254563f48d490eab9b9e9682ea30c8b13d221d06bb1fed6e3c045124b49e6c
4
+ language: ruby
5
+ bundler_args: --with development
6
+ rvm:
7
+ - 2.6.3
8
+ services:
9
+ - mongodb
10
+ before_script:
11
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
12
+ - chmod +x ./cc-test-reporter
13
+ - ./cc-test-reporter before-build
14
+ script:
15
+ - bundle exec rspec
16
+ after_script:
17
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'http://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in obscured-timeline.gemspec
6
+ gemspec
@@ -0,0 +1,70 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ obscured-timeline (1.0.0)
5
+ activesupport
6
+ mongoid
7
+ mongoid_search
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ activemodel (6.0.2.2)
13
+ activesupport (= 6.0.2.2)
14
+ activesupport (6.0.2.2)
15
+ concurrent-ruby (~> 1.0, >= 1.0.2)
16
+ i18n (>= 0.7, < 2)
17
+ minitest (~> 5.1)
18
+ tzinfo (~> 1.1)
19
+ zeitwerk (~> 2.2)
20
+ bson (4.8.2)
21
+ concurrent-ruby (1.1.6)
22
+ diff-lcs (1.3)
23
+ docile (1.3.2)
24
+ factory_bot (5.1.1)
25
+ activesupport (>= 4.2.0)
26
+ fast-stemmer (1.0.2)
27
+ i18n (1.8.2)
28
+ concurrent-ruby (~> 1.0)
29
+ minitest (5.14.0)
30
+ mongo (2.11.4)
31
+ bson (>= 4.4.2, < 5.0.0)
32
+ mongoid (7.1.0)
33
+ activemodel (>= 5.1, < 6.1)
34
+ mongo (>= 2.7.0, < 3.0.0)
35
+ mongoid_search (0.3.6)
36
+ fast-stemmer (~> 1.0.0)
37
+ mongoid (>= 3.0.0)
38
+ rspec (3.9.0)
39
+ rspec-core (~> 3.9.0)
40
+ rspec-expectations (~> 3.9.0)
41
+ rspec-mocks (~> 3.9.0)
42
+ rspec-core (3.9.1)
43
+ rspec-support (~> 3.9.1)
44
+ rspec-expectations (3.9.1)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.9.0)
47
+ rspec-mocks (3.9.1)
48
+ diff-lcs (>= 1.2.0, < 2.0)
49
+ rspec-support (~> 3.9.0)
50
+ rspec-support (3.9.2)
51
+ simplecov (0.18.5)
52
+ docile (~> 1.1)
53
+ simplecov-html (~> 0.11)
54
+ simplecov-html (0.12.2)
55
+ thread_safe (0.3.6)
56
+ tzinfo (1.2.6)
57
+ thread_safe (~> 0.1)
58
+ zeitwerk (2.3.0)
59
+
60
+ PLATFORMS
61
+ ruby
62
+
63
+ DEPENDENCIES
64
+ factory_bot
65
+ obscured-timeline!
66
+ rspec
67
+ simplecov
68
+
69
+ BUNDLED WITH
70
+ 2.1.4
@@ -0,0 +1,94 @@
1
+ [![Vulnerabilities](https://snyk.io/test/github/gonace/obscured.timeline/badge.svg)](https://snyk.io/test/github/gonace/obscured.timeline)
2
+ [![Build Status](https://travis-ci.org/gonace/Obscured.Timeline.svg?branch=master)](https://travis-ci.org/gonace/Obscured.Timeline)
3
+ [![Test Coverage](https://codeclimate.com/github/gonace/Obscured.Timeline/badges/coverage.svg)](https://codeclimate.com/github/gonace/Obscured.Timeline)
4
+ [![Code Climate](https://codeclimate.com/github/gonace/Obscured.Timeline/badges/gpa.svg)](https://codeclimate.com/github/gonace/Obscured.Timeline)
5
+
6
+ # Obscured::Timeline
7
+ ## Introduction
8
+ Obscured timeline adds event to a separate collection for an entity (Document), the naming of the class (Mongoid Document) is used for naming the timeline collection, so if the class is named "Account" the collection name will end up being "account_timeline".
9
+
10
+ ## Installation
11
+ ### Requirements
12
+ - activesupport
13
+ - mongoid
14
+ - mongoid_search
15
+
16
+ ##### Add this line to your application's Gemfile
17
+ ```ruby
18
+ gem 'obscured-timeline', :git => 'https://github.com/gonace/Obscured.Timeline.git', :branch => 'master'
19
+ ```
20
+
21
+ ##### Execute
22
+ ```
23
+ $ bundle
24
+ ```
25
+
26
+ ### Usage
27
+ #### Base
28
+ Use this in files where you create non-default log collections.
29
+ ```ruby
30
+ require 'obscured-timeline'
31
+ ```
32
+
33
+
34
+ ### Example
35
+ #### Document
36
+ ```ruby
37
+ require 'obscured-timeline'
38
+
39
+ module Obscured
40
+ class Account
41
+ include Mongoid::Document
42
+ include Mongoid::Timestamps
43
+ include Obscured::Timeline::Tracker
44
+
45
+ field :name, type: String
46
+ field :email, type: String
47
+ field :locked, type: Boolean, default: false
48
+ end
49
+
50
+ def lock!
51
+ self.locked = true
52
+ self.add_event(type: :security, message: "Account has been locked!", producer: self.username)
53
+ save
54
+ end
55
+ end
56
+
57
+
58
+ account = Obscured::Account.create(:name => "John Doe", :email => "john.doe@obscured.se")
59
+ event = account.add_event(type: :comment, message: "Lorem ipsum dolor sit amet?", producer: "homer.simpson@obscured.se")
60
+
61
+ #returns array of events for document (proprietor)
62
+ account.get_events
63
+
64
+ #returns event by id
65
+ account.get_event(event.id.to_s)
66
+
67
+ #returns array of events by predefined params, supports pagination
68
+ account.find_events({ type: nil, producer: nil }, { limit: 20, skip: 0, order: :created_at.desc, only: [:id, :type, :message, :producer, :created_at, :updated_at, :proprietor] })
69
+
70
+ #retuns array of events
71
+ account.search_events("homer.simpson@obscured.se", { type: :comment, limit: 20, skip: 0, order: :created_at.desc })
72
+ ```
73
+
74
+ #### Service
75
+ ```ruby
76
+ module Obscured
77
+ class AccountTimelineService
78
+ include Mongoid::Timeline::Service::Base
79
+ end
80
+ end
81
+
82
+ module Obscured
83
+ class AccountSynchronizer
84
+ def initialize(account)
85
+ @account = account
86
+ @service = Obscured::AccountTimelineService.new
87
+ end
88
+
89
+ def timeline
90
+ @service.by(proprietor: { account_id: account.id })
91
+ end
92
+ end
93
+ end
94
+ ```
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongoid'
4
+ require 'mongoid_search'
5
+
6
+ Mongoid::Search.setup do |cfg|
7
+ cfg.strip_symbols = /["]/
8
+ cfg.strip_accents = //
9
+ end
10
+
11
+ require 'obscured-timeline/record'
12
+ require 'obscured-timeline/service'
13
+ require 'obscured-timeline/tracker'
14
+
15
+ module Mongoid
16
+ module Timeline
17
+ end
18
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongoid'
4
+ require 'mongoid_search'
5
+
6
+ module Mongoid
7
+ module Timeline
8
+ module Record
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Mongoid::Document
13
+ include Mongoid::Search
14
+ include Mongoid::Timestamps
15
+
16
+ field :type, type: Symbol
17
+ field :severity, type: Symbol, default: :informational
18
+ field :message, type: String
19
+ field :producer, type: String
20
+ field :proprietor, type: Hash
21
+
22
+ index({ type: 1 }, background: true)
23
+ index({ producer: 1 }, background: true)
24
+ index({ _keywords: 1 }, background: true)
25
+
26
+ search_in :id, :type, :producer
27
+ end
28
+
29
+ module ClassMethods
30
+ def make(params = {})
31
+ raise ArgumentError, 'type missing' if params[:type].blank?
32
+ raise ArgumentError, 'type must be a symbol' unless params[:type].instance_of?(Symbol)
33
+ raise ArgumentError, 'message missing' if params[:message].nil?
34
+ raise ArgumentError, 'producer missing' if params[:producer].blank?
35
+ raise ArgumentError, 'proprietor missing' if params[:proprietor].blank?
36
+
37
+ doc = new
38
+ doc.type = params[:type]
39
+ doc.severity = params[:severity].to_sym unless params[:severity].blank?
40
+ doc.message = params[:message]
41
+ doc.producer = params[:producer]
42
+ doc.proprietor = params[:proprietor]
43
+ doc
44
+ end
45
+
46
+ def make!(params = {})
47
+ doc = make(params)
48
+ doc.save!
49
+ doc
50
+ end
51
+
52
+ def by(params = {}, options = {})
53
+ limit = options[:limit].blank? ? nil : options[:limit].to_i
54
+ skip = options[:skip].blank? ? nil : options[:skip].to_i
55
+ order = options[:order].blank? ? :created_at.desc : options[:order]
56
+ only = options[:only].blank? ? %i[id type message producer created_at updated_at proprietor] : options[:only]
57
+
58
+ query = {}
59
+ query[:type] = params[:type].to_sym if params[:type]
60
+ query[:severity] = params[:severity].to_sym if params[:severity]
61
+ query[:producer] = params[:producer].to_sym if params[:producer]
62
+ params[:proprietor]&.map { |k, v| query.merge!("proprietor.#{k}" => v) }
63
+
64
+ criterion = where(query).only(only).limit(limit).skip(skip)
65
+ criterion = criterion.order_by(order) if order
66
+
67
+ docs = criterion.to_a
68
+ docs
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Timeline
5
+ module Service
6
+ module Base
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ def all(criterion = {})
12
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
13
+ m.all(criterion).to_a
14
+ end
15
+ end
16
+
17
+ def find(*args)
18
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
19
+ m.find(*args)
20
+ end
21
+ end
22
+
23
+ def find_by(attrs = {})
24
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
25
+ m.find_by(attrs).to_a
26
+ end
27
+ end
28
+
29
+ def where(expression)
30
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
31
+ m.where(expression).to_a
32
+ end
33
+ end
34
+
35
+ def by(params = {}, options = {})
36
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
37
+ m.by(params, options)
38
+ end
39
+ end
40
+
41
+ def delete(id)
42
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
43
+ m.where(id: id).delete
44
+ end
45
+ end
46
+
47
+ class Record
48
+ include Mongoid::Timeline::Record
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module Mongoid
6
+ module Timeline
7
+ module Tracker
8
+ extend ActiveSupport::Concern
9
+
10
+ class Record
11
+ include Mongoid::Timeline::Record
12
+ end
13
+
14
+ # Adds event to the x_timeline collection for document. This is
15
+ # only called on manually.
16
+ #
17
+ # @example Add event.
18
+ # document.add_event
19
+ #
20
+ # @return [ document ]
21
+ def add_event(event)
22
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
23
+ m.make!(event.merge(proprietor: { "#{self.class.name.demodulize.downcase}_id".to_sym => id }))
24
+ end
25
+ end
26
+
27
+ # Get an event from the x_timeline collection for document. This is
28
+ # only called on manually.
29
+ #
30
+ # @example Get event.
31
+ # document.get_event(id)
32
+ #
33
+ # @return [ document ]
34
+ def get_event(id)
35
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
36
+ m.find(id)
37
+ end
38
+ end
39
+
40
+ # Get events from the x_timeline collection for document by proprietor. This is
41
+ # only called on manually.
42
+ #
43
+ # @example Get event.
44
+ # document.get_events
45
+ # document.events
46
+ #
47
+ # @return [ documents ]
48
+ def get_events
49
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
50
+ m.by(proprietor: { "#{self.class.name.demodulize.downcase}_id".to_sym => id })
51
+ end
52
+ end
53
+ alias events get_events
54
+
55
+ # Find events from the x_timeline collection for document. This is
56
+ # only called on manually.
57
+ #
58
+ # @example Get event.
59
+ # document.find_events(params, options)
60
+ #
61
+ # @return [ documents ]
62
+ def find_events(params, options)
63
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
64
+ m.by({ proprietor: { "#{self.class.name.demodulize.downcase}_id".to_sym => id } }.merge(params), options)
65
+ end
66
+ end
67
+
68
+ # Search events from the x_timeline collection for document. This is
69
+ # only called on manually.
70
+ #
71
+ # @example Get event.
72
+ # document.search_events(text, options)
73
+ #
74
+ # @return [ documents ]
75
+ def search_events(text, options)
76
+ limit = options[:limit].blank? ? nil : options[:limit].to_i
77
+ skip = options[:skip].blank? ? nil : options[:skip].to_i
78
+ order = options[:order].blank? ? :created_at.desc : options[:order]
79
+
80
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
81
+ query = {}
82
+ query[:type] = options[:type].to_sym if options[:type]
83
+ query[:severity] = options[:severity].to_sym if options[:severity]
84
+
85
+ criteria = m.where(query).full_text_search(text)
86
+ criteria = criteria.order_by(order) if order
87
+ criteria = criteria.limit(limit).skip(skip)
88
+
89
+ docs = criteria.to_a
90
+ docs
91
+ end
92
+ end
93
+
94
+ # Edit an event from the x_timeline collection by id. This is
95
+ # only called on manually.
96
+ #
97
+ # @example Get event.
98
+ # document.edit_event(id, params)
99
+ #
100
+ # @return [ document ]
101
+ def edit_event(id, params = {})
102
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
103
+ event = m.where(id: id).first
104
+ event.message = params[:message] if params[:message]
105
+ event.save
106
+ event
107
+ end
108
+ end
109
+
110
+ # Delete an event from the x_timeline collection by id. This is
111
+ # only called on manually.
112
+ #
113
+ # @example Get event.
114
+ # document.get_event(id)
115
+ #
116
+ # @return [ document ]
117
+ def delete_event(id)
118
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
119
+ m.where(id: id).delete
120
+ end
121
+ end
122
+
123
+ # Clear events from the x_timeline collection for document. This is
124
+ # only called on manually.
125
+ #
126
+ # @example Get event.
127
+ # document.clear_events
128
+ #
129
+ # @return [ documents ]
130
+ def clear_events
131
+ Record.with(collection: "#{self.class.name.demodulize.downcase}_timeline") do |m|
132
+ m.where(proprietor: { "#{self.class.name.demodulize.downcase}_id".to_sym => id }).delete
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Timeline
5
+ VERSION = '1.0.0'
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'obscured-timeline/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = 'obscured-timeline'
9
+ gem.version = Mongoid::Timeline::VERSION
10
+ gem.platform = Gem::Platform::RUBY
11
+ gem.licenses = ['MIT']
12
+ gem.authors = ['Erik Hennerfors']
13
+ gem.email = ['erik.hennerfors@obscured.se']
14
+ gem.description = 'Mongoid extension to handles a timeline of events for an entity (e.g. User)'
15
+ gem.summary = 'Mongoid extension that adds the ability to handle events in a timeline for a entity (e.g. User)'
16
+ gem.homepage = 'https://github.com/gonace/Obscured.Timeline'
17
+
18
+ gem.required_ruby_version = '>= 2'
19
+
20
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
21
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
22
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
23
+ gem.require_paths = ['lib']
24
+
25
+ gem.add_dependency 'activesupport'
26
+ gem.add_dependency 'mongoid'
27
+ gem.add_dependency 'mongoid_search'
28
+
29
+ gem.add_development_dependency 'factory_bot'
30
+ gem.add_development_dependency 'rspec'
31
+ gem.add_development_dependency 'simplecov'
32
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'setup'
4
+ require_relative 'helpers/account_document'
5
+ require_relative 'helpers/account_service'
6
+
7
+
8
+ describe Mongoid::Timeline::Service::Account do
9
+ let!(:account) { Obscured::Account.new(email: 'homer.simpsons@obscured.se') }
10
+ let!(:message) { 'Praesent a massa dui. Etiam eget purus consequat, mollis erat et, rhoncus tortor.' }
11
+ let!(:service) { Mongoid::Timeline::Service::Account.new }
12
+
13
+ before(:each) do
14
+ 2.times do
15
+ account.add_event(type: :event, message: message, producer: account.email)
16
+ end
17
+ 5.times do
18
+ account.add_event(type: :comment, message: message, producer: account.email)
19
+ end
20
+ 5.times do
21
+ account.add_event(type: :change, message: message, producer: account.email)
22
+ end
23
+ end
24
+
25
+ describe 'all' do
26
+ let(:response) { service.all }
27
+
28
+ it { expect(response.count).to eq(12) }
29
+ end
30
+
31
+ describe 'find' do
32
+ let!(:event) { account.add_event(type: :change, message: message, producer: account.id) }
33
+ let(:response) { service.find(event.id) }
34
+
35
+ it { expect(response).to_not be(nil) }
36
+ it { expect(response.id).to eq(event.id) }
37
+ end
38
+
39
+ describe 'find_by' do
40
+ let!(:event) { account.add_event(type: :change, message: message, producer: account.id) }
41
+ let(:response) { service.find_by(type: :change) }
42
+
43
+ it { expect(response).to_not be(nil) }
44
+ it { expect(response.count).to eq(1) }
45
+ end
46
+
47
+ describe 'by' do
48
+ context 'proprietor' do
49
+ let(:response) { service.by(proprietor: { account_id: account.id }) }
50
+
51
+ it { expect(response.length).to eq(12) }
52
+ end
53
+
54
+ context 'type' do
55
+ let(:response) { service.by(type: :comment) }
56
+
57
+ it { expect(response.count).to eq(5) }
58
+ end
59
+
60
+ context 'proprietor and type' do
61
+ let(:response) { service.by(type: :event, proprietor: { account_id: account.id }) }
62
+
63
+ it { expect(response.length).to eq(2) }
64
+ end
65
+ end
66
+
67
+ describe 'where' do
68
+ context 'returns correct documents' do
69
+ let(:response) { service.where(type: :event) }
70
+
71
+ it { expect(response.count).to eq(2) }
72
+ end
73
+ end
74
+
75
+ describe 'delete' do
76
+ context 'deletes document by id' do
77
+ let!(:event) { account.add_event(type: :change, message: message, producer: account.id) }
78
+
79
+ it { expect(service.delete(event.id.to_s)).to eq(1) }
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,11 @@
1
+ spec:
2
+ options:
3
+ raise_not_found_error: false
4
+ clients:
5
+ default:
6
+ database: timeline_testing
7
+ hosts:
8
+ - localhost:27017
9
+ options:
10
+ max_pool_size: 100
11
+ min_pool_size: 5
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/obscured-timeline/record'
4
+
5
+ FactoryBot.define do
6
+ factory :event, class: Mongoid::Timeline::Record do
7
+ type { :comment }
8
+ message { 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }
9
+ producer { 'homer.simpson@obscured.se' }
10
+ proprietor { {} }
11
+
12
+
13
+ trait :as_comment do
14
+ type { :comment }
15
+ end
16
+
17
+ trait :as_change do
18
+ type { :change }
19
+ end
20
+
21
+ trait :with_account do
22
+ proprietor { { account_id: BSON::ObjectId.new } }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Obscured
2
+ class Account
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include Mongoid::Timeline::Tracker
6
+
7
+ store_in collection: "accounts"
8
+
9
+ field :email, type: String
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Mongoid
2
+ module Timeline
3
+ module Service
4
+ class Account
5
+ include Mongoid::Timeline::Service::Base
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module Obscured
2
+ class Organization
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include Mongoid::Timeline::Tracker
6
+
7
+ store_in collection: "organizations"
8
+
9
+ field :name, type: String
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Pull in test utilities
4
+ require 'simplecov'
5
+ require 'factory_bot'
6
+ require 'pp'
7
+ require 'rspec'
8
+
9
+ # pull in the code
10
+ require_relative '../lib/obscured-timeline'
11
+
12
+ Mongoid.load!(File.join(File.dirname(__FILE__), '/config/mongoid.yml'), 'spec')
13
+ Mongo::Logger.logger.level = Logger::ERROR
14
+
15
+ RSpec.configure do |c|
16
+ c.order = :random
17
+ c.filter_run :focus
18
+ c.run_all_when_everything_filtered = true
19
+
20
+ c.include FactoryBot::Syntax::Methods
21
+
22
+ c.before(:suite) do
23
+ FactoryBot.find_definitions
24
+ Mongoid.purge!
25
+
26
+ Mongoid::Search.setup do |cfg|
27
+ cfg.strip_symbols = /["]/
28
+ cfg.strip_accents = //
29
+ end
30
+ end
31
+
32
+ c.before(:each) do
33
+ Mongoid.purge!
34
+ end
35
+
36
+ c.after(:suite) do
37
+ Mongoid.purge!
38
+ end
39
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'setup'
4
+ require_relative 'helpers/account_document'
5
+ require_relative 'helpers/organization_document'
6
+
7
+
8
+ describe Mongoid::Timeline::Tracker do
9
+ let!(:account_email) { 'homer.simpson@obscured.se' }
10
+ let!(:message) { 'Praesent a massa dui. Etiam eget purus consequat, mollis erat et, rhoncus tortor.' }
11
+ let(:account) { Obscured::Account.new(email: account_email) }
12
+ let(:organization) { Obscured::Organization.new(name: 'adeprimose') }
13
+
14
+ describe 'write event' do
15
+ context 'validates that that events is written to correct collection' do
16
+ before(:each) do
17
+ account.save!
18
+ organization.save
19
+ end
20
+ let!(:account_event) { account.add_event(type: :comment, message: message, producer: account.email) }
21
+ let!(:organization_event) { organization.add_event(type: :change, message: message, producer: account.email) }
22
+
23
+ context 'for account' do
24
+ it { expect(account_event.type).to eq(:comment) }
25
+ it { expect(account_event.message).to eq(message) }
26
+ it { expect(account_event.proprietor).to eq(account_id: account.id) }
27
+
28
+ context 'get event' do
29
+ let!(:event) { account.add_event(type: :comment, message: message, producer: account.email) }
30
+ let!(:response) { account.get_event(event.id) }
31
+
32
+ it { expect(response.id).to eq(event.id) }
33
+ it { expect(response.type).to eq(event.type) }
34
+ it { expect(response.message).to eq(event.message) }
35
+ end
36
+
37
+ context 'get events' do
38
+ let!(:response) { account.get_events }
39
+
40
+ it { expect(response.count).to eq(1) }
41
+ end
42
+
43
+ context 'find events' do
44
+ let!(:event) { account.add_event(type: :comment, message: message, producer: account.email) }
45
+ let!(:event2) { account.add_event(type: :foobar, message: message, producer: account.email) }
46
+ let!(:event3) { account.add_event(type: :foobar, message: message, producer: account.email) }
47
+ let!(:response) { account.find_events({ type: :foobar }, { }) }
48
+
49
+ it { expect(response.count).to eq(2) }
50
+ end
51
+
52
+ context 'search events' do
53
+ before(:each) do
54
+ 10.times do
55
+ account.add_event(type: :comment, message: message, producer: account.email)
56
+ end
57
+ end
58
+
59
+ let!(:event) { account.add_event(type: :comment, message: message, producer: account.email) }
60
+ let!(:event2) { account.add_event(type: :comment, message: message, producer: account.email) }
61
+ let!(:response) { account.search_events(account.email, limit: 5) }
62
+
63
+ it { expect(response.count).to eq(5) }
64
+ end
65
+ end
66
+
67
+ context 'for organization' do
68
+ it { expect(organization_event.type).to eq(:change) }
69
+ it { expect(organization_event.message).to eq(message) }
70
+ it { expect(organization_event.proprietor).to eq(organization_id: organization.id) }
71
+
72
+ context 'get event' do
73
+ let!(:event) { organization.add_event(type: :comment, message: message, producer: organization.id) }
74
+ let!(:response) { organization.get_event(event.id) }
75
+
76
+ it { expect(response.id).to eq(event.id) }
77
+ it { expect(response.type).to eq(event.type) }
78
+ it { expect(response.message).to eq(event.message) }
79
+ end
80
+
81
+ context 'get events' do
82
+ let!(:response) { organization.get_events }
83
+
84
+ it { expect(response.count).to eq(1) }
85
+ end
86
+
87
+ context 'find events' do
88
+ let!(:event) { organization.add_event(type: :comment, message: message, producer: organization.id) }
89
+ let!(:event2) { organization.add_event(type: :foobar, message: message, producer: organization.id) }
90
+ let!(:event3) { organization.add_event(type: :foobar, message: message, producer: organization.id) }
91
+ let!(:response) { account.find_events({ type: :comment }, limit: 1) }
92
+
93
+ it { expect(response.count).to eq(1) }
94
+ end
95
+
96
+ context 'search events' do
97
+ before(:each) do
98
+ 10.times do
99
+ organization.add_event(type: :comment, message: message, producer: organization.id)
100
+ end
101
+ end
102
+ let!(:response) { organization.search_events(organization.id, limit: 5) }
103
+
104
+ it { expect(response.count).to eq(5) }
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'setup'
4
+ require_relative 'helpers/account_document'
5
+
6
+
7
+ describe Mongoid::Timeline::Tracker do
8
+ let!(:account_email) { 'homer.simpson@obscured.se' }
9
+ let!(:type) { :comment }
10
+ let!(:message) { 'Praesent a massa dui. Etiam eget purus consequat, mollis erat et, rhoncus tortor.' }
11
+ let(:account) { Obscured::Account.new(email: account_email) }
12
+
13
+ describe 'event' do
14
+ before(:each) do
15
+ account.save!
16
+ end
17
+
18
+ context 'add event' do
19
+ let!(:event) { account.add_event(type: type, message: message, producer: account.id) }
20
+
21
+ it { expect(event.type).to eq(type) }
22
+ it { expect(event.message).to eq(message) }
23
+ it { expect(event.proprietor).to eq(account_id: account.id) }
24
+ end
25
+
26
+ context 'get event' do
27
+ let!(:event) { account.add_event(type: type, message: message, producer: account.id) }
28
+ let(:response) { account.get_event(event.id) }
29
+
30
+ it { expect(response.id).to eq(event.id) }
31
+ it { expect(response.type).to eq(event.type) }
32
+ it { expect(response.message).to eq(event.message) }
33
+ end
34
+
35
+ context 'get events' do
36
+ let!(:event) { account.add_event(type: type, message: message, producer: account.id) }
37
+ let!(:event2) { account.add_event(type: type, message: message, producer: account.id) }
38
+ let(:response) { account.get_events }
39
+
40
+ it { expect(response.count).to eq(2) }
41
+ end
42
+
43
+ context 'find events' do
44
+ let(:response) { account.find_events({ type: :comment }, { }) }
45
+
46
+ before(:each) do
47
+ account.add_event(type: :comment, message: message, producer: account.id)
48
+ account.add_event(type: :comment, message: message, producer: account.id)
49
+ account.add_event(type: :foobar, message: message, producer: account.id)
50
+ end
51
+
52
+ it { expect(response.count).to eq(2) }
53
+ end
54
+
55
+ context 'search events' do
56
+ before(:each) do
57
+ 5.times do
58
+ account.add_event(type: :payment, message: message, producer: account.id)
59
+ end
60
+
61
+ 10.times do
62
+ account.add_event(type: :comment, message: message, producer: account.id)
63
+ end
64
+ end
65
+
66
+ context 'with limit' do
67
+ let(:response) { account.search_events(account.id, limit: 5) }
68
+
69
+ it { expect(response.count).to eq(5) }
70
+ end
71
+
72
+ context 'with limit and type' do
73
+ let(:response) { account.search_events(account.id, limit: 10, type: :payment) }
74
+
75
+ it { expect(response.count).to eq(10) }
76
+ end
77
+ end
78
+
79
+ context 'edit event' do
80
+ let!(:event) { account.add_event(type: type, message: message, producer: account.id) }
81
+
82
+ context 'updates message for the event' do
83
+ before(:each) do
84
+ account.edit_event(event.id, message: 'This is is a new message')
85
+ end
86
+
87
+ it { expect(account.get_event(event.id).message).to eq('This is is a new message') }
88
+ end
89
+ end
90
+
91
+ context 'delete event' do
92
+ let!(:event) { account.add_event(type: type, message: message, producer: account.id) }
93
+
94
+ context 'deletes the event' do
95
+ before(:each) do
96
+ account.delete_event(event.id)
97
+ end
98
+
99
+ it { expect(account.get_event(event.id)).to be_nil }
100
+ end
101
+ end
102
+
103
+ context 'clear events' do
104
+ before(:each) do
105
+ 2.times do
106
+ account.add_event(type: :event, message: message, producer: account.email)
107
+ end
108
+ 5.times do
109
+ account.add_event(type: :comment, message: message, producer: account.email)
110
+ end
111
+ 5.times do
112
+ account.add_event(type: :change, message: message, producer: account.email)
113
+ end
114
+ end
115
+
116
+ it { expect(account.get_events.count).to be(12) }
117
+
118
+ context 'clear all event' do
119
+ before(:each) do
120
+ account.clear_events
121
+ end
122
+
123
+ it { expect(account.get_events.count).to be(0) }
124
+ end
125
+ end
126
+ end
127
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: obscured-timeline
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Erik Hennerfors
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-03-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
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: mongoid
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: mongoid_search
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: factory_bot
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: simplecov
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
+ description: Mongoid extension to handles a timeline of events for an entity (e.g.
98
+ User)
99
+ email:
100
+ - erik.hennerfors@obscured.se
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".codeclimate.yml"
106
+ - ".gitignore"
107
+ - ".rubocop.yml"
108
+ - ".ruby-gemset"
109
+ - ".ruby-version"
110
+ - ".simplecov"
111
+ - ".travis.yml"
112
+ - Gemfile
113
+ - Gemfile.lock
114
+ - README.md
115
+ - lib/obscured-timeline.rb
116
+ - lib/obscured-timeline/record.rb
117
+ - lib/obscured-timeline/service.rb
118
+ - lib/obscured-timeline/tracker.rb
119
+ - lib/obscured-timeline/version.rb
120
+ - obscured-timeline.gemspec
121
+ - spec/account_service_spec.rb
122
+ - spec/config/mongoid.yml
123
+ - spec/factories/event_factory.rb
124
+ - spec/helpers/account_document.rb
125
+ - spec/helpers/account_service.rb
126
+ - spec/helpers/organization_document.rb
127
+ - spec/setup.rb
128
+ - spec/tracker_multiple_spec.rb
129
+ - spec/tracker_single_spec.rb
130
+ homepage: https://github.com/gonace/Obscured.Timeline
131
+ licenses:
132
+ - MIT
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '2'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 3.0.3
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Mongoid extension that adds the ability to handle events in a timeline for
153
+ a entity (e.g. User)
154
+ test_files:
155
+ - spec/account_service_spec.rb
156
+ - spec/config/mongoid.yml
157
+ - spec/factories/event_factory.rb
158
+ - spec/helpers/account_document.rb
159
+ - spec/helpers/account_service.rb
160
+ - spec/helpers/organization_document.rb
161
+ - spec/setup.rb
162
+ - spec/tracker_multiple_spec.rb
163
+ - spec/tracker_single_spec.rb