opera 0.4.1 → 0.5.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1223 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +45 -1
- data/README.md +180 -77
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/lib/opera/errors.rb +1 -0
- data/lib/opera/operation/attributes_dsl.rb +16 -7
- data/lib/opera/operation/base.rb +7 -6
- data/lib/opera/operation/builder.rb +2 -2
- data/lib/opera/operation/config.rb +1 -1
- data/lib/opera/operation/executor.rb +3 -3
- data/lib/opera/operation/instructions/executors/operations.rb +1 -1
- data/lib/opera/operation/instructions/executors/within.rb +23 -0
- data/lib/opera/operation.rb +1 -1
- data/lib/opera/version.rb +1 -1
- data/opera.gemspec +12 -10
- metadata +4 -3
- data/lib/opera/operation/instructions/executors/benchmark.rb +0 -26
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
source 'https://rubygems.org'
|
|
2
4
|
|
|
3
5
|
# Specify your gem's dependencies in opera.gemspec
|
|
@@ -6,6 +8,13 @@ gemspec
|
|
|
6
8
|
gem 'rake', '~> 13.2'
|
|
7
9
|
gem 'rspec', '~> 3.13'
|
|
8
10
|
|
|
11
|
+
group :development do
|
|
12
|
+
gem 'rubocop'
|
|
13
|
+
gem 'rubocop-performance'
|
|
14
|
+
gem 'rubocop-rake'
|
|
15
|
+
gem 'rubocop-rspec'
|
|
16
|
+
end
|
|
17
|
+
|
|
9
18
|
group :test, :development do
|
|
10
19
|
gem 'dry-validation'
|
|
11
20
|
gem 'pry'
|
data/Gemfile.lock
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
opera (0.
|
|
4
|
+
opera (0.5.0)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
|
+
ast (2.4.3)
|
|
9
10
|
bigdecimal (3.1.9)
|
|
10
11
|
byebug (12.0.0)
|
|
11
12
|
coderay (1.1.3)
|
|
@@ -46,15 +47,26 @@ GEM
|
|
|
46
47
|
dry-initializer (~> 3.2)
|
|
47
48
|
dry-schema (~> 1.14)
|
|
48
49
|
zeitwerk (~> 2.6)
|
|
50
|
+
json (2.18.0)
|
|
51
|
+
language_server-protocol (3.17.0.5)
|
|
52
|
+
lint_roller (1.1.0)
|
|
49
53
|
logger (1.7.0)
|
|
50
54
|
method_source (1.1.0)
|
|
55
|
+
parallel (1.27.0)
|
|
56
|
+
parser (3.3.10.0)
|
|
57
|
+
ast (~> 2.4.1)
|
|
58
|
+
racc
|
|
59
|
+
prism (1.7.0)
|
|
51
60
|
pry (0.15.2)
|
|
52
61
|
coderay (~> 1.1)
|
|
53
62
|
method_source (~> 1.0)
|
|
54
63
|
pry-byebug (3.11.0)
|
|
55
64
|
byebug (~> 12.0)
|
|
56
65
|
pry (>= 0.13, < 0.16)
|
|
66
|
+
racc (1.8.1)
|
|
67
|
+
rainbow (3.1.1)
|
|
57
68
|
rake (13.2.1)
|
|
69
|
+
regexp_parser (2.11.3)
|
|
58
70
|
rspec (3.13.0)
|
|
59
71
|
rspec-core (~> 3.13.0)
|
|
60
72
|
rspec-expectations (~> 3.13.0)
|
|
@@ -68,6 +80,34 @@ GEM
|
|
|
68
80
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
69
81
|
rspec-support (~> 3.13.0)
|
|
70
82
|
rspec-support (3.13.3)
|
|
83
|
+
rubocop (1.82.1)
|
|
84
|
+
json (~> 2.3)
|
|
85
|
+
language_server-protocol (~> 3.17.0.2)
|
|
86
|
+
lint_roller (~> 1.1.0)
|
|
87
|
+
parallel (~> 1.10)
|
|
88
|
+
parser (>= 3.3.0.2)
|
|
89
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
90
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
91
|
+
rubocop-ast (>= 1.48.0, < 2.0)
|
|
92
|
+
ruby-progressbar (~> 1.7)
|
|
93
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
94
|
+
rubocop-ast (1.49.0)
|
|
95
|
+
parser (>= 3.3.7.2)
|
|
96
|
+
prism (~> 1.7)
|
|
97
|
+
rubocop-performance (1.26.1)
|
|
98
|
+
lint_roller (~> 1.1)
|
|
99
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
100
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
101
|
+
rubocop-rake (0.7.1)
|
|
102
|
+
lint_roller (~> 1.1)
|
|
103
|
+
rubocop (>= 1.72.1)
|
|
104
|
+
rubocop-rspec (3.8.0)
|
|
105
|
+
lint_roller (~> 1.1)
|
|
106
|
+
rubocop (~> 1.81)
|
|
107
|
+
ruby-progressbar (1.13.0)
|
|
108
|
+
unicode-display_width (3.2.0)
|
|
109
|
+
unicode-emoji (~> 4.1)
|
|
110
|
+
unicode-emoji (4.1.0)
|
|
71
111
|
zeitwerk (2.6.18)
|
|
72
112
|
|
|
73
113
|
PLATFORMS
|
|
@@ -80,6 +120,10 @@ DEPENDENCIES
|
|
|
80
120
|
pry-byebug
|
|
81
121
|
rake (~> 13.2)
|
|
82
122
|
rspec (~> 3.13)
|
|
123
|
+
rubocop
|
|
124
|
+
rubocop-performance
|
|
125
|
+
rubocop-rake
|
|
126
|
+
rubocop-rspec
|
|
83
127
|
|
|
84
128
|
BUNDLED WITH
|
|
85
129
|
2.3.3
|
data/README.md
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
[](https://badge.fury.io/rb/opera)
|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
Simple DSL for services/interactions classes.
|
|
8
7
|
|
|
9
8
|
Opera was born to mimic some of the philosophy of the dry gems but keeping the DSL simple.
|
|
@@ -48,7 +47,6 @@ end
|
|
|
48
47
|
|
|
49
48
|
You can later override this configuration in each Operation to have more granularity
|
|
50
49
|
|
|
51
|
-
|
|
52
50
|
## Usage
|
|
53
51
|
|
|
54
52
|
Once opera gem is in your project you can start to build Operations
|
|
@@ -77,10 +75,6 @@ class A < Opera::Operation::Base
|
|
|
77
75
|
step :validate_relationships
|
|
78
76
|
end
|
|
79
77
|
|
|
80
|
-
benchmark do
|
|
81
|
-
success :hal_sync
|
|
82
|
-
end
|
|
83
|
-
|
|
84
78
|
success do
|
|
85
79
|
step :send_mail
|
|
86
80
|
step :report_to_audit_log
|
|
@@ -92,7 +86,6 @@ end
|
|
|
92
86
|
|
|
93
87
|
Start developing your business logic, services and interactions as Opera::Operations and benefit of code that is documented, self-explanatory, easy to maintain and debug.
|
|
94
88
|
|
|
95
|
-
|
|
96
89
|
### Specs
|
|
97
90
|
|
|
98
91
|
When using Opera::Operation inside an engine add the following
|
|
@@ -105,6 +98,7 @@ end
|
|
|
105
98
|
```
|
|
106
99
|
|
|
107
100
|
Without this extra configuration you will receive:
|
|
101
|
+
|
|
108
102
|
```ruby
|
|
109
103
|
NoMethodError:
|
|
110
104
|
undefined method `transaction' for nil:NilClass
|
|
@@ -137,6 +131,7 @@ end
|
|
|
137
131
|
```
|
|
138
132
|
|
|
139
133
|
### Content
|
|
134
|
+
|
|
140
135
|
[Basic operation](#user-content-basic-operation)
|
|
141
136
|
|
|
142
137
|
[Example with sanitizing parameters](#user-content-example-with-sanitizing-parameters)
|
|
@@ -147,8 +142,6 @@ end
|
|
|
147
142
|
|
|
148
143
|
[Passing transaction](#user-content-passing-transaction)
|
|
149
144
|
|
|
150
|
-
[Benchmark](#user-content-benchmark)
|
|
151
|
-
|
|
152
145
|
[Success](#user-content-success)
|
|
153
146
|
|
|
154
147
|
[Finish if](#user-content-finish-if)
|
|
@@ -157,6 +150,8 @@ end
|
|
|
157
150
|
|
|
158
151
|
[Inner Operations](#user-content-inner-operations)
|
|
159
152
|
|
|
153
|
+
[Within](#user-content-within)
|
|
154
|
+
|
|
160
155
|
## Usage examples
|
|
161
156
|
|
|
162
157
|
Some cases and example how to use new operations
|
|
@@ -594,71 +589,6 @@ D, [2020-08-17T12:10:44.898132 #2741] DEBUG -- : (10.3ms) COMMIT
|
|
|
594
589
|
#<Opera::Operation::Result:0x0000556528f29058 @errors={}, @information={}, @executions=[:profile_schema, :create, :update, :send_email, :output], @output={:model=>#<Profile id: 47, user_id: nil, linkedin_uid: nil, picture: nil, headline: nil, summary: nil, first_name: "foo", last_name: "bar", created_at: "2020-08-17 12:10:44", updated_at: "2020-08-16 12:10:44", agree_to_terms_and_conditions: nil, registration_status: "", account_id: 1, start_date: nil, supervisor_id: nil, picture_processing: false, statistics: {}, data: {}, notification_timestamps: {}, suggestions: {}, notification_settings: {}, contact_information: []>}>
|
|
595
590
|
```
|
|
596
591
|
|
|
597
|
-
### Benchmark
|
|
598
|
-
|
|
599
|
-
```ruby
|
|
600
|
-
class Profile::Create < Opera::Operation::Base
|
|
601
|
-
# DEPRECATED
|
|
602
|
-
# context_accessor :profile
|
|
603
|
-
context do
|
|
604
|
-
attr_accessor :profile
|
|
605
|
-
end
|
|
606
|
-
# DEPRECATED
|
|
607
|
-
# dependencies_reader :current_account, :mailer
|
|
608
|
-
dependencies do
|
|
609
|
-
attr_reader :current_account, :mailer
|
|
610
|
-
end
|
|
611
|
-
|
|
612
|
-
validate :profile_schema
|
|
613
|
-
|
|
614
|
-
benchmark :fast_section do
|
|
615
|
-
step :create
|
|
616
|
-
step :update
|
|
617
|
-
end
|
|
618
|
-
|
|
619
|
-
benchmark :slow_section do
|
|
620
|
-
step :send_email
|
|
621
|
-
step :output
|
|
622
|
-
end
|
|
623
|
-
|
|
624
|
-
def profile_schema
|
|
625
|
-
Dry::Validation.Schema do
|
|
626
|
-
required(:first_name).filled
|
|
627
|
-
end.call(params)
|
|
628
|
-
end
|
|
629
|
-
|
|
630
|
-
def create
|
|
631
|
-
self.profile = current_account.profiles.create(params)
|
|
632
|
-
end
|
|
633
|
-
|
|
634
|
-
def update
|
|
635
|
-
profile.update(updated_at: 1.day.ago)
|
|
636
|
-
end
|
|
637
|
-
|
|
638
|
-
def send_email
|
|
639
|
-
return true unless mailer
|
|
640
|
-
|
|
641
|
-
mailer.send_mail(profile: profile)
|
|
642
|
-
end
|
|
643
|
-
|
|
644
|
-
def output
|
|
645
|
-
result.output = { model: profile }
|
|
646
|
-
end
|
|
647
|
-
end
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
#### Example with information (real and total) from benchmark
|
|
651
|
-
|
|
652
|
-
```ruby
|
|
653
|
-
Profile::Create.call(params: {
|
|
654
|
-
first_name: :foo,
|
|
655
|
-
last_name: :bar
|
|
656
|
-
}, dependencies: {
|
|
657
|
-
current_account: Account.find(1)
|
|
658
|
-
})
|
|
659
|
-
#<Opera::Operation::Result:0x007ff414a01238 @errors={}, @information={fast_section: {:real=>0.300013706088066e-05, :total=>0.0}, slow_section: {:real=>1.800013706088066e-05, :total=>0.0}}, @executions=[:profile_schema, :create, :update, :send_email, :output], @output={:model=>#<Profile id: 30, user_id: nil, linkedin_uid: nil, picture: nil, headline: nil, summary: nil, first_name: "foo", last_name: "bar", created_at: "2020-08-19 10:46:00", updated_at: "2020-08-18 10:46:00", agree_to_terms_and_conditions: nil, registration_status: "", account_id: 1, start_date: nil, supervisor_id: nil, picture_processing: false, statistics: {}, data: {}, notification_timestamps: {}, suggestions: {}, notification_settings: {}, contact_information: []>}>
|
|
660
|
-
```
|
|
661
|
-
|
|
662
592
|
### Success
|
|
663
593
|
|
|
664
594
|
```ruby
|
|
@@ -785,8 +715,6 @@ class Profile::Create < Opera::Operation::Base
|
|
|
785
715
|
end
|
|
786
716
|
```
|
|
787
717
|
|
|
788
|
-
#### Example with information (real and total) from benchmark
|
|
789
|
-
|
|
790
718
|
```ruby
|
|
791
719
|
Profile::Create.call(params: {
|
|
792
720
|
first_name: :foo,
|
|
@@ -851,6 +779,7 @@ Profile::Create.call(params: {
|
|
|
851
779
|
```
|
|
852
780
|
|
|
853
781
|
### Inner Operations
|
|
782
|
+
|
|
854
783
|
Expects that method returns array of `Opera::Operation::Result`
|
|
855
784
|
|
|
856
785
|
```ruby
|
|
@@ -888,6 +817,171 @@ Profile::CreateMultiple.call(params: { number: 3 })
|
|
|
888
817
|
#<Opera::Operation::Result:0x0000564189f38c90 @errors={}, @information={}, @executions=[{:create_multiple=>[[:validate, :create], [:validate, :create], [:validate, :create], [:validate, :create]]}, :output], @output=[{:model=>"Profile 1"}, {:model=>"Profile 7"}, {:model=>"Profile 69"}, {:model=>"Profile 92"}]>
|
|
889
818
|
```
|
|
890
819
|
|
|
820
|
+
### Within
|
|
821
|
+
|
|
822
|
+
`within` wraps one or more steps with a method you define on the operation. The method must `yield` to execute the nested steps. If it does not yield, the nested steps are skipped. Normal break conditions (errors, `finish!`) still apply inside the block.
|
|
823
|
+
|
|
824
|
+
```ruby
|
|
825
|
+
class Profile::Create < Opera::Operation::Base
|
|
826
|
+
context do
|
|
827
|
+
attr_accessor :profile
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
dependencies do
|
|
831
|
+
attr_reader :current_account
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
step :build
|
|
835
|
+
|
|
836
|
+
within :read_from_replica do
|
|
837
|
+
step :check_duplicate
|
|
838
|
+
step :validate_quota
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
step :create
|
|
842
|
+
step :output
|
|
843
|
+
|
|
844
|
+
def build
|
|
845
|
+
self.profile = current_account.profiles.build(params)
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
def check_duplicate
|
|
849
|
+
result.add_error(:base, 'already exists') if Profile.exists?(email: params[:email])
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
def validate_quota
|
|
853
|
+
result.add_error(:base, 'quota exceeded') if current_account.profiles.count >= 100
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
def create
|
|
857
|
+
profile.save!
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
def output
|
|
861
|
+
result.output = { model: profile }
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
private
|
|
865
|
+
|
|
866
|
+
def read_from_replica(&block)
|
|
867
|
+
ActiveRecord::Base.connected_to(role: :reading, &block)
|
|
868
|
+
end
|
|
869
|
+
end
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
`within`-method can also be used inline inside any step method when you need the wrapper for only part of that method's logic:
|
|
873
|
+
|
|
874
|
+
```ruby
|
|
875
|
+
def some_step
|
|
876
|
+
value = read_from_replica { Profile.count }
|
|
877
|
+
result.output = { count: value }
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
private
|
|
881
|
+
|
|
882
|
+
def read_from_replica(&block)
|
|
883
|
+
ActiveRecord::Base.connected_to(role: :reading, &block)
|
|
884
|
+
end
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
#### Mixing step and operation inside within
|
|
888
|
+
|
|
889
|
+
`within` can wrap any combination of `step` and `operation` instructions. All of them execute inside the wrapper, and their outputs are available in context afterwards as usual.
|
|
890
|
+
|
|
891
|
+
```ruby
|
|
892
|
+
class Profile::Create < Opera::Operation::Base
|
|
893
|
+
context do
|
|
894
|
+
attr_accessor :profile
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
dependencies do
|
|
898
|
+
attr_reader :current_account, :quota_checker
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
within :read_from_replica do
|
|
902
|
+
step :check_duplicate
|
|
903
|
+
operation :fetch_quota
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
step :create
|
|
907
|
+
step :output
|
|
908
|
+
|
|
909
|
+
def check_duplicate
|
|
910
|
+
result.add_error(:base, 'already exists') if Profile.exists?(email: params[:email])
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
def fetch_quota
|
|
914
|
+
quota_checker.call(params: params)
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
def create
|
|
918
|
+
self.profile = current_account.profiles.create(params)
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
def output
|
|
922
|
+
result.output = { model: profile, quota: context[:fetch_quota_output] }
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
private
|
|
926
|
+
|
|
927
|
+
def read_from_replica(&block)
|
|
928
|
+
ActiveRecord::Base.connected_to(role: :reading, &block)
|
|
929
|
+
end
|
|
930
|
+
end
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
#### Nesting within inside a transaction
|
|
934
|
+
|
|
935
|
+
`within` can be placed inside a `transaction` block alongside other instructions. If any step or operation inside `within` fails, the error propagates up and the transaction is rolled back as normal.
|
|
936
|
+
|
|
937
|
+
```ruby
|
|
938
|
+
class Profile::Create < Opera::Operation::Base
|
|
939
|
+
configure do |config|
|
|
940
|
+
config.transaction_class = ActiveRecord::Base
|
|
941
|
+
end
|
|
942
|
+
|
|
943
|
+
context do
|
|
944
|
+
attr_accessor :profile
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
dependencies do
|
|
948
|
+
attr_reader :current_account, :quota_checker, :audit_logger
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
transaction do
|
|
952
|
+
within :read_from_replica do
|
|
953
|
+
step :check_duplicate
|
|
954
|
+
operation :fetch_quota
|
|
955
|
+
end
|
|
956
|
+
operation :write_audit_log
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
step :output
|
|
960
|
+
|
|
961
|
+
def check_duplicate
|
|
962
|
+
result.add_error(:base, 'already exists') if Profile.exists?(email: params[:email])
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
def fetch_quota
|
|
966
|
+
quota_checker.call(params: params)
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
def write_audit_log
|
|
970
|
+
audit_logger.call(params: params)
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
def output
|
|
974
|
+
result.output = { quota: context[:fetch_quota_output] }
|
|
975
|
+
end
|
|
976
|
+
|
|
977
|
+
private
|
|
978
|
+
|
|
979
|
+
def read_from_replica(&block)
|
|
980
|
+
ActiveRecord::Base.connected_to(role: :reading, &block)
|
|
981
|
+
end
|
|
982
|
+
end
|
|
983
|
+
```
|
|
984
|
+
|
|
891
985
|
## Opera::Operation::Result - Instance Methods
|
|
892
986
|
|
|
893
987
|
Sometimes it may be useful to be able to create an instance of the `Result` with preset `output`.
|
|
@@ -898,6 +992,7 @@ Opera::Operation::Result.new(output: 'success')
|
|
|
898
992
|
```
|
|
899
993
|
|
|
900
994
|
>
|
|
995
|
+
|
|
901
996
|
- success? - [true, false] - Return true if no errors
|
|
902
997
|
- failure? - [true, false] - Return true if any error
|
|
903
998
|
- output - [Anything] - Return Anything
|
|
@@ -908,7 +1003,9 @@ Opera::Operation::Result.new(output: 'success')
|
|
|
908
1003
|
- add_information(Hash) - Adss new information - Useful informations for developers
|
|
909
1004
|
|
|
910
1005
|
## Opera::Operation::Base - Instance Methods
|
|
1006
|
+
|
|
911
1007
|
>
|
|
1008
|
+
|
|
912
1009
|
- context [Hash] - used to pass information between steps - only for internal usage
|
|
913
1010
|
- params [Hash] - immutable and received in call method
|
|
914
1011
|
- dependencies [Hash] - immutable and received in call method
|
|
@@ -921,6 +1018,7 @@ Opera::Operation::Result.new(output: 'success')
|
|
|
921
1018
|
The `context_reader` helper method is designed to facilitate easy access to specified keys within a `context` hash. It dynamically defines a method that acts as a getter for the value associated with a specified key, simplifying data retrieval.
|
|
922
1019
|
|
|
923
1020
|
#### Parameters
|
|
1021
|
+
|
|
924
1022
|
**key (Symbol):** The key(s) for which the getter and setter methods are to be created. These symbols should correspond to keys in the context hash.
|
|
925
1023
|
|
|
926
1024
|
**default (Proc, optional):** A lambda or proc that returns a default value for the key if it is not present in the context hash. This proc is lazily evaluated only when the getter is invoked and the key is not present in the hash.
|
|
@@ -993,6 +1091,7 @@ def serializer
|
|
|
993
1091
|
ProfileSerializer.new
|
|
994
1092
|
end
|
|
995
1093
|
```
|
|
1094
|
+
|
|
996
1095
|
**Conclusion**
|
|
997
1096
|
|
|
998
1097
|
For creating instance methods that are meant to be read-only and not stored within a context hash, defining these methods as private is a more suitable and clear approach compared to using context_reader with a default. This method ensures that transient dependencies remain well-encapsulated and are not confused with persistent application state.
|
|
@@ -1008,6 +1107,7 @@ The `context|params|depenencies` helper method is designed to enable easy access
|
|
|
1008
1107
|
**default (Proc, optional):** A lambda or proc that returns a default value for the key if it is not present in the context hash. This proc is lazily evaluated only when the getter is invoked and the key is not present in the hash.
|
|
1009
1108
|
|
|
1010
1109
|
#### Usage
|
|
1110
|
+
|
|
1011
1111
|
```ruby
|
|
1012
1112
|
context do
|
|
1013
1113
|
attr_accessor :profile
|
|
@@ -1038,7 +1138,9 @@ end
|
|
|
1038
1138
|
```
|
|
1039
1139
|
|
|
1040
1140
|
#### Other methods
|
|
1141
|
+
|
|
1041
1142
|
>
|
|
1143
|
+
|
|
1042
1144
|
- step(Symbol) - single instruction
|
|
1043
1145
|
- return [Truthly] - continue operation execution
|
|
1044
1146
|
- return [False] - stops operation execution
|
|
@@ -1049,6 +1151,8 @@ end
|
|
|
1049
1151
|
- transaction(*Symbols) - list of instructions to be wrapped in transaction
|
|
1050
1152
|
- return [Truthly] - continue operation execution
|
|
1051
1153
|
- return [False] - stops operation execution and breaks transaction/do rollback
|
|
1154
|
+
- within(Symbol, &block) - wraps nested steps with a custom method that must yield
|
|
1155
|
+
- the named method receives a block and must yield to execute the nested steps
|
|
1052
1156
|
- call(params: Hash, dependencies: Hash?)
|
|
1053
1157
|
- return [Opera::Operation::Result]
|
|
1054
1158
|
|
|
@@ -1062,7 +1166,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
1062
1166
|
|
|
1063
1167
|
Bug reports and pull requests are welcome on GitHub at https://github.com/profinda/opera. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/opera/blob/master/CODE_OF_CONDUCT.md).
|
|
1064
1168
|
|
|
1065
|
-
|
|
1066
1169
|
## License
|
|
1067
1170
|
|
|
1068
1171
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'opera'
|
|
5
6
|
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
|
@@ -10,5 +11,5 @@ require "opera"
|
|
|
10
11
|
# require "pry"
|
|
11
12
|
# Pry.start
|
|
12
13
|
|
|
13
|
-
require
|
|
14
|
+
require 'irb'
|
|
14
15
|
IRB.start(__FILE__)
|
data/lib/opera/errors.rb
CHANGED
|
@@ -12,7 +12,10 @@ module Opera
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def attr_reader(*attributes, **options)
|
|
15
|
-
|
|
15
|
+
unless allowed.include?(:attr_reader)
|
|
16
|
+
raise NoMethodError,
|
|
17
|
+
"You cannot use attr_reader inside #{klass.name}##{block_name}"
|
|
18
|
+
end
|
|
16
19
|
|
|
17
20
|
attributes.each do |attribute|
|
|
18
21
|
klass.check_method_availability!(attribute)
|
|
@@ -20,10 +23,10 @@ module Opera
|
|
|
20
23
|
method = block_name
|
|
21
24
|
klass.define_method(attribute) do
|
|
22
25
|
value = if send(method).key?(attribute)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
send(method)[attribute]
|
|
27
|
+
elsif options[:default]
|
|
28
|
+
instance_exec(&options[:default])
|
|
29
|
+
end
|
|
27
30
|
|
|
28
31
|
if send(method).frozen?
|
|
29
32
|
send(method)[attribute] || value
|
|
@@ -35,7 +38,10 @@ module Opera
|
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
def attr_writer(*attributes, **options)
|
|
38
|
-
|
|
41
|
+
unless allowed.include?(:attr_accessor)
|
|
42
|
+
raise NoMethodError,
|
|
43
|
+
"You cannot use attr_writer inside #{klass.name}##{block_name}"
|
|
44
|
+
end
|
|
39
45
|
|
|
40
46
|
attributes.each do |attribute|
|
|
41
47
|
klass.check_method_availability!("#{attribute}=")
|
|
@@ -47,7 +53,10 @@ module Opera
|
|
|
47
53
|
end
|
|
48
54
|
|
|
49
55
|
def attr_accessor(*attributes, **options)
|
|
50
|
-
|
|
56
|
+
unless allowed.include?(:attr_accessor)
|
|
57
|
+
raise NoMethodError,
|
|
58
|
+
"You cannot use attr_accessor inside #{klass.name}##{block_name}"
|
|
59
|
+
end
|
|
51
60
|
|
|
52
61
|
attr_reader(*attributes, **options)
|
|
53
62
|
attr_writer(*attributes)
|
data/lib/opera/operation/base.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Opera
|
|
|
33
33
|
def call(args = {})
|
|
34
34
|
operation = new(params: args.fetch(:params, {}), dependencies: args.fetch(:dependencies, {}))
|
|
35
35
|
executor = Executor.new(operation)
|
|
36
|
-
Instrumentation.new(operation).instrument(name:
|
|
36
|
+
Instrumentation.new(operation).instrument(name: name, level: :operation) do
|
|
37
37
|
executor.evaluate_instructions(instructions)
|
|
38
38
|
end
|
|
39
39
|
executor.result
|
|
@@ -58,7 +58,8 @@ module Opera
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def context(&blk)
|
|
61
|
-
AttributesDSL.new(klass: self, block_name: :context,
|
|
61
|
+
AttributesDSL.new(klass: self, block_name: :context,
|
|
62
|
+
allowed: [:attr_reader, :attr_accessor]).instance_exec(&blk)
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
def params(&blk)
|
|
@@ -73,7 +74,7 @@ module Opera
|
|
|
73
74
|
%i[context params dependencies].each do |method|
|
|
74
75
|
define_method("#{method}_reader") do |*attributes, **options|
|
|
75
76
|
send(method) do
|
|
76
|
-
attr_reader
|
|
77
|
+
attr_reader(*attributes, **options)
|
|
77
78
|
end
|
|
78
79
|
end
|
|
79
80
|
end
|
|
@@ -81,14 +82,14 @@ module Opera
|
|
|
81
82
|
%i[context].each do |method|
|
|
82
83
|
define_method("#{method}_writer") do |*attributes|
|
|
83
84
|
send(method) do
|
|
84
|
-
attr_writer
|
|
85
|
+
attr_writer(*attributes)
|
|
85
86
|
end
|
|
86
87
|
end
|
|
87
88
|
|
|
88
89
|
define_method("#{method}_accessor") do |*attributes, **options|
|
|
89
90
|
send(method) do
|
|
90
|
-
attr_reader
|
|
91
|
-
attr_writer
|
|
91
|
+
attr_reader(*attributes, **options)
|
|
92
|
+
attr_writer(*attributes, **options)
|
|
92
93
|
end
|
|
93
94
|
end
|
|
94
95
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Opera
|
|
4
4
|
module Operation
|
|
5
5
|
module Builder
|
|
6
|
-
INSTRUCTIONS = %I[validate transaction
|
|
6
|
+
INSTRUCTIONS = %I[validate transaction step success finish_if operation operations within].freeze
|
|
7
7
|
|
|
8
8
|
def self.included(base)
|
|
9
9
|
base.extend(ClassMethods)
|
|
@@ -16,7 +16,7 @@ module Opera
|
|
|
16
16
|
|
|
17
17
|
INSTRUCTIONS.each do |instruction|
|
|
18
18
|
define_method instruction do |method = nil, &blk|
|
|
19
|
-
|
|
19
|
+
check_method_availability!(method) if method
|
|
20
20
|
instructions.concat(InnerBuilder.new.send(instruction, method, &blk))
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -34,7 +34,7 @@ module Opera
|
|
|
34
34
|
|
|
35
35
|
def validate!
|
|
36
36
|
unless [DEVELOPMENT_MODE, PRODUCTION_MODE].include?(mode)
|
|
37
|
-
raise ArgumentError, 'Mode is incorrect. Can be either: development or production'
|
|
37
|
+
raise ArgumentError, 'Mode is incorrect. Can be either: development or production'
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -44,10 +44,10 @@ module Opera
|
|
|
44
44
|
Instructions::Executors::Validate.new(operation).call(instruction)
|
|
45
45
|
when :transaction
|
|
46
46
|
Instructions::Executors::Transaction.new(operation).call(instruction)
|
|
47
|
-
when :benchmark
|
|
48
|
-
Instructions::Executors::Benchmark.new(operation).call(instruction)
|
|
49
47
|
when :finish_if
|
|
50
48
|
Instructions::Executors::FinishIf.new(operation).call(instruction)
|
|
49
|
+
when :within
|
|
50
|
+
Instructions::Executors::Within.new(operation).call(instruction)
|
|
51
51
|
else
|
|
52
52
|
raise(UnknownInstructionError, "Unknown instruction #{instruction[:kind]}")
|
|
53
53
|
end
|
|
@@ -79,7 +79,7 @@ module Opera
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def add_instruction_output(instruction, output = {})
|
|
82
|
-
context["#{instruction[:method]}_output"
|
|
82
|
+
context[:"#{instruction[:method]}_output"] = output
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
end
|