subroutine 0.10.0.beta → 0.10.0.beta2
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 +5 -5
- data/.ruby-version +1 -1
- data/Gemfile +4 -2
- data/lib/subroutine/association_fields/component_configuration.rb +19 -0
- data/lib/subroutine/association_fields/configuration.rb +74 -0
- data/lib/subroutine/association_fields.rb +158 -0
- data/lib/subroutine/auth.rb +21 -16
- data/lib/subroutine/fields/configuration.rb +90 -0
- data/lib/subroutine/fields/mass_assignment_error.rb +13 -0
- data/lib/subroutine/fields.rb +146 -82
- data/lib/subroutine/op.rb +14 -38
- data/lib/subroutine/outputs/configuration.rb +39 -0
- data/lib/subroutine/outputs/output_not_set_error.rb +13 -0
- data/lib/subroutine/outputs/unknown_output_error.rb +13 -0
- data/lib/subroutine/outputs.rb +66 -0
- data/lib/subroutine/version.rb +4 -2
- data/test/subroutine/association_test.rb +18 -17
- data/test/subroutine/auth_test.rb +11 -4
- data/test/subroutine/base_test.rb +70 -58
- data/test/subroutine/fields_test.rb +49 -12
- data/test/support/ops.rb +113 -16
- metadata +12 -7
- data/lib/subroutine/association.rb +0 -131
- data/lib/subroutine/output_not_set_error.rb +0 -9
- data/lib/subroutine/unknown_output_error.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f2c3e4d90c09ae08102e8ab71974246b2052203ad830d838328e25345d92593e
|
4
|
+
data.tar.gz: a1f1e10be4abe4940ca34811ce4951d498ece0aa3bb2b199c776e3c13e142653
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 420d85e72ba91cb3f53025fb43d0dd5276bfbc057f3910acba283d53676667408fc4a52cc5ee5f6d18fc00ffd385ba180bf7b2bf62f608a9a0c37f5541b645cb
|
7
|
+
data.tar.gz: 47556a1a5be391f9f0fabad3f656b7a8f813aca312c2a82f278b8f347dddecb024ec5d0cb82d564e7488445ccbac3b50697be68aa769aaa78392d0af8e5186e6
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.5.1
|
data/Gemfile
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "subroutine/fields/configuration"
|
4
|
+
|
5
|
+
module Subroutine
|
6
|
+
module AssociationFields
|
7
|
+
class ComponentConfiguration < ::Subroutine::Fields::Configuration
|
8
|
+
|
9
|
+
def behavior
|
10
|
+
:association_component
|
11
|
+
end
|
12
|
+
|
13
|
+
def association_name
|
14
|
+
config[:association_name]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "subroutine/fields/configuration"
|
4
|
+
require "subroutine/association_fields/component_configuration"
|
5
|
+
|
6
|
+
module Subroutine
|
7
|
+
module AssociationFields
|
8
|
+
class Configuration < ::Subroutine::Fields::Configuration
|
9
|
+
|
10
|
+
def validate!
|
11
|
+
super
|
12
|
+
|
13
|
+
if as && foreign_key
|
14
|
+
raise ArgumentError, ":as and :foreign_key options should not be provided together to an association invocation"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def required_modules
|
19
|
+
super + [::Subroutine::AssociationFields]
|
20
|
+
end
|
21
|
+
|
22
|
+
def polymorphic?
|
23
|
+
!!config[:polymorphic]
|
24
|
+
end
|
25
|
+
|
26
|
+
def as
|
27
|
+
config[:as] || field_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def class_name
|
31
|
+
config[:class_name]
|
32
|
+
end
|
33
|
+
|
34
|
+
def inferred_class_name
|
35
|
+
class_name || as.to_s.camelize
|
36
|
+
end
|
37
|
+
|
38
|
+
def foreign_key
|
39
|
+
config[:foreign_key]
|
40
|
+
end
|
41
|
+
|
42
|
+
def foreign_key_method
|
43
|
+
foreign_key || "#{field_name}_id"
|
44
|
+
end
|
45
|
+
|
46
|
+
def foreign_type_method
|
47
|
+
foreign_key_method.gsub(/_id$/, "_type")
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_foreign_key_field
|
51
|
+
build_child_field(foreign_key_method)
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_foreign_type_field
|
55
|
+
build_child_field(foreign_type_method)
|
56
|
+
end
|
57
|
+
|
58
|
+
def unscoped?
|
59
|
+
!!config[:unscoped]
|
60
|
+
end
|
61
|
+
|
62
|
+
def behavior
|
63
|
+
:association
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def build_child_field(name)
|
69
|
+
ComponentConfiguration.new(name, inheritable_options.merge(association_name: as))
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
require "active_support/concern"
|
5
|
+
require "subroutine/association_fields/configuration"
|
6
|
+
|
7
|
+
module Subroutine
|
8
|
+
module AssociationFields
|
9
|
+
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
included do
|
13
|
+
class << self
|
14
|
+
|
15
|
+
alias_method :field_without_association, :field
|
16
|
+
alias_method :field, :field_with_association
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :set_field_without_association, :set_field
|
21
|
+
alias_method :set_field, :set_field_with_association
|
22
|
+
|
23
|
+
alias_method :get_field_without_association, :get_field
|
24
|
+
alias_method :get_field, :get_field_with_association
|
25
|
+
|
26
|
+
alias_method :clear_field_without_association, :clear_field
|
27
|
+
alias_method :clear_field, :clear_field_with_association
|
28
|
+
|
29
|
+
alias_method :field_provided_without_association?, :field_provided?
|
30
|
+
alias_method :field_provided?, :field_provided_with_association?
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
|
35
|
+
def association(field_name, opts = {})
|
36
|
+
field(field_name, opts.merge(type: :association))
|
37
|
+
end
|
38
|
+
|
39
|
+
# association :user
|
40
|
+
# - user_id
|
41
|
+
# - user_type => "User"
|
42
|
+
|
43
|
+
# association :user, polymorphic: true
|
44
|
+
# - user_id
|
45
|
+
# - user_type
|
46
|
+
# - user => polymorphic_lookup(user_type, user_id)
|
47
|
+
|
48
|
+
# association :inbound_user_request, as: :request
|
49
|
+
# - inbound_user_request_id
|
50
|
+
# - inbound_user_request_type => "InboundUserRequest"
|
51
|
+
# - request => polymorphic_lookup(inbound_user_request_type, inbound_user_request_id)
|
52
|
+
|
53
|
+
# association :inbound_user_request, foreign_key: :request_id
|
54
|
+
# - request_id
|
55
|
+
# - request_type
|
56
|
+
# - inbound_user_request => polymorphic_lookup(request_type, request_id)
|
57
|
+
|
58
|
+
# Other options:
|
59
|
+
# - unscoped => set true if the record should be looked up via Type.unscoped
|
60
|
+
|
61
|
+
def field_with_association(field_name, options = {})
|
62
|
+
if options[:type]&.to_sym == :association
|
63
|
+
config = ::Subroutine::AssociationFields::Configuration.new(field_name, options)
|
64
|
+
|
65
|
+
if config.polymorphic?
|
66
|
+
string config.foreign_type_method, config.build_foreign_type_field
|
67
|
+
else
|
68
|
+
class_eval <<-EV, __FILE__, __LINE__ + 1
|
69
|
+
def #{config.foreign_type_method}
|
70
|
+
#{config.inferred_class_name.inspect}
|
71
|
+
end
|
72
|
+
EV
|
73
|
+
end
|
74
|
+
|
75
|
+
integer config.foreign_key_method, config.build_foreign_key_field
|
76
|
+
|
77
|
+
field_without_association(config.as, config)
|
78
|
+
else
|
79
|
+
field_without_association(field_name, options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
def set_field_with_association(field_name, value, opts = {})
|
86
|
+
config = get_field_config(field_name)
|
87
|
+
|
88
|
+
if config&.behavior == :association
|
89
|
+
set_field(config.foreign_type_method, value&.class&.name, opts) if config.polymorphic?
|
90
|
+
set_field(config.foreign_key_method, value&.id, opts)
|
91
|
+
elsif config&.behavior == :association_component
|
92
|
+
clear_field_without_association(config.association_name)
|
93
|
+
end
|
94
|
+
|
95
|
+
set_field_without_association(field_name, value, opts)
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_field_with_association(field_name)
|
99
|
+
config = get_field_config(field_name)
|
100
|
+
|
101
|
+
if config&.behavior == :association
|
102
|
+
stored_result = get_field_without_association(field_name)
|
103
|
+
return stored_result unless stored_result.nil?
|
104
|
+
|
105
|
+
fk = send(config.foreign_key_method)
|
106
|
+
type = send(config.foreign_type_method)
|
107
|
+
|
108
|
+
result = fetch_association_instance(type, fk, config.unscoped?)
|
109
|
+
set_field_without_association(field_name, result)
|
110
|
+
result
|
111
|
+
else
|
112
|
+
get_field_without_association(field_name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def clear_field_with_association(field_name)
|
117
|
+
config = get_field_config(field_name)
|
118
|
+
|
119
|
+
if config&.behavior == :association
|
120
|
+
clear_field(config.foreign_type_method) if config.polymorphic?
|
121
|
+
clear_field(config.foreign_key_method)
|
122
|
+
end
|
123
|
+
|
124
|
+
clear_field_without_association(field_name)
|
125
|
+
end
|
126
|
+
|
127
|
+
def field_provided_with_association?(field_name)
|
128
|
+
config = get_field_config(field_name)
|
129
|
+
|
130
|
+
if config&.behavior == :association
|
131
|
+
provided = true
|
132
|
+
provided &&= field_provided?(config.foreign_type_method) if config.polymorphic?
|
133
|
+
provided &&= field_provided?(config.foreign_key_method)
|
134
|
+
provided
|
135
|
+
elsif config&.behavior == :association_component
|
136
|
+
field_provided_without_association?(field_name) ||
|
137
|
+
field_provided_without_association?(config.association_name)
|
138
|
+
else
|
139
|
+
field_provided_without_association?(field_name)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def fetch_association_instance(_type, _fk, _unscoped = false)
|
144
|
+
return nil unless _type && _fk
|
145
|
+
|
146
|
+
klass = _type
|
147
|
+
klass = klass.classify.constantize if klass.is_a?(String)
|
148
|
+
|
149
|
+
return nil unless klass
|
150
|
+
|
151
|
+
scope = klass.all
|
152
|
+
scope = scope.unscoped if _unscoped
|
153
|
+
|
154
|
+
scope.find(_fk)
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
data/lib/subroutine/auth.rb
CHANGED
@@ -2,34 +2,37 @@
|
|
2
2
|
|
3
3
|
module Subroutine
|
4
4
|
module Auth
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
5
8
|
class NotAuthorizedError < ::StandardError
|
9
|
+
|
6
10
|
def initialize(msg = nil)
|
7
|
-
msg = I18n.t("errors.#{msg}", default:
|
8
|
-
msg ||= I18n.t(
|
11
|
+
msg = I18n.t("errors.#{msg}", default: "Sorry, you are not authorized to perform this action.") if msg.is_a?(Symbol)
|
12
|
+
msg ||= I18n.t("errors.unauthorized", default: "Sorry, you are not authorized to perform this action.")
|
9
13
|
super msg
|
10
14
|
end
|
11
15
|
|
12
16
|
def status
|
13
17
|
401
|
14
18
|
end
|
19
|
+
|
15
20
|
end
|
16
21
|
|
17
22
|
class AuthorizationNotDeclaredError < ::StandardError
|
23
|
+
|
18
24
|
def initialize(msg = nil)
|
19
|
-
super(msg ||
|
25
|
+
super(msg || "Authorization management has not been declared on this class")
|
20
26
|
end
|
21
|
-
end
|
22
27
|
|
23
|
-
|
24
|
-
base.instance_eval do
|
25
|
-
extend ::Subroutine::Auth::ClassMethods
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
30
|
+
included do
|
31
|
+
class_attribute :authorization_declared, instance_writer: false
|
32
|
+
self.authorization_declared = false
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
34
|
+
class_attribute :user_class_name, instance_writer: false
|
35
|
+
self.user_class_name = "User"
|
33
36
|
end
|
34
37
|
|
35
38
|
module ClassMethods
|
@@ -38,7 +41,6 @@ module Subroutine
|
|
38
41
|
[user_class_name, "Integer", "NilClass"].compact
|
39
42
|
end
|
40
43
|
|
41
|
-
|
42
44
|
def authorize(validation_name)
|
43
45
|
validate validation_name, unless: :skip_auth_checks?
|
44
46
|
end
|
@@ -96,17 +98,19 @@ module Subroutine
|
|
96
98
|
end
|
97
99
|
end
|
98
100
|
end
|
101
|
+
|
99
102
|
end
|
100
103
|
|
101
104
|
def initialize(*args, &block)
|
102
105
|
raise Subroutine::Auth::AuthorizationNotDeclaredError unless self.class.authorization_declared
|
103
106
|
|
104
107
|
super(args.extract_options!, &block)
|
108
|
+
|
105
109
|
@skip_auth_checks = false
|
106
110
|
@current_user = args.shift
|
107
111
|
|
108
|
-
unless self.class.supported_user_class_names.include?(
|
109
|
-
raise ArgumentError, "current_user must be one of the following types {#{self.class.supported_user_class_names.join(",")}} but was #{
|
112
|
+
unless self.class.supported_user_class_names.include?(current_user.class.name)
|
113
|
+
raise ArgumentError, "current_user must be one of the following types {#{self.class.supported_user_class_names.join(",")}} but was #{current_user.class.name}"
|
110
114
|
end
|
111
115
|
end
|
112
116
|
|
@@ -120,7 +124,7 @@ module Subroutine
|
|
120
124
|
end
|
121
125
|
|
122
126
|
def current_user
|
123
|
-
@current_user =
|
127
|
+
@current_user = user_class_name.constantize.find(@current_user) if ::Integer === @current_user
|
124
128
|
@current_user
|
125
129
|
end
|
126
130
|
|
@@ -128,5 +132,6 @@ module Subroutine
|
|
128
132
|
reason ||= :unauthorized
|
129
133
|
raise ::Subroutine::Auth::NotAuthorizedError, reason
|
130
134
|
end
|
135
|
+
|
131
136
|
end
|
132
137
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module Subroutine
|
6
|
+
module Fields
|
7
|
+
class Configuration < ::SimpleDelegator
|
8
|
+
|
9
|
+
PROTECTED_GROUP_IDENTIFIERS = %i[all original default].freeze
|
10
|
+
INHERITABLE_OPTIONS = %i[mass_assignable field_reader field_writer].freeze
|
11
|
+
|
12
|
+
def self.from(field_name, options)
|
13
|
+
case options
|
14
|
+
when Subroutine::Fields::Configuration
|
15
|
+
options.class.new(field_name, options)
|
16
|
+
else
|
17
|
+
new(field_name, options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :field_name
|
22
|
+
|
23
|
+
def initialize(field_name, options)
|
24
|
+
@field_name = field_name
|
25
|
+
config = sanitize_options(options)
|
26
|
+
super(config)
|
27
|
+
validate!
|
28
|
+
end
|
29
|
+
|
30
|
+
alias config __getobj__
|
31
|
+
|
32
|
+
def required_modules
|
33
|
+
[]
|
34
|
+
end
|
35
|
+
|
36
|
+
def behavior
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def inheritable_options
|
41
|
+
config.slice(*INHERITABLE_OPTIONS)
|
42
|
+
end
|
43
|
+
|
44
|
+
def mass_assignable?
|
45
|
+
config[:mass_assignable] != false
|
46
|
+
end
|
47
|
+
|
48
|
+
def field_writer?
|
49
|
+
config[:field_writer] != false
|
50
|
+
end
|
51
|
+
|
52
|
+
def field_reader?
|
53
|
+
config[:field_reader] != false
|
54
|
+
end
|
55
|
+
|
56
|
+
def parent_field
|
57
|
+
config[:parent_field]
|
58
|
+
end
|
59
|
+
|
60
|
+
def groups
|
61
|
+
config[:groups]
|
62
|
+
end
|
63
|
+
|
64
|
+
def in_group?(group_name)
|
65
|
+
groups.include?(group_name.to_sym)
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate!
|
69
|
+
PROTECTED_GROUP_IDENTIFIERS.each do |group_name|
|
70
|
+
next unless in_group?(group_name)
|
71
|
+
|
72
|
+
raise ArgumentError, "Cannot assign a field to protected group `#{group}`. Protected groups are: #{PROTECTED_GROUP_IDENTIFIERS.join(", ")}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def sanitize_options(options)
|
77
|
+
opts = (options || {}).to_h.dup
|
78
|
+
groups = opts[:group] || opts[:groups]
|
79
|
+
opts[:groups] = Array(groups).map(&:to_sym)
|
80
|
+
opts.delete(:group)
|
81
|
+
opts
|
82
|
+
end
|
83
|
+
|
84
|
+
def inspect
|
85
|
+
"#<#{self.class}:#{object_id} name=#{field_name} config=#{config.inspect}>"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|