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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6274fc08a8aa3d54112238d7d3833075f01da3096c30902fdd54e81dc87d1e6
4
- data.tar.gz: 3504022220f66b1a92a36b47aeab1f4975173a18cde7178cf59bae3c616db2c2
3
+ metadata.gz: b16fe13a23203f8a9cd4fc0d2d6d2d2ea0e6ae67199eeb3f24e532f5b48ba6b5
4
+ data.tar.gz: 443f1a78c1b248ccd87bf47a9389384ca734c971930afe328370a79b549a0c4d
5
5
  SHA512:
6
- metadata.gz: 41532c05b4cbc18da0aa6123f7ccd4b7675da006601cfdbf899b520eb0c799e7a8f95ea771e00f686f5d2e55a8325cc91e96b4bb78c9f8499a969fb1608782ec
7
- data.tar.gz: 52ada4e24b67a6fe15de5d9faf88850dea62b16627c54ed409638c8a00f32f8037da93a8ccb02f9352120a6f1a2fbd23ef68ba0ef09be63f1da562cbc19e7e47
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::ActiveRecordSerializer.new(@model.create(data)).to_hash(ProjectionFactory.all(self))
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
- entity = Utils::Query.new(self, nil, filter).build.first
39
- entity&.update(data)
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
- entities = Utils::Query.new(self, nil, filter).build
44
- entities&.each(&:destroy)
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 = [Operators::LESS_THAN, Operators::GREATER_THAN]
67
- strings = [Operators::LIKE, Operators::I_LIKE, Operators::NOT_CONTAINS]
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 = result + equality + ['Includes_All'] if type.is_a? Array
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, @collection.model.where(@arel_table[field.to_sym].gt(value)))
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, @collection.model.where(@arel_table[field.to_sym].lt(value)))
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
@@ -1,3 +1,3 @@
1
1
  module ForestAdminDatasourceActiveRecord
2
- VERSION = "1.8.7"
2
+ VERSION = "1.8.8"
3
3
  end
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.7
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