acts_as_living 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: de0cdd11d713c73fa97d94da8f0812ffbeeb5bf7b724c86a8f6d1fa01cf79ebc
4
+ data.tar.gz: fa9f2638f6e04047cbb7954792d1acdfb375939b65ede0af9f97055b31eef6f7
5
+ SHA512:
6
+ metadata.gz: f944327ad557443227149617a1ecc5fc79bd78a3715d96e1db986e71d782c7d1b110e28ff88023cbea0b86d6f6eb4e079ae062d4a95b3427b827b5cdf73b0e2a
7
+ data.tar.gz: 3d96447eec1760afd73458b059d5659c58b6020edd38cd45295db1558463d6c6dc5b98484ccae61ace9b34c6d8cf5ed4c8c590d02670bc18d7566158c39ccec4
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2020-07-01
10
+ ### Added
11
+ - First Release with 2 included flight routes: Email and SMS. (plugged with Twilio and ActionMailer)
12
+ - FlyJob included
@@ -0,0 +1,39 @@
1
+ # Activerecord::ActsAsLiving
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/activerecord/acts_as_living`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'activerecord-acts_as_living'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install activerecord-acts_as_living
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
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).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/activerecord-acts_as_living.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'acts_as_living/version'
5
+ require 'acts_as_living/class_methods'
6
+ require 'acts_as_living/enum_definer'
7
+ require 'acts_as_living/callbacks_definer'
8
+ require 'acts_as_living/instance_methods_definer'
9
+ require 'acts_as_living/scopes_definer'
10
+ require 'acts_as_living/validations_definer'
11
+
12
+ module ActsAsLiving
13
+ extend ActiveSupport::Autoload
14
+ end
15
+
16
+ require 'acts_as_living/railtie' if defined?(Rails)
@@ -0,0 +1,136 @@
1
+ # # frozen_string_literal: true
2
+
3
+ # module ActsAsLiving::CallbacksDefiner #:nodoc:
4
+ # CALLBACK_TERMINATOR = if ::ActiveSupport::VERSION::MAJOR >= 5
5
+ # ->(_target, result) { result.call == false }
6
+ # else
7
+ # ->(_target, result) { result == false }
8
+ # end
9
+
10
+ # # defines before, after and around callbaks for each stage of the acts_as_living
11
+ # # e.g. before_cancelled { do_something }
12
+ # # e.g. after_activated :run_method
13
+ # # e.g. after_status_change :run_method
14
+
15
+ # def self.call(klass)
16
+ # klass.class_eval do
17
+ # include ActiveSupport::Callbacks
18
+ # extend ClassMethods
19
+ # include InstanceMethods
20
+
21
+ # callbacks_for(:status_change)
22
+ # callbacks_for(:life_stage_change)
23
+ # status_keys.each(&method(:status_callbacks_for))
24
+ # @life_stages.keys.each(&method(:lifestage_callbacks_for))
25
+ # end
26
+ # end
27
+
28
+ # module InstanceMethods
29
+ # def save(*args)
30
+ # return super(*args) unless valid? && status_changed? || valid? && new_record?
31
+
32
+ # _run_status_change_callbacks do
33
+ # run_callbacks("status_change_to_#{status}") do
34
+ # binding.pry if notice_of_termination_received?
35
+ # life_stage_changed? ? run_life_stage_callbacks { binding.pry; 'hey'; super(*args) } : super(*args)
36
+ # end
37
+ # end
38
+ # end
39
+
40
+ # def save!(*args)
41
+ # return super(*args) unless valid? && status_changed? || valid? && new_record?
42
+
43
+ # _run_status_change_callbacks do
44
+ # run_callbacks("status_change_to_#{status}") do
45
+ # life_stage_changed? ? run_life_stage_callbacks { binding.pry; 'hey'; super(*args) } : super(*args)
46
+ # end
47
+ # end
48
+ # end
49
+
50
+ # protected
51
+
52
+ # def run_life_stage_callbacks(&block)
53
+ # _run_life_stage_change_callbacks do
54
+ # _run_life_stage_started_callbacks do
55
+ # _run_life_stage_ended_callbacks(&block)
56
+ # end
57
+ # end
58
+ # end
59
+
60
+ # def _run_life_stage_started_callbacks(&block)
61
+ # life_stages_started.inject(block) do |blk, stage|
62
+ # _run_stage_started_callbacks(stage, &blk)
63
+ # end
64
+ # end
65
+
66
+ # def _run_life_stage_ended_callbacks(&block)
67
+ # life_stages_ended.inject(block) do |blk, stage|
68
+ # _run_stage_ended_callbacks(stage, &blk)
69
+ # end
70
+ # end
71
+
72
+ # def _run_stage_started_callbacks(stage, &block)
73
+ # run_callbacks("#{stage}_started".to_sym, &block)
74
+ # end
75
+
76
+ # def _run_stage_ended_callbacks(stage, &block)
77
+ # run_callbacks("#{stage}_ended".to_sym, &block)
78
+ # end
79
+ # end
80
+
81
+ # module ClassMethods
82
+ # def lifestage_callbacks_for(stage)
83
+ # define_callback_methods_for("#{stage}_started".to_sym)
84
+ # define_callback_methods_for("#{stage}_ended".to_sym)
85
+ # end
86
+
87
+ # def callbacks_for(callback_name)
88
+ # define_callback_methods_for(callback_name)
89
+ # end
90
+
91
+ # def status_callbacks_for(status_name)
92
+ # define_callback_methods_for("status_change_to_#{status_name}")
93
+ # end
94
+
95
+ # def _normalize_callback_options(options)
96
+ # _normalize_callback_option(options, :only, :if)
97
+ # _normalize_callback_option(options, :except, :unless)
98
+ # end
99
+
100
+ # def _normalize_callback_option(options, from, to)
101
+ # return unless (from = options[from])
102
+
103
+ # from_set = Array(from).map(&:to_s).to_set
104
+ # from = proc { |c| from_set.include? c.notification_name.to_s }
105
+ # options[to] = Array(options[to]).unshift(from)
106
+ # end
107
+
108
+ # # rubocop:disable Metrics/MethodLength
109
+ # def define_callback_methods_for(callback_name)
110
+ # define_callbacks(
111
+ # callback_name,
112
+ # terminator: CALLBACK_TERMINATOR,
113
+ # skip_after_callbacks_if_terminated: true
114
+ # )
115
+
116
+ # define_singleton_method("before_#{callback_name}") do |method_or_block = nil, **options, &block|
117
+ # method_or_block ||= block
118
+ # _normalize_callback_options(options)
119
+ # set_callback callback_name, :before, method_or_block, options
120
+ # end
121
+
122
+ # define_singleton_method("after_#{callback_name}") do |method_or_block = nil, **options, &block|
123
+ # method_or_block ||= block
124
+ # _normalize_callback_options(options)
125
+ # set_callback callback_name, :after, method_or_block, options
126
+ # end
127
+
128
+ # define_singleton_method("around_#{callback_name}") do |method_or_block = nil, **options, &block|
129
+ # method_or_block ||= block
130
+ # _normalize_callback_options(options)
131
+ # set_callback callback_name, :around, method_or_block, options
132
+ # end
133
+ # end
134
+ # # rubocop:enable Metrics/MethodLength
135
+ # end
136
+ # end
@@ -0,0 +1,97 @@
1
+
2
+ module ActsAsLiving::ClassMethods
3
+ # validates :status, presence: true
4
+
5
+ def acts_as_living(keys, **options)
6
+ @status_keys = keys
7
+ @life_stages = options.dig(:life_stages)
8
+ @locked_statuses = options.dig(:lock_on)
9
+ @death = options.dig(:death)
10
+ @spread = options.dig(:spread)
11
+ @column = options.dig(:column)
12
+
13
+ ActsAsLiving::EnumDefiner.call(self)
14
+
15
+ run_definers
16
+ end
17
+
18
+ def run_definers
19
+ ActsAsLiving::ScopesDefiner.call(self)
20
+ ActsAsLiving::InstanceMethodsDefiner.call(self)
21
+ # ActsAsLiving::CallbacksDefiner.call(self)
22
+ ActsAsLiving::ValidationsDefiner.call(self)
23
+ end
24
+
25
+ def alive_statuses
26
+ statuses.except(@death).keys
27
+ end
28
+
29
+ def status_keys
30
+ statuses.keys
31
+ end
32
+
33
+ def statuses_after(status)
34
+ return [] if final_status?(status)
35
+
36
+ statuses[status_after(status)..]
37
+ end
38
+
39
+ def statuses_before(status)
40
+ return [] if initial_status?(status)
41
+
42
+ index = status_keys.find_index(status)
43
+ status_keys[0...index]
44
+ end
45
+
46
+ def status_after(status)
47
+ return if final_status?(status)
48
+
49
+ index = status_keys.find_index(status)
50
+ status_keys[index + 1]
51
+ end
52
+
53
+ def status_before(status)
54
+ return if initial_status?(status)
55
+
56
+ index = status_keys.find_index(status)
57
+ status_keys[index - 1]
58
+ end
59
+
60
+ def final_status
61
+ status_keys.last
62
+ end
63
+
64
+ def final_status?(status)
65
+ final_status == status
66
+ end
67
+
68
+ def initial_status
69
+ statuses.key(0)
70
+ end
71
+
72
+ def dead_status
73
+ @death
74
+ end
75
+
76
+ def initial_status?(status)
77
+ initial_status == status
78
+ end
79
+
80
+ def stages_with_ranges
81
+ @life_stages.map(&method(:to_stage_with_range)).to_h
82
+ end
83
+
84
+ def to_stage_with_range(stage, delimiter)
85
+ [stage, (statuses[delimiter.first]..statuses[delimiter.last])]
86
+ end
87
+
88
+ def life_stages
89
+ @life_stages
90
+ end
91
+
92
+ def life_stages_for(status)
93
+ stages_with_ranges.keys.select do |stage|
94
+ stages_with_ranges[stage].include? statuses[status]
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsLiving::EnumDefiner
4
+ def self.call(klass)
5
+ klass.class_eval do
6
+ extend ClassMethods
7
+
8
+ enum @column => enum_options
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def enum_options
14
+ statuses = @status_keys.map.with_index(&method(:to_enum_map)).to_h
15
+ statuses.merge(@death => @spread * -1)
16
+ end
17
+
18
+ def to_enum_map(status, index)
19
+ [status, index * @spread]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsLiving::InstanceMethodsDefiner
4
+ def self.call(klass)
5
+ klass.class_eval do
6
+ extend ClassMethods
7
+ include InstanceMethods
8
+
9
+ @life_stages.each(&method(:define_stage_queries))
10
+
11
+ @status_keys.each(&method(:define_changed_queries))
12
+ end
13
+ end
14
+
15
+ module InstanceMethods
16
+ def status_after(status)
17
+ self.class.status_after(status)
18
+ end
19
+
20
+ def statuses
21
+ self.class.statuses
22
+ end
23
+
24
+ def status_before(status)
25
+ self.class.status_before(status)
26
+ end
27
+
28
+ def to_next_status
29
+ update(status: next_status)
30
+ end
31
+
32
+ def to_next_status!
33
+ update!(status: next_status)
34
+ end
35
+
36
+ def next_status
37
+ status_after(status) if status
38
+ end
39
+
40
+ def to_previous_status
41
+ update(status: previous_status)
42
+ end
43
+
44
+ def to_previous_status!
45
+ update!(status: previous_status)
46
+ end
47
+
48
+ def previous_status
49
+ status_before(status)
50
+ end
51
+
52
+ def final_status?
53
+ self.class.final_status?(status)
54
+ end
55
+
56
+ def initial_status?
57
+ self.class.initial_status?(status)
58
+ end
59
+
60
+ def dead_status?
61
+ self.class.dead_status == status
62
+ end
63
+
64
+ def dead_or_finalized?
65
+ dead_status? || final_status?
66
+ end
67
+
68
+ def klass_stages_with_ranges
69
+ self.class.stages_with_ranges
70
+ end
71
+
72
+ def klass_life_stages_for(status)
73
+ self.class.life_stages_for(status)
74
+ end
75
+
76
+ def klass_statuses
77
+ self.class.statuses
78
+ end
79
+
80
+ def locked?(&block)
81
+ return unless block
82
+
83
+ @locked_on.to_set.intersect? [status, status_was].to_set
84
+ end
85
+
86
+ def life_stages
87
+ klass_life_stages_for(status)
88
+ end
89
+
90
+ def life_stage_changed?
91
+ klass_life_stages_for(status) != klass_life_stages_for(status_was)
92
+ end
93
+
94
+ def life_stages_started
95
+ klass_life_stages_for(status) - klass_life_stages_for(status_was)
96
+ end
97
+
98
+ def life_stages_ended
99
+ klass_life_stages_for(status_was) - klass_life_stages_for(status)
100
+ end
101
+ end
102
+
103
+ module ClassMethods
104
+ def define_stage_queries(stage, delimiters)
105
+ define_method("#{stage}?") do
106
+ if delimiters.length == 1
107
+ klass_statuses[status] == klass_statuses[delimiters]
108
+ else
109
+ klass_statuses[status] >= klass_statuses[delimiters.first] &&
110
+ klass_statuses[status] <= klass_statuses[delimiters.second]
111
+ end
112
+ end
113
+
114
+ define_method("pre_#{stage}?") do
115
+ klass_statuses[status] < klass_statuses[delimiters.first] unless cancelled?
116
+ end
117
+
118
+ define_method("past_#{stage}?") do
119
+ klass_statuses[status] > klass_statuses[delimiters.last] || cancelled?
120
+ end
121
+ end
122
+
123
+ def define_changed_queries(status_key)
124
+ define_method("status_changed_to_#{status_key}?") do
125
+ status_changed? && status_was == status_key
126
+ end
127
+
128
+ define_method("pre_#{status_key}?") do
129
+ klass_statuses[status] < klass_statuses[status_key] unless cancelled?
130
+ end
131
+
132
+ define_method("past_#{status_key}?") do
133
+ klass_statuses[status] > klass_statuses[status_key] || cancelled?
134
+ end
135
+
136
+ define_method("cancelled_from_#{status_key}?") do
137
+ status == 'cancelled' && status_was == status_key
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'rails/railtie'
5
+
6
+ module ActsAsLiving
7
+ class Railtie < Rails::Railtie
8
+ config.to_prepare do
9
+ ActiveSupport.on_load(:active_record) do
10
+ extend ActsAsLiving::ClassMethods
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ module ActsAsLiving::ScopesDefiner
2
+ def self.call(klass)
3
+ klass.class_eval do
4
+ statuses.each do |status, _num|
5
+ scope "past_#{status}", -> { where('status >= ?', statuses[status]) }
6
+ scope "pre_#{status}", -> { where('status < ?', statuses[status]) }
7
+ scope "not_#{status}", -> { where.not(status: status) }
8
+ end
9
+
10
+ scope :cancelled, -> { where('status < 0') }
11
+
12
+ @life_stages.each do |stage, delimiters|
13
+ if delimiters.length == 1
14
+ scope stage, -> { where(status: delimiters.first) }
15
+ else
16
+ scope stage, lambda {
17
+ where('status >= ? AND status <= ?', statuses[delimiters.first], statuses[delimiters.second])
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ module ActsAsLiving::ValidationsDefiner
2
+ def self.call(klass)
3
+ klass.class_eval do
4
+ include InstanceMethods
5
+
6
+ validates :status, presence: true
7
+
8
+ validate :status_progression, on: :update, if: :status_changed?
9
+ validate :initialized_status, on: :create, if: :status_changed?
10
+ end
11
+ end
12
+
13
+ module InstanceMethods
14
+ def status_progression
15
+ return if status.to_s == self.class.dead_status.to_s || status == status_after(status_was)
16
+
17
+ message = if status_was == self.class.final_status
18
+ "The contract can only be updated to '#{self.class.dead_status}'"
19
+ else
20
+ "The contract can only be updated to '#{self.class.dead_status}' or '#{status_after(status_was)}'"
21
+ end
22
+
23
+ errors.add(:status, message)
24
+ end
25
+
26
+ def initialized_status
27
+ return if status == self.class.initial_status
28
+
29
+ errors.add(:status, "Contract has to be initialized with '#{self.class.initial_status}' status")
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsLiving
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_living
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Guilherme Andrade
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-07-01 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: '6.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '6.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.3
33
+ - !ruby/object:Gem::Dependency
34
+ name: activesupport
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '6.0'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 6.0.3
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '6.0'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 6.0.3
53
+ - !ruby/object:Gem::Dependency
54
+ name: railties
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '6.0'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 6.0.3
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '6.0'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 6.0.3
73
+ description: An ActiveRecord plugin that assists in acts_as_living status progressions.
74
+ email:
75
+ - guilherme.andrade.ao@gmail.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - Changelog.md
81
+ - README.md
82
+ - lib/acts_as_living.rb
83
+ - lib/acts_as_living/callbacks_definer.rb
84
+ - lib/acts_as_living/class_methods.rb
85
+ - lib/acts_as_living/enum_definer.rb
86
+ - lib/acts_as_living/instance_methods_definer.rb
87
+ - lib/acts_as_living/railtie.rb
88
+ - lib/acts_as_living/scopes_definer.rb
89
+ - lib/acts_as_living/validations_definer.rb
90
+ - lib/acts_as_living/version.rb
91
+ homepage: https://github.com/guilherme-andrade/acts_as_living
92
+ licenses:
93
+ - MIT
94
+ metadata:
95
+ homepage_uri: https://github.com/guilherme-andrade/acts_as_living
96
+ source_code_uri: https://github.com/guilherme-andrade/acts_as_living
97
+ changelog_uri: https://github.com/guilherme-andrade/acts_as_living/blob/master/Changelog.md
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubygems_version: 3.0.3
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: An ActiveRecord plugin that assists in acts_as_living status progressions.
117
+ test_files: []