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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0dbba6e4428e540eddcfb16df3079f0d9c2db2d2
4
- data.tar.gz: bd752b132b834c5c35e07fa2434f8d381da3c385
2
+ SHA256:
3
+ metadata.gz: f2c3e4d90c09ae08102e8ab71974246b2052203ad830d838328e25345d92593e
4
+ data.tar.gz: a1f1e10be4abe4940ca34811ce4951d498ece0aa3bb2b199c776e3c13e142653
5
5
  SHA512:
6
- metadata.gz: 439bce703b933575f763ac2aa4428e65babc620275a5ca45cae8ed255592428966867281d20f6093dea7fd2dde8fbc764d82f57dd5bc81e5e21d2562e5f94db0
7
- data.tar.gz: 8e95b71122f2dcabf504c5c0f7674f12a79923175434cf65300161cc7251123b11a0f9a797889b4bd71b4a2d823921e3d6026f43e9200d8ffbb1d75f17a1a3a9
6
+ metadata.gz: 420d85e72ba91cb3f53025fb43d0dd5276bfbc057f3910acba283d53676667408fc4a52cc5ee5f6d18fc00ffd385ba180bf7b2bf62f608a9a0c37f5541b645cb
7
+ data.tar.gz: 47556a1a5be391f9f0fabad3f656b7a8f813aca312c2a82f278b8f347dddecb024ec5d0cb82d564e7488445ccbac3b50697be68aa769aaa78392d0af8e5186e6
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.4.6
1
+ 2.5.1
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
2
4
 
3
5
  # Specify your gem's dependencies in subroutine.gemspec
4
6
  gemspec
5
7
 
6
- gem 'activemodel', '~> 5.2.3'
8
+ gem "activemodel", "~> 4.2.11"
@@ -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
@@ -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: 'Sorry, you are not authorized to perform this action.') if msg.is_a?(Symbol)
8
- msg ||= I18n.t('errors.unauthorized', default: 'Sorry, you are not authorized to perform this action.')
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 || 'Authorization management has not been declared on this class')
25
+ super(msg || "Authorization management has not been declared on this class")
20
26
  end
21
- end
22
27
 
23
- def self.included(base)
24
- base.instance_eval do
25
- extend ::Subroutine::Auth::ClassMethods
28
+ end
26
29
 
27
- class_attribute :authorization_declared, instance_writer: false
28
- self.authorization_declared = false
30
+ included do
31
+ class_attribute :authorization_declared, instance_writer: false
32
+ self.authorization_declared = false
29
33
 
30
- class_attribute :user_class_name, instance_writer: false
31
- self.user_class_name = "User"
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?(@current_user.class.name)
109
- raise ArgumentError, "current_user must be one of the following types {#{self.class.supported_user_class_names.join(",")}} but was #{@current_user.class.name}"
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 = self.user_class_name.constantize.find(@current_user) if ::Integer === @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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Subroutine
4
+ module Fields
5
+ class MassAssignmentError < ::StandardError
6
+
7
+ def initialize(field_name)
8
+ super("`#{field_name}` is not mass assignable")
9
+ end
10
+
11
+ end
12
+ end
13
+ end