wallaby-active_record 0.2.7 → 0.3.0.beta1

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: 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