stance 0.1.0 → 0.5.1

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: 593f0e612e270f03cdacbe2338c181287094558be03d721588db3ab54b094f52
4
- data.tar.gz: 20fb411cd404fa4762e195c44a4a730670ea07862499d50b12f855b41d97cc22
3
+ metadata.gz: 6b55abfe34c74dfe170cc5adda3b174c120bc7b953f174c815350145954697de
4
+ data.tar.gz: 0b407ba5c2fc0f5784ef3489f676f0894d8116211a097096cba8a4c41030c785
5
5
  SHA512:
6
- metadata.gz: cdd07c0676ff44d4ebf2137747d573992c7dc7015c00b3520452aa2478aad46fc4bbf94b8c9d22cd90380a10dad9be96ab091945e1842e3f0529c2d7f70641ab
7
- data.tar.gz: 1b1939a3e0b0686172b4a0edf7fa91cea2fac0a5c7257a18f7003f117bd43674d772692643f9d8bfdc96776a8f00eb8c451b6451a9c911e18673e591942245ce
6
+ metadata.gz: ff33b9da2fd7cf166f7ecb316c1afe04cc61ffefd140a1c4b8164443258673e2f6c440757e90706700cfd7b2b07a830ac34a8c9f039394308f214e5a246922c3
7
+ data.tar.gz: 587bdd935ea27aaff3df1201691c21d7340e1fea876c7857c94d17f94af86013816986cd4efbfcefcfc87ac3355fd510c2ea3c1c2e74d02765eeac447cfde9c2
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ gem 'combustion'
10
10
  gem 'minitest'
11
11
  gem 'minitest-autotest'
12
12
  gem 'minitest-focus'
13
- gem 'rake', '~> 12.0'
13
+ gem 'mocha'
14
+ gem 'rake', '~> 13.0'
14
15
  gem 'rubocop'
15
16
  gem 'sqlite3'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stance (0.1.0)
4
+ stance (0.5.0)
5
5
  activerecord (>= 5)
6
6
  multi_json
7
7
  railties (>= 5)
@@ -9,48 +9,48 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- actionpack (6.0.3.2)
13
- actionview (= 6.0.3.2)
14
- activesupport (= 6.0.3.2)
15
- rack (~> 2.0, >= 2.0.8)
12
+ actionpack (6.1.3.1)
13
+ actionview (= 6.1.3.1)
14
+ activesupport (= 6.1.3.1)
15
+ rack (~> 2.0, >= 2.0.9)
16
16
  rack-test (>= 0.6.3)
17
17
  rails-dom-testing (~> 2.0)
18
18
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
19
- actionview (6.0.3.2)
20
- activesupport (= 6.0.3.2)
19
+ actionview (6.1.3.1)
20
+ activesupport (= 6.1.3.1)
21
21
  builder (~> 3.1)
22
22
  erubi (~> 1.4)
23
23
  rails-dom-testing (~> 2.0)
24
24
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
25
- activemodel (6.0.3.2)
26
- activesupport (= 6.0.3.2)
27
- activerecord (6.0.3.2)
28
- activemodel (= 6.0.3.2)
29
- activesupport (= 6.0.3.2)
30
- activesupport (6.0.3.2)
25
+ activemodel (6.1.3.1)
26
+ activesupport (= 6.1.3.1)
27
+ activerecord (6.1.3.1)
28
+ activemodel (= 6.1.3.1)
29
+ activesupport (= 6.1.3.1)
30
+ activesupport (6.1.3.1)
31
31
  concurrent-ruby (~> 1.0, >= 1.0.2)
32
- i18n (>= 0.7, < 2)
33
- minitest (~> 5.1)
34
- tzinfo (~> 1.1)
35
- zeitwerk (~> 2.2, >= 2.2.2)
36
- ast (2.4.1)
32
+ i18n (>= 1.6, < 2)
33
+ minitest (>= 5.1)
34
+ tzinfo (~> 2.0)
35
+ zeitwerk (~> 2.3)
36
+ ast (2.4.2)
37
37
  autotest-suffix (1.1.0)
38
38
  builder (3.2.4)
39
- combustion (1.3.0)
39
+ combustion (1.3.1)
40
40
  activesupport (>= 3.0.0)
41
41
  railties (>= 3.0.0)
42
42
  thor (>= 0.14.6)
43
- concurrent-ruby (1.1.6)
43
+ concurrent-ruby (1.1.8)
44
44
  crass (1.0.6)
45
- erubi (1.9.0)
46
- i18n (1.8.3)
45
+ erubi (1.10.0)
46
+ i18n (1.8.10)
47
47
  concurrent-ruby (~> 1.0)
48
- loofah (2.6.0)
48
+ loofah (2.9.1)
49
49
  crass (~> 1.0.2)
50
50
  nokogiri (>= 1.5.9)
51
51
  method_source (1.0.0)
52
- mini_portile2 (2.4.0)
53
- minitest (5.14.1)
52
+ mini_portile2 (2.5.0)
53
+ minitest (5.14.4)
54
54
  minitest-autotest (1.1.1)
55
55
  minitest-server (~> 1.0)
56
56
  path_expander (~> 1.0)
@@ -58,13 +58,16 @@ GEM
58
58
  minitest (>= 4, < 6)
59
59
  minitest-server (1.0.6)
60
60
  minitest (~> 5.0)
61
- multi_json (1.14.1)
62
- nokogiri (1.10.9)
63
- mini_portile2 (~> 2.4.0)
64
- parallel (1.19.2)
65
- parser (2.7.1.4)
61
+ mocha (1.12.0)
62
+ multi_json (1.15.0)
63
+ nokogiri (1.11.3)
64
+ mini_portile2 (~> 2.5.0)
65
+ racc (~> 1.4)
66
+ parallel (1.20.1)
67
+ parser (3.0.1.0)
66
68
  ast (~> 2.4.1)
67
69
  path_expander (1.1.0)
70
+ racc (1.5.2)
68
71
  rack (2.2.3)
69
72
  rack-test (1.1.0)
70
73
  rack (>= 1.0, < 3)
@@ -73,35 +76,34 @@ GEM
73
76
  nokogiri (>= 1.6)
74
77
  rails-html-sanitizer (1.3.0)
75
78
  loofah (~> 2.3)
76
- railties (6.0.3.2)
77
- actionpack (= 6.0.3.2)
78
- activesupport (= 6.0.3.2)
79
+ railties (6.1.3.1)
80
+ actionpack (= 6.1.3.1)
81
+ activesupport (= 6.1.3.1)
79
82
  method_source
80
83
  rake (>= 0.8.7)
81
- thor (>= 0.20.3, < 2.0)
84
+ thor (~> 1.0)
82
85
  rainbow (3.0.0)
83
- rake (12.3.3)
84
- regexp_parser (1.7.1)
85
- rexml (3.2.4)
86
- rubocop (0.86.0)
86
+ rake (13.0.3)
87
+ regexp_parser (2.1.1)
88
+ rexml (3.2.5)
89
+ rubocop (1.12.1)
87
90
  parallel (~> 1.10)
88
- parser (>= 2.7.0.1)
91
+ parser (>= 3.0.0.0)
89
92
  rainbow (>= 2.2.2, < 4.0)
90
- regexp_parser (>= 1.7)
93
+ regexp_parser (>= 1.8, < 3.0)
91
94
  rexml
92
- rubocop-ast (>= 0.0.3, < 1.0)
95
+ rubocop-ast (>= 1.2.0, < 2.0)
93
96
  ruby-progressbar (~> 1.7)
94
- unicode-display_width (>= 1.4.0, < 2.0)
95
- rubocop-ast (0.0.3)
96
- parser (>= 2.7.0.1)
97
- ruby-progressbar (1.10.1)
97
+ unicode-display_width (>= 1.4.0, < 3.0)
98
+ rubocop-ast (1.4.1)
99
+ parser (>= 2.7.1.5)
100
+ ruby-progressbar (1.11.0)
98
101
  sqlite3 (1.4.2)
99
- thor (1.0.1)
100
- thread_safe (0.3.6)
101
- tzinfo (1.2.7)
102
- thread_safe (~> 0.1)
103
- unicode-display_width (1.7.0)
104
- zeitwerk (2.3.0)
102
+ thor (1.1.0)
103
+ tzinfo (2.0.4)
104
+ concurrent-ruby (~> 1.0)
105
+ unicode-display_width (2.0.0)
106
+ zeitwerk (2.4.2)
105
107
 
106
108
  PLATFORMS
107
109
  ruby
@@ -112,10 +114,11 @@ DEPENDENCIES
112
114
  minitest
113
115
  minitest-autotest
114
116
  minitest-focus
115
- rake (~> 12.0)
117
+ mocha
118
+ rake (~> 13.0)
116
119
  rubocop
117
120
  sqlite3
118
121
  stance!
119
122
 
120
123
  BUNDLED WITH
121
- 2.1.4
124
+ 2.2.9
data/README.md CHANGED
@@ -1,6 +1,14 @@
1
- # Stance - Simple Events for Rails apps
1
+ # Stance - Simple & Explicit Events for Rails apps
2
+
3
+ ## Usage
2
4
 
3
5
  ```ruby
6
+ # Your model
7
+ class Appointment < ActiveRecord::Base
8
+ include Stance::Eventable
9
+ end
10
+
11
+ # Define your events
4
12
  class AppointmentEvents < Stance::Events
5
13
  # Define events.
6
14
  event :my_event
@@ -10,25 +18,62 @@ class AppointmentEvents < Stance::Events
10
18
  event 'offers.create'
11
19
  event 'offers.delete'
12
20
 
13
- # Singleton event: only one event with this name can exist for the same subject.
21
+ # Singleton event: only one active event with this name can exist for the same subject.
14
22
  event :my_event, singleton: true
15
23
 
24
+ # By default, events are recorded in the database, unless you set the `record` option to false,
25
+ event :my_recordless_event, record: false
26
+
27
+ # You can define a class event, which is published on the class instead of the instance.
28
+ event :my_class_event, class: true
29
+
30
+ # Will be called before/after each event in this class. Have access to the event `subject` and
31
+ # `record`.
32
+ before_create :do_something_before
33
+ after_create :do_something_after
34
+
16
35
  # Optionally, create a class for an event.
17
36
  class SomeEvent < Stance::Event
18
- # Return false if you do not want the event to be created.
19
- def callable?
20
- false
21
- end
22
-
23
- def call
24
- # do something when the event is created.
25
- end
37
+ # Define optional callbacks which have access to the `subject` and event `record`.
38
+ before_create :do_something_before
39
+ after_create :do_something_after
26
40
  end
27
41
  end
28
42
 
29
43
  # Publish events from the model
30
44
  Appointment.find(1).publish_event :some_event
31
45
  Appointment.find(1).publish_event 'offers.create'
46
+ Appointment.find(1).publish_event :event_with_metadata, foo: :bah
47
+ Appointment.publish_event :my_class_event
48
+ ```
49
+
50
+ ### ActiveRecord Callbacks
51
+
52
+ Stance comes with a couple of opt-in modules to help ease your Callback spaghetti...
53
+
54
+ ```ruby
55
+ class Appointment < ActiveRecord::Base
56
+ include Stance::Eventable
57
+ include Stance::ActiveRecordCallbacks
58
+ end
59
+
60
+ class AppointmentEvents < Stance::Events
61
+ include Stance::ActiveRecordEvents
62
+ end
63
+
64
+ # Now all your model callbacks will trigger an event of the same name, where any public methods
65
+ # defined will be called.
66
+ class AppointmentEvents::AfterCreate < Stance::Event
67
+ include Stance::ActiveRecordEvents
68
+
69
+ # This method will be called upon the :after_create callback of the Appointment model.
70
+ def do_something;end
71
+
72
+ private
73
+
74
+ # Private methods will not be called by the callback.
75
+ def my_private_method;end
76
+ end
32
77
  ```
33
78
 
34
79
  ## Installation
@@ -36,7 +36,7 @@ module Stance
36
36
  end
37
37
 
38
38
  def event_class_name
39
- @event_class_name ||= "#{subject.model_name.name}Events::#{name.tr('.', '/').classify}"
39
+ @event_class_name ||= "#{subject_type}Events::#{name.tr('.', '/').classify}"
40
40
  end
41
41
 
42
42
  def event_class
data/lib/stance.rb CHANGED
@@ -6,7 +6,19 @@ require 'stance/engine'
6
6
  module Stance
7
7
  class EventNotFound < StandardError; end
8
8
 
9
+ mattr_accessor :disabled_events
10
+ @@disabled_events = []
11
+
12
+ def self.disable(*events)
13
+ disabled_events.concat events
14
+ yield
15
+ ensure
16
+ self.disabled_events -= events
17
+ end
18
+
9
19
  autoload :Events, 'stance/events'
10
20
  autoload :Event, 'stance/event'
11
21
  autoload :Eventable, 'stance/eventable'
22
+ autoload :ActiveRecordCallbacks, 'stance/active_record_callbacks'
23
+ autoload :ActiveRecordEvents, 'stance/active_record_events'
12
24
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stance
4
+ module ActiveRecordCallbacks
5
+ extend ActiveSupport::Concern
6
+
7
+ CALLBACKS = %i[before_validation after_validation before_save before_create
8
+ after_create before_update after_update before_destroy after_destroy after_save
9
+ after_touch after_commit after_save_commit after_create_commit
10
+ after_update_commit after_destroy_commit after_rollback].freeze
11
+
12
+ included do
13
+ CALLBACKS.each do |cb|
14
+ send(cb) { publish_event cb }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stance
4
+ module ActiveRecordEvents
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ with_options record: false do
9
+ Stance::ActiveRecordCallbacks::CALLBACKS.each { |ev| event(ev) }
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/stance/event.rb CHANGED
@@ -2,29 +2,65 @@
2
2
 
3
3
  module Stance
4
4
  class Event
5
+ include ActiveSupport::Callbacks
6
+
7
+ define_callbacks :create
5
8
  attr_reader :record, :options
6
9
 
7
10
  delegate :subject, :name, to: :record
8
11
 
12
+ class << self
13
+ def before_create(*methods, &block)
14
+ set_callback :create, :before, *methods, &block
15
+ end
16
+
17
+ def after_create(*methods, &block)
18
+ set_callback :create, :after, *methods, &block
19
+ end
20
+ end
21
+
9
22
  def initialize(name, subject, metadata, options)
10
- @options = { singleton: false }.merge(options)
11
- @record = Stance::EventRecord.new(name: name, subject: subject, metadata: metadata)
23
+ @options = { singleton: false, record: true, class: false }.merge(options)
24
+
25
+ attrs = { name: name, metadata: metadata }
26
+ if subject.is_a?(String)
27
+ attrs[:subject_type] = subject
28
+ else
29
+ attrs[:subject] = subject
30
+ end
31
+ @record = Stance::EventRecord.new(attrs)
12
32
  end
13
33
 
14
- def valid?
15
- # If event is a singleton, check there is no other active event with the same name. If there
16
- # is, return false.
17
- return false if options[:singleton] && subject.events.active.exists?(name: name)
34
+ def create
35
+ return self if singleton_exists?
18
36
 
19
- callable? && record.save
37
+ Rails.logger.info "Event: #{full_name}"
38
+
39
+ Stance::EventRecord.transaction do
40
+ run_callbacks :create do
41
+ # Call each public method of the Event class if a custom class.
42
+ if self.class.name != 'Stance::Event'
43
+ (public_methods(false) - Stance::Event.instance_methods(false)).each do |method|
44
+ send method
45
+ end
46
+ end
47
+
48
+ record.save if @options[:record]
49
+ end
50
+ end
51
+
52
+ self
20
53
  end
21
54
 
22
- def callable?
23
- true
55
+ def full_name
56
+ "#{record.subject_type.downcase}.#{name}"
24
57
  end
25
58
 
26
- def call
27
- true
59
+ private
60
+
61
+ # Event is a singleton and already exists.
62
+ def singleton_exists?
63
+ options[:singleton] && subject.events.active.exists?(name: name)
28
64
  end
29
65
  end
30
66
  end
@@ -8,6 +8,43 @@ module Stance
8
8
  has_many :events, as: :subject, class_name: 'Stance::EventRecord'
9
9
  end
10
10
 
11
+ class_methods do
12
+ def publish_event(name, metadata = {})
13
+ name = name.to_s
14
+ ensure_event! name
15
+
16
+ # Find the Event class - if any - and call it. Falls back to Stance::Event.
17
+ ev = event_class(name).new(name, model_name.name, metadata, events_class.events[name])
18
+
19
+ return ev if Stance.disabled_events.include?(ev.full_name)
20
+
21
+ events_class.new(ev).run_callbacks(:create) { ev.create }
22
+ end
23
+
24
+ private
25
+
26
+ # Raise EventNotFound if the class event has not been defined.
27
+ def ensure_event!(name)
28
+ return if events_class.events.one? { |event, options| name == event && options[:class] }
29
+
30
+ raise Stance::EventNotFound, "Class event `#{name}` not found"
31
+ end
32
+
33
+ def events_class
34
+ @events_class ||= events_class_name.constantize
35
+ end
36
+
37
+ def events_class_name
38
+ @events_class_name ||= "#{model_name.name}Events"
39
+ end
40
+
41
+ def event_class(name)
42
+ name.constantize
43
+ rescue NameError
44
+ Stance::Event
45
+ end
46
+ end
47
+
11
48
  # Publish an event.
12
49
  #
13
50
  # Creates an EventRecord with the given `name`, `metadata` and self as the 'subject'.
@@ -18,19 +55,16 @@ module Stance
18
55
  ensure_event! name
19
56
 
20
57
  # Find the Event class - if any - and call it. Falls back to Stance::Event.
21
- event_class_name = "#{events_class_name}::#{name.to_s.tr('.', '/').classify}"
22
- ev = event_class(event_class_name).new(name, self, metadata, events_class.events[name])
58
+ ev = event_class(name).new(name, self, metadata, events_class.events[name])
23
59
 
24
- Stance::EventRecord.transaction do
25
- ev.valid? && ev.call
26
- end
27
- end
60
+ return ev if Stance.disabled_events.include?(ev.full_name)
28
61
 
29
- private
62
+ events_class.new(ev).run_callbacks(:create) { ev.create }
63
+ end
30
64
 
31
65
  # Raise EventNotFound if the event has not been defined.
32
66
  def ensure_event!(name)
33
- return if events_class.events.keys.include?(name)
67
+ return if events_class.events.one? { |event, options| name == event && !options[:class] }
34
68
 
35
69
  raise Stance::EventNotFound, "Event `#{name}` not found"
36
70
  end
@@ -44,7 +78,7 @@ module Stance
44
78
  end
45
79
 
46
80
  def event_class(name)
47
- name.constantize
81
+ "#{events_class_name}::#{name.tr('.', '/').classify}".constantize
48
82
  rescue NameError
49
83
  Stance::Event
50
84
  end
data/lib/stance/events.rb CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  module Stance
4
4
  class Events
5
+ include ActiveSupport::Callbacks
6
+
7
+ attr_reader :event
8
+
9
+ define_callbacks :create
10
+
5
11
  class << self
6
12
  attr_reader :events
7
13
 
@@ -9,6 +15,18 @@ module Stance
9
15
  @events ||= {}
10
16
  @events[name.to_s] = options
11
17
  end
18
+
19
+ def before_create(*methods, &block)
20
+ set_callback :create, :before, *methods, &block
21
+ end
22
+
23
+ def after_create(*methods, &block)
24
+ set_callback :create, :after, *methods, &block
25
+ end
26
+ end
27
+
28
+ def initialize(event)
29
+ @event = event
12
30
  end
13
31
  end
14
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stance
4
- VERSION = '0.1.0'
4
+ VERSION = '0.5.1'
5
5
  end
data/stance.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = 'Event System for Rails'
12
12
  spec.homepage = 'https://github.com/joelmoss/stance'
13
13
  spec.license = 'MIT'
14
- spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
14
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
15
15
 
16
16
  spec.metadata['homepage_uri'] = spec.homepage
17
17
  spec.metadata['source_code_uri'] = spec.homepage
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Moss
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-24 00:00:00.000000000 Z
11
+ date: 2021-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -52,7 +52,7 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5'
55
- description:
55
+ description:
56
56
  email:
57
57
  - joel@developwithstyle.com
58
58
  executables: []
@@ -74,6 +74,8 @@ files:
74
74
  - lib/generators/stance/install_generator.rb
75
75
  - lib/generators/stance/templates/migration.rb.tt
76
76
  - lib/stance.rb
77
+ - lib/stance/active_record_callbacks.rb
78
+ - lib/stance/active_record_events.rb
77
79
  - lib/stance/engine.rb
78
80
  - lib/stance/event.rb
79
81
  - lib/stance/eventable.rb
@@ -87,7 +89,7 @@ metadata:
87
89
  homepage_uri: https://github.com/joelmoss/stance
88
90
  source_code_uri: https://github.com/joelmoss/stance
89
91
  changelog_uri: https://github.com/joelmoss/stance/releases
90
- post_install_message:
92
+ post_install_message:
91
93
  rdoc_options: []
92
94
  require_paths:
93
95
  - lib
@@ -95,15 +97,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
97
  requirements:
96
98
  - - ">="
97
99
  - !ruby/object:Gem::Version
98
- version: 2.3.0
100
+ version: 2.7.0
99
101
  required_rubygems_version: !ruby/object:Gem::Requirement
100
102
  requirements:
101
103
  - - ">="
102
104
  - !ruby/object:Gem::Version
103
105
  version: '0'
104
106
  requirements: []
105
- rubygems_version: 3.1.2
106
- signing_key:
107
+ rubygems_version: 3.2.3
108
+ signing_key:
107
109
  specification_version: 4
108
110
  summary: Event System for Rails
109
111
  test_files: []