wallaby-active_record 0.1.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +31 -0
- data/lib/adapters/wallaby/active_record/cancancan_provider.rb +9 -0
- data/lib/adapters/wallaby/active_record/default_provider.rb +9 -0
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder/association_builder.rb +52 -0
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder/polymorphic_builder.rb +52 -0
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder/sti_builder.rb +50 -0
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder.rb +66 -0
- data/lib/adapters/wallaby/active_record/model_decorator/title_field_finder.rb +32 -0
- data/lib/adapters/wallaby/active_record/model_decorator.rb +155 -0
- data/lib/adapters/wallaby/active_record/model_finder.rb +45 -0
- data/lib/adapters/wallaby/active_record/model_pagination_provider.rb +34 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/normalizer.rb +54 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/permitter.rb +68 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/querier/transformer.rb +78 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/querier.rb +172 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/validator.rb +37 -0
- data/lib/adapters/wallaby/active_record/model_service_provider.rb +166 -0
- data/lib/adapters/wallaby/active_record/pundit_provider.rb +19 -0
- data/lib/adapters/wallaby/active_record.rb +7 -0
- data/lib/wallaby/active_record/version.rb +7 -0
- data/lib/wallaby/active_record.rb +38 -0
- metadata +152 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
class ActiveRecord
|
|
5
|
+
class ModelServiceProvider
|
|
6
|
+
# Filter the params
|
|
7
|
+
class Permitter
|
|
8
|
+
# @param model_decorator [Wallaby::ModelDecorator]
|
|
9
|
+
def initialize(model_decorator)
|
|
10
|
+
@model_decorator = model_decorator
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [Array<String>] a list of field names of general types
|
|
14
|
+
def simple_field_names
|
|
15
|
+
field_names =
|
|
16
|
+
non_range_fields.keys +
|
|
17
|
+
belongs_to_fields.map do |_, metadata|
|
|
18
|
+
[metadata[:foreign_key], metadata[:polymorphic_type]]
|
|
19
|
+
end.flatten.compact
|
|
20
|
+
fields = [@model_decorator.primary_key, 'created_at', 'updated_at']
|
|
21
|
+
field_names.reject { |field_name| fields.include? field_name }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Array<String>] a list of field names of range and association
|
|
25
|
+
def compound_hashed_fields
|
|
26
|
+
field_names =
|
|
27
|
+
range_fields.keys +
|
|
28
|
+
many_association_fields.map { |_, metadata| metadata[:foreign_key] }
|
|
29
|
+
field_names.each_with_object({}) { |name, hash| hash[name] = [] }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
# @return [Array<String>] a list of field names that ain't association
|
|
35
|
+
def non_association_fields
|
|
36
|
+
@model_decorator.fields.reject { |_, metadata| metadata[:is_association] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Array<String>] a list of field names that ain't range
|
|
40
|
+
def non_range_fields
|
|
41
|
+
non_association_fields.reject { |_, metadata| /range|point/ =~ metadata[:type] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Array<String>] a list of range field names
|
|
45
|
+
def range_fields
|
|
46
|
+
non_association_fields.select { |_, metadata| /range|point/ =~ metadata[:type] }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Array<String>] a list of association field names
|
|
50
|
+
def association_fields
|
|
51
|
+
@model_decorator.fields.select { |_, metadata| metadata[:is_association] && !metadata[:has_scope] }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Array<String>] a list of many association field names:
|
|
55
|
+
# - has_many
|
|
56
|
+
# - has_and_belongs_to_many
|
|
57
|
+
def many_association_fields
|
|
58
|
+
association_fields.select { |_, metadata| /many/ =~ metadata[:type] }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Array<String>] a list of belongs_to association field names
|
|
62
|
+
def belongs_to_fields
|
|
63
|
+
association_fields.select { |_, metadata| metadata[:type] == 'belongs_to' }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
class ActiveRecord
|
|
5
|
+
class ModelServiceProvider
|
|
6
|
+
class Querier
|
|
7
|
+
# Build up query using the results
|
|
8
|
+
class Transformer < Parslet::Transform
|
|
9
|
+
SIMPLE_OPERATORS = {
|
|
10
|
+
':' => :eq,
|
|
11
|
+
':=' => :eq,
|
|
12
|
+
':!' => :not_eq,
|
|
13
|
+
':!=' => :not_eq,
|
|
14
|
+
':<>' => :not_eq,
|
|
15
|
+
':~' => :matches,
|
|
16
|
+
':^' => :matches,
|
|
17
|
+
':$' => :matches,
|
|
18
|
+
':!~' => :does_not_match,
|
|
19
|
+
':!^' => :does_not_match,
|
|
20
|
+
':!$' => :does_not_match,
|
|
21
|
+
':>' => :gt,
|
|
22
|
+
':>=' => :gteq,
|
|
23
|
+
':<' => :lt,
|
|
24
|
+
':<=' => :lteq
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
SEQUENCE_OPERATORS = {
|
|
28
|
+
':' => :in,
|
|
29
|
+
':=' => :in,
|
|
30
|
+
':!' => :not_in,
|
|
31
|
+
':!=' => :not_in,
|
|
32
|
+
':<>' => :not_in,
|
|
33
|
+
':()' => :between,
|
|
34
|
+
':!()' => :not_between
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
# For single keyword
|
|
38
|
+
rule keyword: simple(:value) do
|
|
39
|
+
value.try :to_str
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# For multiple keywords
|
|
43
|
+
rule keyword: sequence(:value) do
|
|
44
|
+
value.presence.try :map, :to_str
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# For operators
|
|
48
|
+
rule left: simple(:left), op: simple(:op), right: simple(:right) do
|
|
49
|
+
oped = op.try :to_str
|
|
50
|
+
operator = SIMPLE_OPERATORS[oped]
|
|
51
|
+
# skip if the operator is unknown
|
|
52
|
+
next unless operator
|
|
53
|
+
|
|
54
|
+
lefted = left.try :to_str
|
|
55
|
+
convert =
|
|
56
|
+
case oped
|
|
57
|
+
when ':~', ':!~' then "%#{right}%"
|
|
58
|
+
when ':^', ':!^' then "#{right}%"
|
|
59
|
+
when ':$', ':!$' then "%#{right}"
|
|
60
|
+
end
|
|
61
|
+
{ left: lefted, op: operator, right: convert || right }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# For operators that have multiple items
|
|
65
|
+
rule left: simple(:left), op: simple(:op), right: sequence(:right) do
|
|
66
|
+
oped = op.try :to_str
|
|
67
|
+
operator = SEQUENCE_OPERATORS[oped]
|
|
68
|
+
next unless operator
|
|
69
|
+
|
|
70
|
+
lefted = left.try :to_str
|
|
71
|
+
convert = Range.new right.try(:first), right.try(:last) if %w(:() :!()).include?(oped)
|
|
72
|
+
{ left: lefted, op: operator, right: convert || right }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
class ActiveRecord
|
|
5
|
+
class ModelServiceProvider
|
|
6
|
+
# Query builder
|
|
7
|
+
class Querier
|
|
8
|
+
TEXT_FIELDS = %w(string text citext longtext tinytext mediumtext).freeze
|
|
9
|
+
|
|
10
|
+
# @param model_decorator [Wallaby::ModelDecorator]
|
|
11
|
+
def initialize(model_decorator)
|
|
12
|
+
@model_decorator = model_decorator
|
|
13
|
+
@model_class = @model_decorator.model_class
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Pull out the query expression string from the parameter `q`,
|
|
17
|
+
# use parser to understand the expression, then use transformer to run
|
|
18
|
+
# SQL arel query.
|
|
19
|
+
# @param params [ActionController::Parameters]
|
|
20
|
+
# @return [ActiveRecord::Relation]
|
|
21
|
+
def search(params)
|
|
22
|
+
filter_name, keywords, field_queries = extract params
|
|
23
|
+
scope = filtered_by filter_name
|
|
24
|
+
query = text_search keywords
|
|
25
|
+
query = field_search field_queries, query
|
|
26
|
+
scope.where query
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# @see Wallaby::Parser
|
|
32
|
+
def parser
|
|
33
|
+
@parser ||= Parser.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @see Wallaby::ActiveRecord::ModelServiceProvider::Querier::Transformer
|
|
37
|
+
def transformer
|
|
38
|
+
@transformer ||= Transformer.new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Arel::Table] arel table
|
|
42
|
+
def table
|
|
43
|
+
@model_class.arel_table
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param params [ActionController::Parameters]
|
|
47
|
+
# @return [Array<String, Array, Array>] a list of object for other
|
|
48
|
+
# method to use.
|
|
49
|
+
def extract(params)
|
|
50
|
+
expressions = to_expressions params
|
|
51
|
+
keywords = expressions.select { |v| v.is_a? String }
|
|
52
|
+
field_queries = expressions.select { |v| v.is_a? Hash }
|
|
53
|
+
filter_name = params[:filter]
|
|
54
|
+
[filter_name, keywords, field_queries]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @param params [ActionController::Parameters]
|
|
58
|
+
# @return [Array] a list of transformed operations
|
|
59
|
+
def to_expressions(params)
|
|
60
|
+
parsed = parser.parse(params[:q] || EMPTY_STRING)
|
|
61
|
+
converted = transformer.apply parsed
|
|
62
|
+
converted.is_a?(Array) ? converted : [converted]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Use the filter name to find out the scope in the following precedents:
|
|
66
|
+
# - scope from metadata
|
|
67
|
+
# - defined scope from the model
|
|
68
|
+
# - unscoped
|
|
69
|
+
# @param filter_name [String] filter name
|
|
70
|
+
# @return [ActiveRecord::Relation]
|
|
71
|
+
def filtered_by(filter_name)
|
|
72
|
+
valid_filter_name =
|
|
73
|
+
FilterUtils.filter_name_by(filter_name, @model_decorator.filters)
|
|
74
|
+
scope = find_scope(valid_filter_name)
|
|
75
|
+
if scope.blank? then unscoped
|
|
76
|
+
elsif scope.is_a?(Proc) then @model_class.instance_exec(&scope)
|
|
77
|
+
elsif @model_class.respond_to?(scope)
|
|
78
|
+
@model_class.public_send(scope)
|
|
79
|
+
else unscoped
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Find out the scope for given filter
|
|
84
|
+
# - from metadata
|
|
85
|
+
# - filter name itself
|
|
86
|
+
# @param filter_name [String] filter name
|
|
87
|
+
# @return [String]
|
|
88
|
+
def find_scope(filter_name)
|
|
89
|
+
filter = @model_decorator.filters[filter_name] || {}
|
|
90
|
+
filter[:scope] || filter_name
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Unscoped query
|
|
94
|
+
# @return [ActiveRecord::Relation]
|
|
95
|
+
def unscoped
|
|
96
|
+
@model_class.where nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Search text for the text columns that appear in `index_field_names`
|
|
100
|
+
# @param keywords [String] keywords
|
|
101
|
+
# @param query [ActiveRecord::Relation, nil]
|
|
102
|
+
# @return [ActiveRecord::Relation]
|
|
103
|
+
def text_search(keywords, query = nil)
|
|
104
|
+
return query unless keywords_check? keywords
|
|
105
|
+
|
|
106
|
+
text_fields.each do |field_name|
|
|
107
|
+
sub_query = nil
|
|
108
|
+
keywords.each do |keyword|
|
|
109
|
+
exp = table[field_name].matches("%#{keyword}%")
|
|
110
|
+
sub_query = sub_query.try(:and, exp) || exp
|
|
111
|
+
end
|
|
112
|
+
query = query.try(:or, sub_query) || sub_query
|
|
113
|
+
end
|
|
114
|
+
query
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Perform SQL query for the colon query (e.g. data:<2000-01-01)
|
|
118
|
+
# @param field_queries [Array]
|
|
119
|
+
# @param query [ActiveRecord::Relation]
|
|
120
|
+
# @return [ActiveRecord::Relation]
|
|
121
|
+
def field_search(field_queries, query)
|
|
122
|
+
return query unless field_check? field_queries
|
|
123
|
+
|
|
124
|
+
field_queries.each do |exp|
|
|
125
|
+
sub_query = table[exp[:left]].try(exp[:op], exp[:right])
|
|
126
|
+
query = query.try(:and, sub_query) || sub_query
|
|
127
|
+
end
|
|
128
|
+
query
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @return [Array<String>] a list of text fields from `index_field_names`
|
|
132
|
+
def text_fields
|
|
133
|
+
@text_fields ||= begin
|
|
134
|
+
index_field_names = @model_decorator.index_field_names.map(&:to_s)
|
|
135
|
+
@model_decorator.fields.select do |field_name, metadata|
|
|
136
|
+
index_field_names.include?(field_name) &&
|
|
137
|
+
TEXT_FIELDS.include?(metadata[:type].to_s)
|
|
138
|
+
end.keys
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @param keywords [Array<String>] a list of keywords
|
|
143
|
+
# @return [Boolean] false when keywords are empty
|
|
144
|
+
# true when text fields for query exist
|
|
145
|
+
# otherwise, raise exception
|
|
146
|
+
def keywords_check?(keywords)
|
|
147
|
+
return false if keywords.blank?
|
|
148
|
+
return true if text_fields.present?
|
|
149
|
+
|
|
150
|
+
message = I18n.t 'errors.unprocessable_entity.keyword_search'
|
|
151
|
+
raise UnprocessableEntity, message
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @param field_queries [Array]
|
|
155
|
+
# @return [Boolean] false when field queries are blank
|
|
156
|
+
# true when the fields used are valid (exist in `fields`)
|
|
157
|
+
# otherwise, raise exception
|
|
158
|
+
def field_check?(field_queries)
|
|
159
|
+
return false if field_queries.blank?
|
|
160
|
+
|
|
161
|
+
fields = field_queries.map { |exp| exp[:left] }
|
|
162
|
+
invalid_fields = fields - @model_decorator.fields.keys
|
|
163
|
+
return true if invalid_fields.blank?
|
|
164
|
+
|
|
165
|
+
message = I18n.t 'errors.unprocessable_entity.field_colon_search',
|
|
166
|
+
invalid_fields: invalid_fields.to_sentence
|
|
167
|
+
raise UnprocessableEntity, message
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
class ActiveRecord
|
|
5
|
+
class ModelServiceProvider
|
|
6
|
+
# Validator
|
|
7
|
+
class Validator
|
|
8
|
+
# @param model_decorator [Wallaby::ModelDecorator]
|
|
9
|
+
def initialize(model_decorator)
|
|
10
|
+
@model_decorator = model_decorator
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @param resource [Object] resource object
|
|
14
|
+
# @return [Boolean] whether the resource object is valid
|
|
15
|
+
def valid?(resource)
|
|
16
|
+
resource.attributes.each do |field_name, values|
|
|
17
|
+
metadata = @model_decorator.fields[field_name]
|
|
18
|
+
next if valid_range_type? values, metadata
|
|
19
|
+
|
|
20
|
+
resource.errors.add field_name, 'required for range data'
|
|
21
|
+
end
|
|
22
|
+
resource.errors.blank?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# @param values [Array]
|
|
28
|
+
# @return [Boolean] whether the values are valid range values
|
|
29
|
+
def valid_range_type?(values, metadata)
|
|
30
|
+
!metadata \
|
|
31
|
+
|| !%w(daterange tsrange tstzrange).include?(metadata[:type]) \
|
|
32
|
+
|| !values.try(:any?, &:blank?)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
class ActiveRecord
|
|
5
|
+
# Model service provider
|
|
6
|
+
# @see Wallaby::ModelServiceProvider
|
|
7
|
+
class ModelServiceProvider < ::Wallaby::ModelServiceProvider
|
|
8
|
+
# @param params [ActionController::Parameters]
|
|
9
|
+
# @param action [String, Symbol]
|
|
10
|
+
# @param authorizer
|
|
11
|
+
# @return [ActionController::Parameters] whitelisted parameters
|
|
12
|
+
# @see Wallaby::ModelServiceProvider#permit
|
|
13
|
+
def permit(params, action, authorizer)
|
|
14
|
+
authorized_fields = authorizer.permit_params action, @model_class
|
|
15
|
+
params.require(param_key).permit(authorized_fields || permitted_fields)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @note Pagination free here. Since somewhere might need the collection without any pagination
|
|
19
|
+
# @param params [ActionController::Parameters]
|
|
20
|
+
# @param authorizer [Ability] for now
|
|
21
|
+
# @return [ActiveRecord::Relation] relation
|
|
22
|
+
# @see Wallaby::ModelServiceProvider#collection
|
|
23
|
+
def collection(params, authorizer)
|
|
24
|
+
query = querier.search params
|
|
25
|
+
query = query.order params[:sort] if params[:sort].present?
|
|
26
|
+
authorizer.accessible_for :index, query
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param query [ActiveRecord::Relation]
|
|
30
|
+
# @param params [ActionController::Parameters]
|
|
31
|
+
# @return [ActiveRecord::Relation] paginated query
|
|
32
|
+
# @see Wallaby::ModelServiceProvider#paginate
|
|
33
|
+
def paginate(query, params)
|
|
34
|
+
per = params[:per] || Wallaby.configuration.pagination.page_size
|
|
35
|
+
query = query.page params[:page] if query.respond_to? :page
|
|
36
|
+
query = query.per per if query.respond_to? :per
|
|
37
|
+
query
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @note No mass assignment happens here!
|
|
41
|
+
# @return [Object] new resource object
|
|
42
|
+
# @see Wallaby::ModelServiceProvider#new
|
|
43
|
+
def new(_params, _authorizer)
|
|
44
|
+
@model_class.new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @note No mass assignment happens here!
|
|
48
|
+
# Find the record using id.
|
|
49
|
+
# @param id [Integer, String]
|
|
50
|
+
# @return [Object] persisted resource object
|
|
51
|
+
# @raise [Wallaby::ResourceNotFound] when record is not found
|
|
52
|
+
# @see Wallaby::ModelServiceProvider#find
|
|
53
|
+
def find(id, _params, _authorizer)
|
|
54
|
+
@model_class.find id
|
|
55
|
+
rescue ::ActiveRecord::RecordNotFound
|
|
56
|
+
raise ResourceNotFound, id
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Assign resource with new values and store it in database as new record.
|
|
60
|
+
# @param resource [Object]
|
|
61
|
+
# @param params [ActionController::Parameters]
|
|
62
|
+
# @param authorizer [Wallaby::ModelAuthorizer]
|
|
63
|
+
# @see Wallaby::ModelServiceProvider#create
|
|
64
|
+
def create(resource, params, authorizer)
|
|
65
|
+
save __callee__, resource, params, authorizer
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Assign resource with new values and store it in database as an update.
|
|
69
|
+
# @param resource [Object]
|
|
70
|
+
# @param params [ActionController::Parameters]
|
|
71
|
+
# @param authorizer [Wallaby::ModelAuthorizer]
|
|
72
|
+
# @see Wallaby::ModelServiceProvider#update
|
|
73
|
+
def update(resource, params, authorizer)
|
|
74
|
+
save __callee__, resource, params, authorizer
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Remove a record from database
|
|
78
|
+
# @param resource [Object]
|
|
79
|
+
# @see Wallaby::ModelServiceProvider#destroy
|
|
80
|
+
def destroy(resource, _params, _authorizer)
|
|
81
|
+
resource.destroy
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
protected
|
|
85
|
+
|
|
86
|
+
# Save the record
|
|
87
|
+
# @param action [String] `create`, `update`
|
|
88
|
+
# @param resource [Object]
|
|
89
|
+
# @param params [ActionController::Parameters]
|
|
90
|
+
# @param authorizer [Wallaby::ModelAuthorizer]
|
|
91
|
+
# @return resource itself
|
|
92
|
+
# @raise [ActiveRecord::StatementInvalid, ActiveModel::UnknownAttributeError, ActiveRecord::UnknownAttributeError]
|
|
93
|
+
def save(action, resource, params, authorizer)
|
|
94
|
+
resource.assign_attributes normalize params
|
|
95
|
+
ensure_attributes_for authorizer, action, resource
|
|
96
|
+
resource.save if valid? resource
|
|
97
|
+
resource
|
|
98
|
+
rescue ::ActiveRecord::ActiveRecordError, ActiveModel::ForbiddenAttributesError, unknown_attribute_error => e
|
|
99
|
+
resource.errors.add :base, e.message
|
|
100
|
+
resource
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Normalize params
|
|
104
|
+
# @param params [ActionController::Parameters]
|
|
105
|
+
def normalize(params)
|
|
106
|
+
normalizer.normalize params
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# See if a resource is valid
|
|
110
|
+
# @param resource [Object]
|
|
111
|
+
# @return [Boolean]
|
|
112
|
+
def valid?(resource)
|
|
113
|
+
validator.valid? resource
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# To make sure that the record can be updated with the values that are
|
|
117
|
+
# allowed to.
|
|
118
|
+
# @param authorizer [Object]
|
|
119
|
+
# @param action [String]
|
|
120
|
+
# @param resource [Object]
|
|
121
|
+
def ensure_attributes_for(authorizer, action, resource)
|
|
122
|
+
return if authorizer.blank?
|
|
123
|
+
|
|
124
|
+
restricted_conditions = authorizer.attributes_for action, resource
|
|
125
|
+
resource.assign_attributes restricted_conditions
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @return [String] param key
|
|
129
|
+
def param_key
|
|
130
|
+
@model_class.model_name.param_key
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @return [Array] the list of attributes to whitelist for mass assignment
|
|
134
|
+
def permitted_fields
|
|
135
|
+
@permitted_fields ||=
|
|
136
|
+
permitter.simple_field_names << permitter.compound_hashed_fields
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @see Wallaby::ModelServiceProvider::Permitter
|
|
140
|
+
def permitter
|
|
141
|
+
@permitter ||= Permitter.new @model_decorator
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# @see Wallaby::ModelServiceProvider::Querier
|
|
145
|
+
def querier
|
|
146
|
+
@querier ||= Querier.new @model_decorator
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @see Wallaby::ModelServiceProvider::Normalizer
|
|
150
|
+
def normalizer
|
|
151
|
+
@normalizer ||= Normalizer.new @model_decorator
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @see Wallaby::ModelServiceProvider::Validator
|
|
155
|
+
def validator
|
|
156
|
+
@validator ||= Validator.new @model_decorator
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @return [Class] ActiveModel::UnknownAttributeError if Rails 4
|
|
160
|
+
# @return [Class] ActiveRecord::UnknownAttributeError if Rails 5
|
|
161
|
+
def unknown_attribute_error
|
|
162
|
+
(defined?(::ActiveModel::UnknownAttributeError) ? ::ActiveModel : ::ActiveRecord)::UnknownAttributeError
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
class ActiveRecord
|
|
5
|
+
# Pundit provider for ActiveRecord
|
|
6
|
+
class PunditProvider < PunditAuthorizationProvider
|
|
7
|
+
# Filter a scope
|
|
8
|
+
# @param _action [Symbol, String]
|
|
9
|
+
# @param scope [Object]
|
|
10
|
+
# @return [Object]
|
|
11
|
+
def accessible_for(_action, scope)
|
|
12
|
+
Pundit.policy_scope! user, scope
|
|
13
|
+
rescue Pundit::NotDefinedError
|
|
14
|
+
Rails.logger.warn I18n.t('errors.pundit.not_found.scope_policy', scope: scope)
|
|
15
|
+
scope
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kaminari'
|
|
4
|
+
require 'wallaby/core'
|
|
5
|
+
|
|
6
|
+
require 'wallaby/active_record/version'
|
|
7
|
+
|
|
8
|
+
# All files required for ActiveRecord ORM
|
|
9
|
+
require 'adapters/wallaby/active_record'
|
|
10
|
+
require 'adapters/wallaby/active_record/model_finder'
|
|
11
|
+
|
|
12
|
+
# ModelPaginationProvider: begin
|
|
13
|
+
require 'adapters/wallaby/active_record/model_pagination_provider'
|
|
14
|
+
# ModelPaginationProvider: end
|
|
15
|
+
|
|
16
|
+
# ModelDecorator: begin
|
|
17
|
+
require 'adapters/wallaby/active_record/model_decorator'
|
|
18
|
+
require 'adapters/wallaby/active_record/model_decorator/title_field_finder'
|
|
19
|
+
require 'adapters/wallaby/active_record/model_decorator/fields_builder'
|
|
20
|
+
require 'adapters/wallaby/active_record/model_decorator/fields_builder/sti_builder'
|
|
21
|
+
require 'adapters/wallaby/active_record/model_decorator/fields_builder/association_builder'
|
|
22
|
+
require 'adapters/wallaby/active_record/model_decorator/fields_builder/polymorphic_builder'
|
|
23
|
+
# ModelDecorator: end
|
|
24
|
+
|
|
25
|
+
# ModelServiceProvider: begin
|
|
26
|
+
require 'adapters/wallaby/active_record/model_service_provider'
|
|
27
|
+
require 'adapters/wallaby/active_record/model_service_provider/normalizer'
|
|
28
|
+
require 'adapters/wallaby/active_record/model_service_provider/permitter'
|
|
29
|
+
require 'adapters/wallaby/active_record/model_service_provider/querier'
|
|
30
|
+
require 'adapters/wallaby/active_record/model_service_provider/querier/transformer'
|
|
31
|
+
require 'adapters/wallaby/active_record/model_service_provider/validator'
|
|
32
|
+
# ModelServiceProvider: end
|
|
33
|
+
|
|
34
|
+
# AuthorizationProvider: begin
|
|
35
|
+
require 'adapters/wallaby/active_record/default_provider'
|
|
36
|
+
require 'adapters/wallaby/active_record/cancancan_provider'
|
|
37
|
+
require 'adapters/wallaby/active_record/pundit_provider'
|
|
38
|
+
# AuthorizationProvider: end
|