forest_admin_datasource_active_record 1.8.7 → 1.8.8
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/lib/forest_admin_datasource_active_record/collection.rb +11 -5
- data/lib/forest_admin_datasource_active_record/parser/column.rb +42 -3
- data/lib/forest_admin_datasource_active_record/utils/error_handler.rb +39 -0
- data/lib/forest_admin_datasource_active_record/utils/query.rb +79 -2
- data/lib/forest_admin_datasource_active_record/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b16fe13a23203f8a9cd4fc0d2d6d2d2ea0e6ae67199eeb3f24e532f5b48ba6b5
|
|
4
|
+
data.tar.gz: 443f1a78c1b248ccd87bf47a9389384ca734c971930afe328370a79b549a0c4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5753a7d39a2bbb7b32e88b4ba0aaf0953c57ea926f2674669d633ab85b70422cc0428afeb3d64838f27a2ebb6b468ea85e1e6d30dbf154b659966663f5b9503
|
|
7
|
+
data.tar.gz: 2e1e77c261b33343aebcfd94172c843031249d8e61fc6f0a7a3ec2d0219f12ea420c2fdceb4ba8082c5435069ab6068f2704361b7935514b303b8e1a638ac298
|
|
@@ -31,17 +31,23 @@ module ForestAdminDatasourceActiveRecord
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def create(_caller, data)
|
|
34
|
-
Utils::
|
|
34
|
+
Utils::ErrorHandler.handle_errors(:create) do
|
|
35
|
+
Utils::ActiveRecordSerializer.new(@model.create!(data)).to_hash(ProjectionFactory.all(self))
|
|
36
|
+
end
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def update(_caller, filter, data)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
Utils::ErrorHandler.handle_errors(:update) do
|
|
41
|
+
entity = Utils::Query.new(self, nil, filter).build.first
|
|
42
|
+
entity&.update!(data)
|
|
43
|
+
end
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
def delete(_caller, filter)
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
Utils::ErrorHandler.handle_errors(:delete) do
|
|
48
|
+
entities = Utils::Query.new(self, nil, filter).build
|
|
49
|
+
entities&.each(&:destroy)
|
|
50
|
+
end
|
|
45
51
|
end
|
|
46
52
|
|
|
47
53
|
private
|
|
@@ -61,10 +61,49 @@ module ForestAdminDatasourceActiveRecord
|
|
|
61
61
|
def operators_for_column_type(type)
|
|
62
62
|
result = [Operators::PRESENT, Operators::BLANK, Operators::MISSING]
|
|
63
63
|
equality = [Operators::EQUAL, Operators::NOT_EQUAL, Operators::IN, Operators::NOT_IN]
|
|
64
|
+
orderables = [
|
|
65
|
+
Operators::LESS_THAN,
|
|
66
|
+
Operators::GREATER_THAN,
|
|
67
|
+
Operators::LESS_THAN_OR_EQUAL,
|
|
68
|
+
Operators::GREATER_THAN_OR_EQUAL
|
|
69
|
+
]
|
|
70
|
+
strings = [
|
|
71
|
+
Operators::LIKE,
|
|
72
|
+
Operators::I_LIKE,
|
|
73
|
+
Operators::CONTAINS,
|
|
74
|
+
Operators::I_CONTAINS,
|
|
75
|
+
Operators::NOT_CONTAINS,
|
|
76
|
+
Operators::NOT_I_CONTAINS,
|
|
77
|
+
Operators::STARTS_WITH,
|
|
78
|
+
Operators::I_STARTS_WITH,
|
|
79
|
+
Operators::ENDS_WITH,
|
|
80
|
+
Operators::I_ENDS_WITH,
|
|
81
|
+
Operators::SHORTER_THAN,
|
|
82
|
+
Operators::LONGER_THAN
|
|
83
|
+
]
|
|
64
84
|
|
|
65
85
|
if type.is_a? String
|
|
66
|
-
orderables = [
|
|
67
|
-
|
|
86
|
+
orderables = [
|
|
87
|
+
Operators::LESS_THAN,
|
|
88
|
+
Operators::GREATER_THAN,
|
|
89
|
+
Operators::LESS_THAN_OR_EQUAL,
|
|
90
|
+
Operators::GREATER_THAN_OR_EQUAL
|
|
91
|
+
]
|
|
92
|
+
strings = [
|
|
93
|
+
Operators::CONTAINS,
|
|
94
|
+
Operators::I_CONTAINS,
|
|
95
|
+
Operators::NOT_CONTAINS,
|
|
96
|
+
Operators::NOT_I_CONTAINS,
|
|
97
|
+
Operators::STARTS_WITH,
|
|
98
|
+
Operators::I_STARTS_WITH,
|
|
99
|
+
Operators::ENDS_WITH,
|
|
100
|
+
Operators::I_ENDS_WITH,
|
|
101
|
+
Operators::LIKE,
|
|
102
|
+
Operators::I_LIKE,
|
|
103
|
+
Operators::MATCH,
|
|
104
|
+
Operators::SHORTER_THAN,
|
|
105
|
+
Operators::LONGER_THAN
|
|
106
|
+
]
|
|
68
107
|
|
|
69
108
|
result += equality if %w[Boolean Binary Enum Uuid].include?(type)
|
|
70
109
|
|
|
@@ -73,7 +112,7 @@ module ForestAdminDatasourceActiveRecord
|
|
|
73
112
|
result = result + equality + orderables + strings if %w[String].include?(type)
|
|
74
113
|
end
|
|
75
114
|
|
|
76
|
-
result
|
|
115
|
+
result += [Operators::EQUAL, Operators::NOT_EQUAL, Operators::INCLUDES_ALL] if type.is_a?(Array)
|
|
77
116
|
|
|
78
117
|
result
|
|
79
118
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module ForestAdminDatasourceActiveRecord
|
|
2
|
+
module Utils
|
|
3
|
+
class ErrorHandler
|
|
4
|
+
# Handle errors from ActiveRecord operations and convert them to ValidationErrors
|
|
5
|
+
# @param method [Symbol] The operation type: :create, :update, or :delete
|
|
6
|
+
# @return The result of the block execution
|
|
7
|
+
# @raise [ForestAdminDatasourceToolkit::Exceptions::ValidationError] When a database constraint is violated
|
|
8
|
+
def self.handle_errors(method)
|
|
9
|
+
yield
|
|
10
|
+
rescue ActiveRecord::RecordNotUnique => _e
|
|
11
|
+
message = 'The query violates a unicity constraint in the database. ' \
|
|
12
|
+
'Please ensure that you are not duplicating information across records.'
|
|
13
|
+
|
|
14
|
+
raise ForestAdminDatasourceToolkit::Exceptions::ValidationError, message
|
|
15
|
+
rescue ActiveRecord::InvalidForeignKey => _e
|
|
16
|
+
message = build_foreign_key_message(method)
|
|
17
|
+
|
|
18
|
+
raise ForestAdminDatasourceToolkit::Exceptions::ValidationError, message
|
|
19
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
20
|
+
raise ForestAdminDatasourceToolkit::Exceptions::ValidationError, e.message
|
|
21
|
+
rescue StandardError => e # rubocop:disable Lint/DuplicateBranch
|
|
22
|
+
raise ForestAdminDatasourceToolkit::Exceptions::ValidationError, e.message
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.build_foreign_key_message(method)
|
|
26
|
+
base_message = 'The query violates a foreign key constraint in the database. '
|
|
27
|
+
|
|
28
|
+
case method
|
|
29
|
+
when :create, :update
|
|
30
|
+
"#{base_message}Please ensure that you are not linking to a relation which was deleted."
|
|
31
|
+
when :delete
|
|
32
|
+
"#{base_message}Please ensure that no records are linked to the one that you wish to delete."
|
|
33
|
+
else
|
|
34
|
+
base_message
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -93,16 +93,79 @@ module ForestAdminDatasourceActiveRecord
|
|
|
93
93
|
when Operators::NOT_EQUAL, Operators::NOT_IN
|
|
94
94
|
@query = query_aggregator(aggregator, @collection.model.where.not({ field => value }))
|
|
95
95
|
when Operators::GREATER_THAN
|
|
96
|
-
@query = query_aggregator(aggregator,
|
|
96
|
+
@query = query_aggregator(aggregator, build_comparison_query(condition_tree.field, field, value, :gt))
|
|
97
|
+
when Operators::GREATER_THAN_OR_EQUAL
|
|
98
|
+
@query = query_aggregator(aggregator, build_comparison_query(condition_tree.field, field, value, :gteq))
|
|
97
99
|
when Operators::LESS_THAN
|
|
98
|
-
@query = query_aggregator(aggregator,
|
|
100
|
+
@query = query_aggregator(aggregator, build_comparison_query(condition_tree.field, field, value, :lt))
|
|
101
|
+
when Operators::LESS_THAN_OR_EQUAL
|
|
102
|
+
@query = query_aggregator(aggregator, build_comparison_query(condition_tree.field, field, value, :lteq))
|
|
103
|
+
when Operators::CONTAINS
|
|
104
|
+
@query = query_aggregator(aggregator,
|
|
105
|
+
@collection.model.where(@arel_table[field.to_sym].matches("%#{value}%")))
|
|
106
|
+
when Operators::I_CONTAINS
|
|
107
|
+
lower_field = Arel::Nodes::NamedFunction.new('LOWER', [@arel_table[field.to_sym]])
|
|
108
|
+
@query = query_aggregator(aggregator,
|
|
109
|
+
@collection.model.where(lower_field.matches("%#{value.to_s.downcase}%")))
|
|
99
110
|
when Operators::NOT_CONTAINS
|
|
100
111
|
@query = query_aggregator(aggregator,
|
|
101
112
|
@collection.model.where.not(@arel_table[field.to_sym].matches("%#{value}%")))
|
|
113
|
+
when Operators::NOT_I_CONTAINS
|
|
114
|
+
lower_field = Arel::Nodes::NamedFunction.new('LOWER', [@arel_table[field.to_sym]])
|
|
115
|
+
@query = query_aggregator(aggregator,
|
|
116
|
+
@collection.model.where.not(lower_field.matches("%#{value.to_s.downcase}%")))
|
|
117
|
+
when Operators::STARTS_WITH
|
|
118
|
+
@query = query_aggregator(aggregator,
|
|
119
|
+
@collection.model.where(@arel_table[field.to_sym].matches("#{value}%")))
|
|
120
|
+
when Operators::I_STARTS_WITH
|
|
121
|
+
lower_field = Arel::Nodes::NamedFunction.new('LOWER', [@arel_table[field.to_sym]])
|
|
122
|
+
@query = query_aggregator(aggregator,
|
|
123
|
+
@collection.model.where(lower_field.matches("#{value.to_s.downcase}%")))
|
|
124
|
+
when Operators::ENDS_WITH
|
|
125
|
+
@query = query_aggregator(aggregator,
|
|
126
|
+
@collection.model.where(@arel_table[field.to_sym].matches("%#{value}")))
|
|
127
|
+
when Operators::I_ENDS_WITH
|
|
128
|
+
lower_field = Arel::Nodes::NamedFunction.new('LOWER', [@arel_table[field.to_sym]])
|
|
129
|
+
@query = query_aggregator(aggregator,
|
|
130
|
+
@collection.model.where(lower_field.matches("%#{value.to_s.downcase}")))
|
|
102
131
|
when Operators::LIKE
|
|
103
132
|
@query = query_aggregator(aggregator, @collection.model.where(@arel_table[field.to_sym].matches(value)))
|
|
133
|
+
when Operators::I_LIKE
|
|
134
|
+
lower_field = Arel::Nodes::NamedFunction.new('LOWER', [@arel_table[field.to_sym]])
|
|
135
|
+
@query = query_aggregator(aggregator,
|
|
136
|
+
@collection.model.where(lower_field.matches(value.to_s.downcase)))
|
|
137
|
+
when Operators::MATCH
|
|
138
|
+
# Match operator supports:
|
|
139
|
+
# - Regexp objects from pattern transformations: Regexp.new("pattern")
|
|
140
|
+
# - JavaScript regex strings from comparison transformations: "/(pattern)/g"
|
|
141
|
+
pattern = if value.is_a?(Regexp)
|
|
142
|
+
value.source
|
|
143
|
+
elsif (match = value.to_s.match(%r{^/(.+)/[gim]*$}))
|
|
144
|
+
match[1]
|
|
145
|
+
else
|
|
146
|
+
value.to_s
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Use database-specific regex syntax
|
|
150
|
+
adapter_name = @collection.model.connection.adapter_name.downcase
|
|
151
|
+
regex_clause = case adapter_name
|
|
152
|
+
when 'postgresql'
|
|
153
|
+
"#{@arel_table.name}.#{field} ~ ?"
|
|
154
|
+
when 'mysql2', 'mysql', 'sqlite', 'sqlite3'
|
|
155
|
+
"#{@arel_table.name}.#{field} REGEXP ?"
|
|
156
|
+
else
|
|
157
|
+
raise ArgumentError, "Match operator is not supported for database adapter '#{adapter_name}'"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
@query = query_aggregator(aggregator, @collection.model.where(regex_clause, pattern))
|
|
104
161
|
when Operators::INCLUDES_ALL
|
|
105
162
|
@query = query_aggregator(aggregator, @collection.model.where(@arel_table[field.to_sym].matches_all(value)))
|
|
163
|
+
when Operators::SHORTER_THAN
|
|
164
|
+
length_func = Arel::Nodes::NamedFunction.new('LENGTH', [@arel_table[field.to_sym]])
|
|
165
|
+
@query = query_aggregator(aggregator, @collection.model.where(length_func.lt(value)))
|
|
166
|
+
when Operators::LONGER_THAN
|
|
167
|
+
length_func = Arel::Nodes::NamedFunction.new('LENGTH', [@arel_table[field.to_sym]])
|
|
168
|
+
@query = query_aggregator(aggregator, @collection.model.where(length_func.gt(value)))
|
|
106
169
|
end
|
|
107
170
|
|
|
108
171
|
@query
|
|
@@ -161,6 +224,20 @@ module ForestAdminDatasourceActiveRecord
|
|
|
161
224
|
field
|
|
162
225
|
end
|
|
163
226
|
|
|
227
|
+
def build_comparison_query(original_field, formatted_field, value, operator)
|
|
228
|
+
# When comparing a String field with a numeric value, compare the length of the string
|
|
229
|
+
# Otherwise, do a lexicographic comparison
|
|
230
|
+
if value.is_a?(Numeric)
|
|
231
|
+
field_schema = ForestAdminDatasourceToolkit::Utils::Collection.get_field_schema(@collection, original_field)
|
|
232
|
+
if field_schema.column_type == 'String'
|
|
233
|
+
length_func = Arel::Nodes::NamedFunction.new('LENGTH', [@arel_table[formatted_field.to_sym]])
|
|
234
|
+
return @collection.model.where(length_func.send(operator, value))
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
@collection.model.where(@arel_table[formatted_field.to_sym].send(operator, value))
|
|
239
|
+
end
|
|
240
|
+
|
|
164
241
|
def query_aggregator(aggregator, query)
|
|
165
242
|
if !@query.respond_to?(:where_clause) || @query.where_clause.empty?
|
|
166
243
|
query
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: forest_admin_datasource_active_record
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.8.
|
|
4
|
+
version: 1.8.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthieu
|
|
@@ -74,6 +74,7 @@ files:
|
|
|
74
74
|
- lib/forest_admin_datasource_active_record/parser/relation.rb
|
|
75
75
|
- lib/forest_admin_datasource_active_record/parser/validation.rb
|
|
76
76
|
- lib/forest_admin_datasource_active_record/utils/active_record_serializer.rb
|
|
77
|
+
- lib/forest_admin_datasource_active_record/utils/error_handler.rb
|
|
77
78
|
- lib/forest_admin_datasource_active_record/utils/query.rb
|
|
78
79
|
- lib/forest_admin_datasource_active_record/utils/query_aggregate.rb
|
|
79
80
|
- lib/forest_admin_datasource_active_record/version.rb
|