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 +4 -4
- data/README.md +13 -2
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder/sti_builder.rb +1 -1
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder.rb +35 -17
- data/lib/adapters/wallaby/active_record/model_decorator/title_field_finder.rb +1 -1
- data/lib/adapters/wallaby/active_record/model_decorator.rb +41 -36
- data/lib/adapters/wallaby/active_record/model_finder.rb +5 -1
- data/lib/adapters/wallaby/active_record/model_pagination_provider.rb +2 -2
- data/lib/adapters/wallaby/active_record/model_service_provider/normalizer.rb +6 -5
- data/lib/adapters/wallaby/active_record/model_service_provider/permitter.rb +5 -5
- data/lib/adapters/wallaby/active_record/model_service_provider/querier/transformer.rb +2 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/querier/wrapper.rb +1 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/querier.rb +84 -33
- data/lib/adapters/wallaby/active_record/model_service_provider/validator.rb +1 -1
- data/lib/adapters/wallaby/active_record/model_service_provider.rb +23 -24
- data/lib/wallaby/active_record/version.rb +1 -1
- metadata +10 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90fb0c2b0c23402987290d4b290b953de709949ffffcbfc1255e8ef7c5efd653
|
4
|
+
data.tar.gz: 67d1cbd6394ed22ff8db981b043d10c719992175630e8e0bcc38d5eec4e42acd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
11
|
-
interfaces
|
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
|
-
@
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
@
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
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
|
-
|
11
|
+
].freeze
|
12
12
|
|
13
13
|
# Classes to exclude for {#show_field_names}
|
14
|
-
SHOW_EXCLUSIVE_CLASS_NAMES = %w
|
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
|
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!
|
49
|
-
hash.merge!
|
50
|
-
|
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
|
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
|
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
|
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
|
-
|
78
|
-
metadata
|
79
|
-
|
80
|
-
|
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
|
-
|
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
|
-
||
|
98
|
-
|
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 ||=
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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 ||
|
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
|
-
|
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
|
-
#
|
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.
|
17
|
+
belongs_to_fields.flat_map do |_, metadata|
|
18
18
|
[metadata[:foreign_key], metadata[:polymorphic_type]]
|
19
|
-
end.
|
19
|
+
end.compact
|
20
20
|
fields = [@model_decorator.primary_key, 'created_at', 'updated_at']
|
21
|
-
field_names.reject { |field_name| fields.include?
|
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|
|
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
|
@@ -5,7 +5,7 @@ module Wallaby
|
|
5
5
|
class ModelServiceProvider
|
6
6
|
# Query builder
|
7
7
|
class Querier
|
8
|
-
TEXT_FIELDS = %w
|
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
|
23
|
-
scope = filtered_by
|
24
|
-
|
25
|
-
|
26
|
-
|
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?
|
57
|
-
|
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.
|
60
|
-
else
|
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
|
108
|
+
# Search text for the text columns (see {}) in {#index_field_names}
|
79
109
|
# @param keywords [String] keywords
|
80
|
-
# @param
|
81
|
-
# @return [
|
82
|
-
def text_search(keywords,
|
83
|
-
return
|
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
|
-
|
121
|
+
arel_query = arel_query.try(:or, sub_query) || sub_query
|
92
122
|
end
|
93
|
-
|
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
|
99
|
-
# @return [
|
100
|
-
def field_search(field_queries,
|
101
|
-
return
|
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
|
105
|
-
|
134
|
+
sub_queries = build_sub_queries_with(exps)
|
135
|
+
arel_query = arel_query.try(:and, sub_queries) || sub_queries
|
106
136
|
end
|
107
|
-
|
137
|
+
arel_query
|
108
138
|
end
|
109
139
|
|
110
|
-
# @return [Array<String>] a list of text fields from
|
140
|
+
# @return [Array<String>] a list of text fields from {#index_field_names}
|
111
141
|
def text_fields
|
112
|
-
@text_fields ||=
|
113
|
-
index_field_names
|
114
|
-
|
115
|
-
|
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
|
-
||
|
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]
|
10
|
+
# @return [ActionController::Parameters] allowlisted parameters
|
11
11
|
def permit(params, action, authorizer)
|
12
|
-
authorized_fields = authorizer.permit_params
|
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
|
22
|
-
query =
|
23
|
-
authorizer.accessible_for
|
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.
|
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
|
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
|
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
|
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
|
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
|
86
|
-
ensure_attributes_for
|
87
|
-
resource.save if valid?
|
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
|
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
|
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?
|
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
|
117
|
-
resource.assign_attributes
|
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
|
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
|
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
|
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
|
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
|
147
|
+
@validator ||= Validator.new(@model_decorator)
|
149
148
|
end
|
150
149
|
|
151
150
|
# @return [Class] ActiveModel::UnknownAttributeError if Rails 4
|
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.
|
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:
|
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.
|
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.
|
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:
|
149
|
+
version: 1.3.1
|
149
150
|
requirements: []
|
150
|
-
rubygems_version: 3.
|
151
|
+
rubygems_version: 3.3.7
|
151
152
|
signing_key:
|
152
153
|
specification_version: 4
|
153
154
|
summary: Wallaby's ActiveRecord ORM adapter
|