wallaby-active_record 0.2.7 → 0.3.0.beta1

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
2
  SHA256:
3
- metadata.gz: 55cc61191c3d68eabf0bb98b4977d10c7d5063736e95cba4b4cb446bf1ecebb7
4
- data.tar.gz: 8d03e8a115f52bd386e87bd6ba2a8a95fdec39743a1a040396f8f1b06ac1c0fe
3
+ metadata.gz: 90fb0c2b0c23402987290d4b290b953de709949ffffcbfc1255e8ef7c5efd653
4
+ data.tar.gz: 67d1cbd6394ed22ff8db981b043d10c719992175630e8e0bcc38d5eec4e42acd
5
5
  SHA512:
6
- metadata.gz: '0685f26c6acf1c511be09cfe68258bce7ca3cd01263047205fcdbfc2e81da7ca1239af3f0725510401d02f6a2c2629003e79a41760a8e672acaa710cd566d8db'
7
- data.tar.gz: fa1497666790dba1d421f1194ce3e3ffc4851e3425a7a2da5701af9f5d4573d1df9bbf3a9ee656aa3490f81ed363af4a6758121d6c3cbd761f3d52707f84f6bb
6
+ metadata.gz: 678e44a61fe8bd550d24924c95fe4b52a51a8e5667f93e46efadb337613e14a99d7befef28d85f2de8d988b6fcf11dc91577640de4246edebb8fba778a639772
7
+ data.tar.gz: 610ef0709fd1c48f770fa2414ba7d64b51b8bc3410b12453a8d3c9a6c1c10fbb62ddb65cd0cd5768415eb8bb2554def31c71a7ad1c11a41490e3cf408466efd1
data/README.md CHANGED
@@ -7,8 +7,19 @@
7
7
  [![Test Coverage](https://api.codeclimate.com/v1/badges/9ba0a610043a2e1a9e74/test_coverage)](https://codeclimate.com/github/wallaby-rails/wallaby-active_record/test_coverage)
8
8
  [![Inch CI](https://inch-ci.org/github/wallaby-rails/wallaby-active_record.svg?branch=master)](https://inch-ci.org/github/wallaby-rails/wallaby-active_record)
9
9
 
10
- Wallaby::ActiveRecord is the ActiveRecord adapter that implements the [Wallaby::Core](https://github.com/wallaby-rails/wallaby-core)
11
- interfaces to handle ActiveRecord model/instance(s) in all the CRUD/authorization/pagination operations.
10
+ `Wallaby::ActiveRecord` adapter is designed to seamlessly integrate ActiveRecord models and instances with the [Wallaby::Core](https://github.com/wallaby-rails/wallaby-core)
11
+ interfaces, enabling smooth handling of CRUD operations, authorization, and pagination.
12
+
13
+ Key features of `Wallaby::ActiveRecord` include:
14
+
15
+ - Offer built-in compatibility with popular authorization frameworks like CanCanCan and Pundit, allowing you to easily manage access control for your ActiveRecord models.
16
+ - Fully support all Rails associations (`belongs_to`, `has_one`, `has_many`, `has_and_belongs_to_many`).
17
+ - All PostgreSQL types supported by Rails are fully supported to ensure smooth handling of data with different types.
18
+ - Facilitate the normalization of data during form submissions, specifically for `binary`, `point` and `range` types.
19
+ - Provide the flexibility to define custom filters for the `index` action, enabling you to create tailored queries based on your specific requirements.
20
+ - Allow sorting for single or multiple columns and provide `nulls` sorting option, enhancing the control and precision of result ordering.
21
+ - Support STI (Single Table Inheritance)
22
+ - Offer advance search capabilities using a convenient colon syntax, empowering users to perform precise searches with ease.
12
23
 
13
24
  ## Install
14
25
 
@@ -29,7 +29,7 @@ module Wallaby
29
29
  # @param klass [Class]
30
30
  # @return [Array<Class>]
31
31
  def sti_list(klass)
32
- (klass.descendants << klass).sort_by(&:name)
32
+ (klass.descendants << klass).sort_by(&:name).uniq
33
33
  end
34
34
 
35
35
  # Find out which parent is the one that can give us the STI list.
@@ -12,28 +12,31 @@ module Wallaby
12
12
 
13
13
  # @return [Hash<String, Hash>] a hash for general fields
14
14
  def general_fields
15
- @model_class.columns.each_with_object({}) do |column, fields|
16
- metadata = {
17
- type: to_type(column).freeze,
18
- label: @model_class.human_attribute_name(column.name)
19
- }
20
- sti_builder.update(metadata, column)
21
- fields[column.name] = metadata
22
- end
15
+ @general_fields ||=
16
+ @model_class.columns.each_with_object({}) do |column, fields|
17
+ metadata = {
18
+ type: to_type(column).freeze,
19
+ label: @model_class.human_attribute_name(column.name)
20
+ }
21
+ sti_builder.update(metadata, column)
22
+ fields[column.name] = metadata
23
+ end
23
24
  end
24
25
 
25
26
  # @return [Hash<String, Hash>] a hash for association fields
26
27
  # (e.g. belongs_to / has_one / has_many / has_and_belongs_to_many)
27
28
  def association_fields
28
- @model_class.reflections.each_with_object({}) do |(name, ref), fields|
29
- metadata = {
30
- type: ref.macro.to_s,
31
- label: @model_class.human_attribute_name(name)
32
- }
33
- association_builder.update(metadata, ref)
34
- polymorphic_builder.update(metadata, ref)
35
- fields[name] = metadata
36
- end
29
+ @association_fields ||=
30
+ @model_class.reflections.each_with_object({}) do |(name, reflection), fields|
31
+ metadata = {
32
+ type: reflection.macro.to_s, # association type
33
+ label: @model_class.human_attribute_name(name)
34
+ }
35
+ association_builder.update(metadata, reflection)
36
+ polymorphic_builder.update(metadata, reflection)
37
+ update_general_fields_with(metadata)
38
+ fields[name] = metadata
39
+ end
37
40
  end
38
41
 
39
42
  protected
@@ -61,6 +64,21 @@ module Wallaby
61
64
  def polymorphic_builder
62
65
  @polymorphic_builder ||= PolymorphicBuilder.new
63
66
  end
67
+
68
+ # @param metadata [Hash] association metadata
69
+ def update_general_fields_with(metadata)
70
+ metadata[:foreign_key].try do |key|
71
+ general_fields[key].try do |general_metadata|
72
+ general_metadata[:is_foreign_key] = general_metadata[:hidden] = true
73
+ end
74
+ end
75
+
76
+ metadata[:polymorphic_type].try do |key|
77
+ general_fields[key].try do |general_metadata|
78
+ general_metadata[:is_polymorphic_type] = general_metadata[:hidden] = true
79
+ end
80
+ end
81
+ end
64
82
  end
65
83
  end
66
84
  end
@@ -5,7 +5,7 @@ module Wallaby
5
5
  class ModelDecorator
6
6
  # Try to find the field that can be used as title
7
7
  class TitleFieldFinder
8
- TITLE_FIELD_TYPES = %w(string).freeze
8
+ TITLE_FIELD_TYPES = %w[string].freeze
9
9
 
10
10
  # @param model_class [Class]
11
11
  # @param fields [Hash] fields metadata
@@ -5,16 +5,16 @@ module Wallaby
5
5
  # Modal decorator for {Wallaby::ActiveRecord}
6
6
  class ModelDecorator < ::Wallaby::ModelDecorator
7
7
  # Data types to exclude for {#index_field_names}
8
- INDEX_EXCLUSIVE_DATA_TYPES = %w(
8
+ INDEX_EXCLUSIVE_DATA_TYPES = %w[
9
9
  binary citext hstore json jsonb tsvector xml
10
10
  blob mediumblob longblob text mediumtext longtext
11
- ).freeze
11
+ ].freeze
12
12
 
13
13
  # Classes to exclude for {#show_field_names}
14
- SHOW_EXCLUSIVE_CLASS_NAMES = %w(ActiveStorage::Attachment ActiveStorage::Blob).freeze
14
+ SHOW_EXCLUSIVE_CLASS_NAMES = %w[ActiveStorage::Attachment ActiveStorage::Blob].freeze
15
15
 
16
16
  # Fields to exclude for {#form_field_names}
17
- FORM_EXCLUSIVE_DATA_TYPES = %w(created_at updated_at).freeze
17
+ FORM_EXCLUSIVE_DATA_TYPES = %w[created_at updated_at].freeze
18
18
 
19
19
  # Original metadata information of the primative and association fields
20
20
  # pulling out from the ActiveRecord model.
@@ -45,46 +45,49 @@ module Wallaby
45
45
  @fields ||= ::ActiveSupport::HashWithIndifferentAccess.new.tap do |hash|
46
46
  next hash.default = {} unless @model_class.table_exists?
47
47
 
48
- hash.merge! general_fields
49
- hash.merge! association_fields
50
- hash.except!(*foreign_keys_from_associations)
48
+ hash.merge!(association_fields)
49
+ hash.merge!(general_fields)
50
+ rescue ::ActiveRecord::NoDatabaseError
51
+ hash.default = {}
51
52
  end.freeze
52
- rescue ::ActiveRecord::NoDatabaseError
53
- Hash.new({}).with_indifferent_access
54
53
  end
55
54
 
56
55
  # A copy of {#fields} for index page
57
56
  # @return [ActiveSupport::HashWithIndifferentAccess] metadata
58
57
  def index_fields
59
- @index_fields ||= Utils.clone fields
58
+ @index_fields ||= Utils.clone(fields)
60
59
  end
61
60
 
62
61
  # A copy of {#fields} for show page
63
62
  # @return [ActiveSupport::HashWithIndifferentAccess] metadata
64
63
  def show_fields
65
- @show_fields ||= Utils.clone fields
64
+ @show_fields ||= Utils.clone(fields)
66
65
  end
67
66
 
68
67
  # A copy of {#fields} for form (new/edit) page
69
68
  # @return [ActiveSupport::HashWithIndifferentAccess] metadata
70
69
  def form_fields
71
- @form_fields ||= Utils.clone fields
70
+ @form_fields ||= Utils.clone(fields)
72
71
  end
73
72
 
74
73
  # @return [Array<String>] a list of field names for index page (note: only primitive SQL types are included).
75
74
  def index_field_names
76
75
  @index_field_names ||=
77
- index_fields.reject do |_field_name, metadata|
78
- metadata[:is_association] \
79
- || INDEX_EXCLUSIVE_DATA_TYPES.include?(metadata[:type])
80
- end.keys
76
+ reposition(
77
+ index_fields.reject do |_field_name, metadata|
78
+ metadata[:hidden] || # e.g. foreign keys, polymorphic columns
79
+ metadata[:is_association] || # associations
80
+ INDEX_EXCLUSIVE_DATA_TYPES.include?(metadata[:type]) # not the types for index page
81
+ end.keys
82
+ )
81
83
  end
82
84
 
83
85
  # @return [Array<String>] a list of field names for show page (note: **ActiveStorage** fields are excluded).
84
86
  def show_field_names
85
87
  @show_field_names ||=
86
88
  show_fields.reject do |_field_name, metadata|
87
- SHOW_EXCLUSIVE_CLASS_NAMES.include? metadata[:class].try(:name)
89
+ metadata[:hidden] || # e.g. foreign keys, polymorphic columns
90
+ SHOW_EXCLUSIVE_CLASS_NAMES.include?(metadata[:class].try(:name)) # not the types for show page
88
91
  end.keys
89
92
  end
90
93
 
@@ -93,9 +96,11 @@ module Wallaby
93
96
  def form_field_names
94
97
  @form_field_names ||=
95
98
  form_fields.reject do |field_name, metadata|
96
- field_name == primary_key \
97
- || FORM_EXCLUSIVE_DATA_TYPES.include?(field_name) \
98
- || metadata[:has_scope] || metadata[:is_through]
99
+ field_name == primary_key || # primary key
100
+ metadata[:has_scope] || # not a regular association
101
+ metadata[:is_through] || # not direct association
102
+ metadata[:hidden] || # e.g. foreign keys, polymorphic columns
103
+ FORM_EXCLUSIVE_DATA_TYPES.include?(field_name) # not the types for form page
99
104
  end.keys
100
105
  end
101
106
 
@@ -106,7 +111,17 @@ module Wallaby
106
111
 
107
112
  # @return [String] primary key for the resource
108
113
  def primary_key
109
- @primary_key ||= @model_class.primary_key
114
+ @primary_key ||=
115
+ @model_class.primary_key || Wallaby::Logger.warn(<<~WARNING)
116
+ No primary key is found and all resource pages (show/edit) will fail to build.
117
+
118
+ If the resource pages are needed, try:
119
+
120
+ - Set `self.primary_key=` in the model #{@model_class}
121
+ - Set `self.primary_key=` in the decorator
122
+
123
+ Otherwise, configure to disable access to new/show/edit/destroy
124
+ WARNING
110
125
  end
111
126
 
112
127
  # To guess the title for resource.
@@ -117,20 +132,21 @@ module Wallaby
117
132
  # @param resource [Object]
118
133
  # @return [String] the title of given resource
119
134
  def guess_title(resource)
120
- resource.try title_field_finder.find
135
+ title_field_finder.find.try do |title_method|
136
+ resource.try(title_method)
137
+ end
121
138
  end
122
139
 
123
140
  protected
124
141
 
125
142
  # @return [Wallaby::ActiveRecord::ModelDecorator::FieldsBuilder]
126
143
  def field_builder
127
- @field_builder ||= FieldsBuilder.new @model_class
144
+ @field_builder ||= FieldsBuilder.new(@model_class)
128
145
  end
129
146
 
130
147
  # @return [Wallaby::ActiveRecord::ModelDecorator::TitleFieldFinder]
131
148
  def title_field_finder
132
- @title_field_finder ||=
133
- TitleFieldFinder.new @model_class, general_fields
149
+ @title_field_finder ||= TitleFieldFinder.new(@model_class, general_fields)
134
150
  end
135
151
 
136
152
  # @!method general_fields
@@ -141,17 +157,6 @@ module Wallaby
141
157
  # (see Wallaby::ActiveRecord::ModelDecorator::FieldsBuilder#association_fields)
142
158
  # @see Wallaby::ActiveRecord::ModelDecorator::FieldsBuilder#association_fields
143
159
  delegate :general_fields, :association_fields, to: :field_builder
144
-
145
- # Find out all the foreign keys for association fields
146
- # @param fields [Hash] metadata of fields
147
- # @return [Array<String>] a list of foreign keys
148
- def foreign_keys_from_associations(fields = association_fields)
149
- fields.each_with_object([]) do |(_field_name, metadata), keys|
150
- keys << metadata[:foreign_key] if metadata[:foreign_key]
151
- keys << metadata[:polymorphic_type] if metadata[:polymorphic_type]
152
- keys
153
- end
154
- end
155
160
  end
156
161
  end
157
162
  end
@@ -12,7 +12,7 @@ module Wallaby
12
12
  # @return [Array<Class>]
13
13
  def all
14
14
  ::ActiveRecord::Base.descendants.reject do |model_class|
15
- defined?(::ApplicationRecord) && model_class == ::ApplicationRecord ||
15
+ application_record?(model_class) ||
16
16
  model_class.abstract_class? ||
17
17
  anonymous?(model_class) ||
18
18
  model_class.name.index('HABTM') ||
@@ -22,6 +22,10 @@ module Wallaby
22
22
 
23
23
  protected
24
24
 
25
+ def application_record?(model_class)
26
+ defined?(::ApplicationRecord) && model_class == ::ApplicationRecord
27
+ end
28
+
25
29
  # @param model_class [Class]
26
30
  # @see Wallaby::ModuleUtils.anonymous_class?
27
31
  def anonymous?(model_class)
@@ -15,12 +15,12 @@ module Wallaby
15
15
 
16
16
  # @return [Integer] total count for the collection
17
17
  def total
18
- @collection.unscope(:offset, :limit).count
18
+ @collection.unscope(:offset, :limit).count # rubocop:disable CodeReuse/ActiveRecord
19
19
  end
20
20
 
21
21
  # @return [Integer] page size from parameters or Wallaby configuration
22
22
  def page_size
23
- (@params[:per] || Wallaby.configuration.pagination.page_size).to_i
23
+ @params[:per].to_i
24
24
  end
25
25
 
26
26
  # @return [Integer] page number from parameters starting from 1
@@ -14,11 +14,12 @@ module Wallaby
14
14
  def normalize(params)
15
15
  params.each do |field_name, values|
16
16
  metadata =
17
- @model_decorator.metadata_of(field_name).presence || @model_decorator.form_metadata_of(field_name)
17
+ @model_decorator.metadata_of(field_name).presence ||
18
+ @model_decorator.form_metadata_of(field_name)
18
19
  type = metadata[:type].try(:[], /range|point|binary/)
19
20
  next unless type
20
21
 
21
- public_send "normalize_#{type}_values", params, field_name, values
22
+ try "normalize_#{type}_values", params, field_name, values
22
23
  end
23
24
  params
24
25
  end
@@ -29,7 +30,7 @@ module Wallaby
29
30
  # @param values [Array]
30
31
  def normalize_range_values(params, field_name, values)
31
32
  normalized = Array(values).map(&:presence).compact
32
- params[field_name] = (normalized.present? && values.length == 2) && (values.first...values.last) || nil
33
+ params[field_name] = ((normalized.present? && values.length == 2) && (values.first...values.last)) || nil
33
34
  end
34
35
 
35
36
  # Turn values into points
@@ -38,7 +39,7 @@ module Wallaby
38
39
  # @param values [Array]
39
40
  def normalize_point_values(params, field_name, values)
40
41
  normalized = Array(values).map(&:presence).compact
41
- params[field_name] = normalized.present? && values.map(&:to_f) || nil
42
+ params[field_name] = (normalized.present? && values.map(&:to_f)) || nil
42
43
  end
43
44
 
44
45
  # Turn values into binary
@@ -46,7 +47,7 @@ module Wallaby
46
47
  # @param field_name [String]
47
48
  # @param values [Object]
48
49
  def normalize_binary_values(params, field_name, values)
49
- params[field_name] = values.is_a?(::ActionDispatch::Http::UploadedFile) && values.read || nil
50
+ params[field_name] = (values.is_a?(::ActionDispatch::Http::UploadedFile) && values.read) || nil
50
51
  end
51
52
  end
52
53
  end
@@ -3,7 +3,7 @@
3
3
  module Wallaby
4
4
  class ActiveRecord
5
5
  class ModelServiceProvider
6
- # Whitelist the params for mass-assignment
6
+ # Allowlist the params for mass-assignment
7
7
  class Permitter
8
8
  # @param model_decorator [Wallaby::ModelDecorator]
9
9
  def initialize(model_decorator)
@@ -14,11 +14,11 @@ module Wallaby
14
14
  def simple_field_names
15
15
  field_names =
16
16
  non_range_fields.keys +
17
- belongs_to_fields.map do |_, metadata|
17
+ belongs_to_fields.flat_map do |_, metadata|
18
18
  [metadata[:foreign_key], metadata[:polymorphic_type]]
19
- end.flatten.compact
19
+ end.compact
20
20
  fields = [@model_decorator.primary_key, 'created_at', 'updated_at']
21
- field_names.reject { |field_name| fields.include? field_name }
21
+ field_names.reject { |field_name| fields.include?(field_name) }.uniq
22
22
  end
23
23
 
24
24
  # @return [Array<String>] a list of field names of range and association
@@ -55,7 +55,7 @@ module Wallaby
55
55
  # - has_many
56
56
  # - has_and_belongs_to_many
57
57
  def many_association_fields
58
- association_fields.select { |_, metadata| /many/ =~ metadata[:type] }
58
+ association_fields.select { |_, metadata| metadata[:type].include?('many') }
59
59
  end
60
60
 
61
61
  # @return [Array<String>] a list of belongs_to association field names
@@ -93,8 +93,10 @@ module Wallaby
93
93
  if right.include? nil
94
94
  exps.push left: lefted, op: SIMPLE_OPERATORS[oped], right: right.delete(nil), join: join
95
95
  end
96
+
96
97
  exps.push left: lefted, op: operator, right: right, join: join
97
98
  end
99
+
98
100
  exps
99
101
  end
100
102
 
@@ -9,6 +9,7 @@ module Wallaby
9
9
  # with non-{Wallaby::ActiveRecord::ModelServiceProvider::Querier::Transformer} result
10
10
  class Wrapper
11
11
  attr_reader :list
12
+
12
13
  delegate :push, to: :list
13
14
  delegate :each, to: :list
14
15
  delegate :last, to: :list
@@ -5,7 +5,7 @@ module Wallaby
5
5
  class ModelServiceProvider
6
6
  # Query builder
7
7
  class Querier
8
- TEXT_FIELDS = %w(string text citext longtext tinytext mediumtext).freeze
8
+ TEXT_FIELDS = %w[string text citext longtext tinytext mediumtext].freeze
9
9
 
10
10
  # @param model_decorator [Wallaby::ModelDecorator]
11
11
  def initialize(model_decorator)
@@ -19,11 +19,26 @@ module Wallaby
19
19
  # @param params [ActionController::Parameters]
20
20
  # @return [ActiveRecord::Relation]
21
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
22
+ filter_name, keywords, field_queries = extract(params)
23
+ scope = filtered_by(filter_name)
24
+ scope = preload_associations_for(scope)
25
+ arel_query = text_search(keywords)
26
+ arel_query = field_search(field_queries, arel_query)
27
+ scope.where(arel_query) # rubocop:disable CodeReuse/ActiveRecord
28
+ end
29
+
30
+ # Extract the sorting from parameters `sort`,
31
+ # and combine with metadata option `:nulls`
32
+ # to execute order for the given scope.
33
+ # @param sort_string [String]
34
+ # @param scope [ActiveRecord::Relation]
35
+ # @return [ActiveRecord::Relation]
36
+ def sort(sort_string, scope)
37
+ return scope if sort_string.blank?
38
+
39
+ sort_hash = normalize_sort(Sorting::HashBuilder.build(sort_string))
40
+ sorting = Sorting::HashBuilder.to_str(sort_hash)
41
+ scope.order(Arel.sql(sorting)) # rubocop:disable CodeReuse/ActiveRecord
27
42
  end
28
43
 
29
44
  protected
@@ -53,11 +68,13 @@ module Wallaby
53
68
  valid_filter_name =
54
69
  FilterUtils.filter_name_by(filter_name, @model_decorator.filters)
55
70
  scope = find_scope(valid_filter_name)
56
- if scope.blank? then unscoped
57
- elsif scope.is_a?(Proc) then @model_class.instance_exec(&scope)
71
+ return unscoped if scope.blank?
72
+
73
+ if scope.is_a?(Proc) then @model_class.instance_exec(&scope)
58
74
  elsif @model_class.respond_to?(scope)
59
- @model_class.public_send(scope)
60
- else unscoped
75
+ @model_class.try(scope)
76
+ else
77
+ unscoped
61
78
  end
62
79
  end
63
80
 
@@ -72,15 +89,28 @@ module Wallaby
72
89
 
73
90
  # @return [ActiveRecord::Relation] Unscoped query
74
91
  def unscoped
75
- @model_class.where nil
92
+ @model_class.where nil # rubocop:disable CodeReuse/ActiveRecord
93
+ end
94
+
95
+ # Preload association for the scope
96
+ # @param scope [ActiveRecord::Relation]
97
+ # @return [ActiveRecord::Relation]
98
+ def preload_associations_for(scope)
99
+ index_associations = index_field_names.select do |field_name|
100
+ @model_decorator.fields.dig(field_name, :is_association)
101
+ end
102
+
103
+ return scope if index_associations.blank?
104
+
105
+ scope.preload(*index_associations) # rubocop:disable CodeReuse/ActiveRecord
76
106
  end
77
107
 
78
- # Search text for the text columns (see {}) in `index_field_names`
108
+ # Search text for the text columns (see {}) in {#index_field_names}
79
109
  # @param keywords [String] keywords
80
- # @param query [ActiveRecord::Relation, nil]
81
- # @return [ActiveRecord::Relation, nil]
82
- def text_search(keywords, query = nil)
83
- return query unless keywords_check? keywords
110
+ # @param arel_query [Arel::Nodes::Node, nil]
111
+ # @return [Arel::Nodes::Node, nil]
112
+ def text_search(keywords, arel_query = nil)
113
+ return arel_query unless keywords_check?(keywords)
84
114
 
85
115
  text_fields.each do |field_name|
86
116
  sub_query = nil
@@ -88,34 +118,31 @@ module Wallaby
88
118
  exp = table[field_name].matches(Escaper.execute(keyword))
89
119
  sub_query = sub_query.try(:and, exp) || exp
90
120
  end
91
- query = query.try(:or, sub_query) || sub_query
121
+ arel_query = arel_query.try(:or, sub_query) || sub_query
92
122
  end
93
- query
123
+ arel_query
94
124
  end
95
125
 
96
126
  # Perform SQL query for the colon query (e.g. data:<2000-01-01)
97
127
  # @param field_queries [Array]
98
- # @param query [ActiveRecord::Relation]
99
- # @return [ActiveRecord::Relation]
100
- def field_search(field_queries, query)
101
- return query unless field_check? field_queries
128
+ # @param arel_query [Arel::Nodes::Node]
129
+ # @return [Arel::Nodes::Node]
130
+ def field_search(field_queries, arel_query)
131
+ return arel_query unless field_check?(field_queries)
102
132
 
103
133
  field_queries.each do |exps|
104
- sub_queries = build_sub_queries_with exps
105
- query = query.try(:and, sub_queries) || sub_queries
134
+ sub_queries = build_sub_queries_with(exps)
135
+ arel_query = arel_query.try(:and, sub_queries) || sub_queries
106
136
  end
107
- query
137
+ arel_query
108
138
  end
109
139
 
110
- # @return [Array<String>] a list of text fields from `index_field_names`
140
+ # @return [Array<String>] a list of text fields from {#index_field_names}
111
141
  def text_fields
112
- @text_fields ||= begin
113
- index_field_names = @model_decorator.index_field_names.map(&:to_s)
114
- @model_decorator.fields.select do |field_name, metadata|
115
- index_field_names.include?(field_name) &&
116
- TEXT_FIELDS.include?(metadata[:type].to_s)
117
- end.keys
118
- end
142
+ @text_fields ||= @model_decorator.fields.select do |field_name, metadata|
143
+ index_field_names.include?(field_name) &&
144
+ TEXT_FIELDS.include?(metadata[:type].to_s)
145
+ end.keys
119
146
  end
120
147
 
121
148
  # @param keywords [Array<String>] a list of keywords
@@ -153,6 +180,30 @@ module Wallaby
153
180
  end
154
181
  query
155
182
  end
183
+
184
+ def normalize_sort(hash)
185
+ sanitized =
186
+ hash.reject do |name, _sort|
187
+ @model_decorator.fields[name].blank? || # not a column or association?
188
+ @model_decorator.index_fields[name][:sort_disabled] || # sort disabled?
189
+ @model_decorator.index_field_names.exclude?(name) # not included?
190
+ end
191
+ sanitized.each_with_object({}) do |(field_name, value), normalized|
192
+ column_name = "#{@model_class.table_name}.#{field_name}"
193
+ normalized[column_name] =
194
+ case nulls_order = @model_decorator.index_fields.dig(field_name, :nulls)
195
+ when :first, :last, 'first', 'last'
196
+ "#{value} nulls #{nulls_order}"
197
+ else
198
+ value
199
+ end.upcase
200
+ end
201
+ end
202
+
203
+ # @return [Array<String>]
204
+ def index_field_names
205
+ @index_field_names ||= @model_decorator.index_field_names.map(&:to_s)
206
+ end
156
207
  end
157
208
  end
158
209
  end
@@ -30,7 +30,7 @@ module Wallaby
30
30
  # @return [false] otherwise
31
31
  def valid_range_type?(values, metadata)
32
32
  !metadata \
33
- || !%w(daterange tsrange tstzrange).include?(metadata[:type]) \
33
+ || %w[daterange tsrange tstzrange].exclude?(metadata[:type]) \
34
34
  || !values.try(:any?, &:blank?)
35
35
  end
36
36
  end
@@ -7,9 +7,9 @@ module Wallaby
7
7
  # @param params [ActionController::Parameters]
8
8
  # @param action [String, Symbol]
9
9
  # @param authorizer
10
- # @return [ActionController::Parameters] whitelisted parameters
10
+ # @return [ActionController::Parameters] allowlisted parameters
11
11
  def permit(params, action, authorizer)
12
- authorized_fields = authorizer.permit_params action, @model_class
12
+ authorized_fields = authorizer.permit_params(action, @model_class)
13
13
  params.require(param_key).permit(authorized_fields || permitted_fields)
14
14
  end
15
15
 
@@ -18,19 +18,18 @@ module Wallaby
18
18
  # @param authorizer [Ability] for now
19
19
  # @return [ActiveRecord::Relation] relation
20
20
  def collection(params, authorizer)
21
- query = querier.search params
22
- query = query.order params[:sort] if params[:sort].present?
23
- authorizer.accessible_for :index, query
21
+ query = querier.search(params)
22
+ query = querier.sort(params[:sort], query)
23
+ authorizer.accessible_for(:index, query)
24
24
  end
25
25
 
26
26
  # @param query [ActiveRecord::Relation]
27
27
  # @param params [ActionController::Parameters]
28
28
  # @return [ActiveRecord::Relation] paginated query
29
29
  def paginate(query, params)
30
- per = (params[:per] || Wallaby.configuration.pagination.page_size).to_i
30
+ per = (params[:per] || Wallaby.controller_configuration.try(:page_size)).to_i
31
31
  page = [params[:page].to_i, 1].max # starting from page 1
32
- query = query.offset((page - 1) * per).limit(per)
33
- query
32
+ query.offset((page - 1) * per).limit(per) # rubocop:disable CodeReuse/ActiveRecord
34
33
  end
35
34
 
36
35
  # @note No mass assignment happens here!
@@ -45,7 +44,7 @@ module Wallaby
45
44
  # @return [Object] persisted resource object
46
45
  # @raise [Wallaby::ResourceNotFound] when record is not found
47
46
  def find(id, _params, _authorizer)
48
- @model_class.find id
47
+ @model_class.find(id)
49
48
  rescue ::ActiveRecord::RecordNotFound
50
49
  raise ResourceNotFound, id
51
50
  end
@@ -55,7 +54,7 @@ module Wallaby
55
54
  # @param params [ActionController::Parameters]
56
55
  # @param authorizer [Wallaby::ModelAuthorizer]
57
56
  def create(resource, params, authorizer)
58
- save __callee__, resource, params, authorizer
57
+ save(__callee__, resource, params, authorizer)
59
58
  end
60
59
 
61
60
  # Assign resource with new values and store it in database as an update.
@@ -63,7 +62,7 @@ module Wallaby
63
62
  # @param params [ActionController::Parameters]
64
63
  # @param authorizer [Wallaby::ModelAuthorizer]
65
64
  def update(resource, params, authorizer)
66
- save __callee__, resource, params, authorizer
65
+ save(__callee__, resource, params, authorizer)
67
66
  end
68
67
 
69
68
  # Remove a record from database
@@ -82,19 +81,19 @@ module Wallaby
82
81
  # @return resource itself
83
82
  # @raise [ActiveRecord::StatementInvalid, ActiveModel::UnknownAttributeError, ActiveRecord::UnknownAttributeError]
84
83
  def save(action, resource, params, authorizer)
85
- resource.assign_attributes normalize params
86
- ensure_attributes_for authorizer, action, resource
87
- resource.save if valid? resource
84
+ resource.assign_attributes(normalize(params))
85
+ ensure_attributes_for(authorizer, action, resource)
86
+ resource.save if valid?(resource)
88
87
  resource
89
88
  rescue ::ActiveRecord::ActiveRecordError, ActiveModel::ForbiddenAttributesError, unknown_attribute_error => e
90
- resource.errors.add :base, e.message
89
+ resource.errors.add(:base, e.message)
91
90
  resource
92
91
  end
93
92
 
94
93
  # Normalize params
95
94
  # @param params [ActionController::Parameters]
96
95
  def normalize(params)
97
- normalizer.normalize params
96
+ normalizer.normalize(params)
98
97
  end
99
98
 
100
99
  # See if a resource is valid
@@ -102,7 +101,7 @@ module Wallaby
102
101
  # @return [true] if valid
103
102
  # @return [false] otherwise
104
103
  def valid?(resource)
105
- validator.valid? resource
104
+ validator.valid?(resource)
106
105
  end
107
106
 
108
107
  # To make sure that the record can be updated with the values that are
@@ -113,8 +112,8 @@ module Wallaby
113
112
  def ensure_attributes_for(authorizer, action, resource)
114
113
  return if authorizer.blank?
115
114
 
116
- restricted_conditions = authorizer.attributes_for action, resource
117
- resource.assign_attributes restricted_conditions
115
+ restricted_conditions = authorizer.attributes_for(action, resource)
116
+ resource.assign_attributes(restricted_conditions)
118
117
  end
119
118
 
120
119
  # @return [String] param key
@@ -122,7 +121,7 @@ module Wallaby
122
121
  @model_class.model_name.param_key
123
122
  end
124
123
 
125
- # @return [Array] the list of attributes to whitelist for mass assignment
124
+ # @return [Array] the list of attributes to allowlist for mass assignment
126
125
  def permitted_fields
127
126
  @permitted_fields ||=
128
127
  permitter.simple_field_names << permitter.compound_hashed_fields
@@ -130,22 +129,22 @@ module Wallaby
130
129
 
131
130
  # @return [Wallaby::ActiveRecord::ModelServiceProvider::Permitter]
132
131
  def permitter
133
- @permitter ||= Permitter.new @model_decorator
132
+ @permitter ||= Permitter.new(@model_decorator)
134
133
  end
135
134
 
136
135
  # @return [Wallaby::ActiveRecord::ModelServiceProvider::Querier]
137
136
  def querier
138
- @querier ||= Querier.new @model_decorator
137
+ @querier ||= Querier.new(@model_decorator)
139
138
  end
140
139
 
141
140
  # @return [Wallaby::ActiveRecord::ModelServiceProvider::Normalizer]
142
141
  def normalizer
143
- @normalizer ||= Normalizer.new @model_decorator
142
+ @normalizer ||= Normalizer.new(@model_decorator)
144
143
  end
145
144
 
146
145
  # @return [Wallaby::ActiveRecord::ModelServiceProvider::Validator]
147
146
  def validator
148
- @validator ||= Validator.new @model_decorator
147
+ @validator ||= Validator.new(@model_decorator)
149
148
  end
150
149
 
151
150
  # @return [Class] ActiveModel::UnknownAttributeError if Rails 4
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Wallaby
4
4
  module ActiveRecordGem
5
- VERSION = '0.2.7' # :nodoc:
5
+ VERSION = '0.3.0.beta1' # :nodoc:
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wallaby-active_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.3.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tian Chen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-02 00:00:00.000000000 Z
11
+ date: 2023-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: wallaby-core
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 0.2.3
33
+ version: 0.3.0.beta1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 0.2.3
40
+ version: 0.3.0.beta1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: cancancan
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -132,6 +132,7 @@ metadata:
132
132
  homepage_uri: https://github.com/wallaby-rails/wallaby-active_record
133
133
  source_code_uri: https://github.com/wallaby-rails/wallaby-active_record
134
134
  changelog_uri: https://github.com/wallaby-rails/wallaby-active_record/blob/master/CHANGELOG.md
135
+ rubygems_mfa_required: 'true'
135
136
  post_install_message:
136
137
  rdoc_options: []
137
138
  require_paths:
@@ -143,11 +144,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
143
144
  version: '0'
144
145
  required_rubygems_version: !ruby/object:Gem::Requirement
145
146
  requirements:
146
- - - ">="
147
+ - - ">"
147
148
  - !ruby/object:Gem::Version
148
- version: '0'
149
+ version: 1.3.1
149
150
  requirements: []
150
- rubygems_version: 3.1.2
151
+ rubygems_version: 3.3.7
151
152
  signing_key:
152
153
  specification_version: 4
153
154
  summary: Wallaby's ActiveRecord ORM adapter