acts_as_living 0.1.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: 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: []