subroutine 0.10.0.beta → 0.10.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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