wallaby-active_record 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +31 -0
- data/lib/adapters/wallaby/active_record/cancancan_provider.rb +9 -0
- data/lib/adapters/wallaby/active_record/default_provider.rb +9 -0
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder/association_builder.rb +52 -0
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder/polymorphic_builder.rb +52 -0
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder/sti_builder.rb +50 -0
- data/lib/adapters/wallaby/active_record/model_decorator/fields_builder.rb +66 -0
- data/lib/adapters/wallaby/active_record/model_decorator/title_field_finder.rb +32 -0
- data/lib/adapters/wallaby/active_record/model_decorator.rb +155 -0
- data/lib/adapters/wallaby/active_record/model_finder.rb +45 -0
- data/lib/adapters/wallaby/active_record/model_pagination_provider.rb +34 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/normalizer.rb +54 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/permitter.rb +68 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/querier/transformer.rb +78 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/querier.rb +172 -0
- data/lib/adapters/wallaby/active_record/model_service_provider/validator.rb +37 -0
- data/lib/adapters/wallaby/active_record/model_service_provider.rb +166 -0
- data/lib/adapters/wallaby/active_record/pundit_provider.rb +19 -0
- data/lib/adapters/wallaby/active_record.rb +7 -0
- data/lib/wallaby/active_record/version.rb +7 -0
- data/lib/wallaby/active_record.rb +38 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 92d10c82a2195970de2aa77c1ad8cd776c03fe68c264678e962bab91a01e7e0d
|
4
|
+
data.tar.gz: 629a2839e2e558ca281f2a48777fbfa20a0a7a77a7497ca8b2a185a5d731d8ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 71820d2020d94d02e2a02063c70faa630a58ae3e2f6ca0422f043a9f3042cf4eeedf336fe8d8f4fcb37f3c0471715427a34329dfacacd27444deb45aef57d04f
|
7
|
+
data.tar.gz: 392802282ef7e87b47c7018544f1324fea3d6750304d88e0d28b1ccf7a27d869cbd68dfc4ed58d03610f9a48b80b10f0a55f37bfdbb3af82ea7234997d71930c
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2010-2019 Google LLC. http://angular.io/license
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Wallaby::ActiveRecord
|
2
|
+
|
3
|
+
This gem contains the ActiveRecord ORM adapter for Wallaby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'wallaby-active_record'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```shell
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```shell
|
22
|
+
$ gem install wallaby-active_record
|
23
|
+
```
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wallaby-rails/wallaby-active_record.
|
28
|
+
|
29
|
+
## License
|
30
|
+
|
31
|
+
This project is [MIT Licensed](LICENSE)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
class ActiveRecord
|
5
|
+
class ModelDecorator
|
6
|
+
class FieldsBuilder
|
7
|
+
# To build the metadata for associations
|
8
|
+
class AssociationBuilder
|
9
|
+
# Update the metadata
|
10
|
+
# @param metadata [Hash]
|
11
|
+
# @param reflection [ActiveRecord::Reflection]
|
12
|
+
def update(metadata, reflection)
|
13
|
+
type = reflection.macro
|
14
|
+
metadata[:is_association] = true
|
15
|
+
metadata[:sort_disabled] = true
|
16
|
+
metadata[:is_through] = through?(reflection)
|
17
|
+
metadata[:has_scope] = scope?(reflection)
|
18
|
+
metadata[:foreign_key] = foreign_key_for(reflection, type)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# @param reflection [ActiveRecord::Reflection]
|
24
|
+
# @param type [Symbol]
|
25
|
+
# @return [String] foreign key
|
26
|
+
def foreign_key_for(reflection, type)
|
27
|
+
if type == :belongs_to || reflection.polymorphic?
|
28
|
+
reflection.foreign_key
|
29
|
+
elsif reflection.collection?
|
30
|
+
# @see https://github.com/rails/rails/blob/92703a9ea5d8b96f30e0b706b801c9185ef14f0e/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
|
31
|
+
reflection.name.to_s.singularize << '_ids'
|
32
|
+
else
|
33
|
+
reflection.association_foreign_key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param reflection [ActiveRecord::Reflection]
|
38
|
+
# @return [Boolean] whether it's a through relation
|
39
|
+
def through?(reflection)
|
40
|
+
reflection.is_a? ::ActiveRecord::Reflection::ThroughReflection
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param reflection [ActiveRecord::Reflection]
|
44
|
+
# @return [Boolean] whether it has scope
|
45
|
+
def scope?(reflection)
|
46
|
+
reflection.scope.present?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
class ActiveRecord
|
5
|
+
class ModelDecorator
|
6
|
+
class FieldsBuilder
|
7
|
+
# To build the metadata for polymorphic
|
8
|
+
class PolymorphicBuilder
|
9
|
+
# update the metadata
|
10
|
+
# @param metadata [Hash]
|
11
|
+
# @param reflection [ActiveRecord::Reflection]
|
12
|
+
def update(metadata, reflection)
|
13
|
+
if reflection.polymorphic?
|
14
|
+
metadata[:is_polymorphic] = true
|
15
|
+
metadata[:polymorphic_type] = reflection.foreign_type
|
16
|
+
metadata[:polymorphic_list] = polymorphic_list_for(reflection)
|
17
|
+
else
|
18
|
+
metadata[:class] = reflection.klass
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @param reflection [ActiveRecord::Reflection]
|
25
|
+
# @return [Array<Class>] a list of classes for this polymorphism
|
26
|
+
def polymorphic_list_for(reflection)
|
27
|
+
all_model_class.each_with_object([]) do |model_class, list|
|
28
|
+
list << model_class if polymorphic_defined? model_class, reflection.name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Array<Class>] a list of all ActiveRecord classes
|
33
|
+
def all_model_class
|
34
|
+
Map
|
35
|
+
.mode_map
|
36
|
+
.select { |_, mode| mode == ::Wallaby::ActiveRecord }.keys
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param model_class [Class]
|
40
|
+
# @param polymorphic_name [String] polymorphic name
|
41
|
+
# @return [Boolean] if polymorphism defined?
|
42
|
+
def polymorphic_defined?(model_class, polymorphic_name)
|
43
|
+
polymorphic_name_sym = polymorphic_name.try(:to_sym)
|
44
|
+
model_class.reflections.any? do |_field_name, reflection|
|
45
|
+
reflection.options[:as].try(:to_sym) == polymorphic_name_sym
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
class ActiveRecord
|
5
|
+
class ModelDecorator
|
6
|
+
class FieldsBuilder
|
7
|
+
# To build the metadata for sti column
|
8
|
+
class StiBuilder
|
9
|
+
# @param model_class [Class]
|
10
|
+
def initialize(model_class)
|
11
|
+
@model_class = model_class
|
12
|
+
end
|
13
|
+
|
14
|
+
# update the metadata
|
15
|
+
# @param metadata [Hash]
|
16
|
+
# @param column [ActiveRecord::ConnectionAdapters::Column]
|
17
|
+
def update(metadata, column)
|
18
|
+
return unless @model_class.inheritance_column == column.name
|
19
|
+
|
20
|
+
metadata[:type] = 'sti'
|
21
|
+
metadata[:sti_class_list] = sti_list(find_parent_of(@model_class))
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# @param klass [Class]
|
27
|
+
# @return [Array<Class>] a list of STI classes for this model
|
28
|
+
def sti_list(klass)
|
29
|
+
list = klass.descendants << klass
|
30
|
+
list.sort_by(&:name)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param klass [Class]
|
34
|
+
# @return [Class] the top parent class in the STI hierarchy
|
35
|
+
def find_parent_of(klass)
|
36
|
+
parent = klass
|
37
|
+
parent = parent.superclass until top_parent?(parent.superclass)
|
38
|
+
parent
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param klass [Class]
|
42
|
+
# @return [Boolean] whether the class is ActiveRecord base class
|
43
|
+
def top_parent?(klass)
|
44
|
+
klass == ModelFinder.base || ModuleUtils.try_to(klass, :abstract_class?)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
class ActiveRecord
|
5
|
+
class ModelDecorator
|
6
|
+
# To search and build the metadata for fields
|
7
|
+
class FieldsBuilder
|
8
|
+
# @param model_class [Class]
|
9
|
+
def initialize(model_class)
|
10
|
+
@model_class = model_class
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Hash<String, Hash>] a hash for general fields
|
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
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Hash<String, Hash>] a hash for general fields
|
26
|
+
def association_fields
|
27
|
+
@model_class.reflections.each_with_object({}) do |(name, ref), fields|
|
28
|
+
metadata = {
|
29
|
+
type: ref.macro.to_s,
|
30
|
+
label: @model_class.human_attribute_name(name)
|
31
|
+
}
|
32
|
+
association_builder.update(metadata, ref)
|
33
|
+
polymorphic_builder.update(metadata, ref)
|
34
|
+
fields[name] = metadata
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
# Detect active_storage type
|
41
|
+
# @param column [ActiveRecord::ConnectionAdapters::Column]
|
42
|
+
# @return [String] field type
|
43
|
+
def to_type(column)
|
44
|
+
return 'active_storage' if @model_class.respond_to?("with_attached_#{column.name}")
|
45
|
+
|
46
|
+
column.type.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Wallaby::ActiveRecord::ModelDecorator::StiBuilder]
|
50
|
+
def sti_builder
|
51
|
+
@sti_builder ||= StiBuilder.new(@model_class)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Wallaby::ActiveRecord::ModelDecorator::AssociationBuilder]
|
55
|
+
def association_builder
|
56
|
+
@association_builder ||= AssociationBuilder.new
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Wallaby::ActiveRecord::ModelDecorator::PolymorphicBuilder]
|
60
|
+
def polymorphic_builder
|
61
|
+
@polymorphic_builder ||= PolymorphicBuilder.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
class ActiveRecord
|
5
|
+
class ModelDecorator
|
6
|
+
# Try to find the field that can be used as title
|
7
|
+
class TitleFieldFinder
|
8
|
+
TITLE_FIELD_TYPES = %w(string).freeze
|
9
|
+
|
10
|
+
# @param model_class [Class]
|
11
|
+
# @param fields [Hash] fields metadata
|
12
|
+
def initialize(model_class, fields)
|
13
|
+
@model_class = model_class
|
14
|
+
@fields = fields
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String] field name that can be used as title
|
18
|
+
def find
|
19
|
+
possible_title_fields = @fields.select do |_field_name, metadata|
|
20
|
+
TITLE_FIELD_TYPES.include? metadata[:type]
|
21
|
+
end
|
22
|
+
target_field = possible_title_fields.keys.find do |field_name|
|
23
|
+
TITLE_NAMES.any? { |v| field_name.to_s.index v }
|
24
|
+
end
|
25
|
+
target_field \
|
26
|
+
|| possible_title_fields.keys.first \
|
27
|
+
|| @model_class.primary_key
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
class ActiveRecord
|
5
|
+
# Modal decorator for ActiveRecord
|
6
|
+
class ModelDecorator < ::Wallaby::ModelDecorator
|
7
|
+
# Data types to exclude for index page
|
8
|
+
INDEX_EXCLUSIVE_DATA_TYPES =
|
9
|
+
(['', 'medium', 'long'] * 2)
|
10
|
+
.zip(%w(blob text) * 3).map(&:join)
|
11
|
+
.concat(%w(binary citext hstore json jsonb tsvector xml)).freeze
|
12
|
+
|
13
|
+
# Class to exclude for show page
|
14
|
+
SHOW_EXCLUSIVE_CLASS_NAMES = %w(ActiveStorage::Attachment ActiveStorage::Blob).freeze
|
15
|
+
|
16
|
+
# Data types to exclude for form page
|
17
|
+
FORM_EXCLUSIVE_DATA_TYPES = %w(created_at updated_at).freeze
|
18
|
+
|
19
|
+
# Origin metadata directly coming from ActiveRecord.
|
20
|
+
#
|
21
|
+
# It needs to be frozen so that we can keep the metadata integrity
|
22
|
+
# @example sample fields:
|
23
|
+
# model_decorator.fields
|
24
|
+
# # =>
|
25
|
+
# {
|
26
|
+
# # general field
|
27
|
+
# id: { name: 'id', type: 'integer', label: 'Id' },
|
28
|
+
# # association field
|
29
|
+
# category: {
|
30
|
+
# 'name' => 'category',
|
31
|
+
# 'type' => 'belongs_to',
|
32
|
+
# 'label' => 'Category',
|
33
|
+
# 'is_association' => true,
|
34
|
+
# 'is_through' => false,
|
35
|
+
# 'has_scope' => false,
|
36
|
+
# 'foreign_key' => 'category_id',
|
37
|
+
# 'class' => Category
|
38
|
+
# }
|
39
|
+
# }
|
40
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] metadata
|
41
|
+
def fields
|
42
|
+
@fields ||= ::ActiveSupport::HashWithIndifferentAccess.new.tap do |hash|
|
43
|
+
# NOTE: There is a chance that people create ActiveRecord class
|
44
|
+
# before they do the migration, so initialising the fields will raise
|
45
|
+
# all kinds of error. Therefore, we need to check the table existence
|
46
|
+
if @model_class.table_exists?
|
47
|
+
hash.merge! general_fields
|
48
|
+
hash.merge! association_fields
|
49
|
+
hash.except!(*foreign_keys_from_associations)
|
50
|
+
end
|
51
|
+
end.freeze
|
52
|
+
end
|
53
|
+
|
54
|
+
# A copy of {#fields} for index page
|
55
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] metadata
|
56
|
+
def index_fields
|
57
|
+
@index_fields ||= Utils.clone fields
|
58
|
+
end
|
59
|
+
|
60
|
+
# A copy of {#fields} for show page
|
61
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] metadata
|
62
|
+
def show_fields
|
63
|
+
@show_fields ||= Utils.clone fields
|
64
|
+
end
|
65
|
+
|
66
|
+
# A copy of {#fields} for form (new/edit) page
|
67
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] metadata
|
68
|
+
def form_fields
|
69
|
+
@form_fields ||= Utils.clone fields
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Array<String>] a list of field names for index page (note: only primitive SQL types are included).
|
73
|
+
def index_field_names
|
74
|
+
@index_field_names ||=
|
75
|
+
index_fields.reject do |_field_name, metadata|
|
76
|
+
metadata[:is_association] \
|
77
|
+
|| INDEX_EXCLUSIVE_DATA_TYPES.include?(metadata[:type])
|
78
|
+
end.keys
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [Array<String>] a list of field names for show page (note: **ActiveStorage** fields are excluded).
|
82
|
+
def show_field_names
|
83
|
+
@show_field_names ||=
|
84
|
+
show_fields.reject do |_field_name, metadata|
|
85
|
+
SHOW_EXCLUSIVE_CLASS_NAMES.include? metadata[:class].try(:name)
|
86
|
+
end.keys
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Array<String>] a list of field names for form (new/edit) page (note: complex fields are excluded).
|
90
|
+
def form_field_names
|
91
|
+
@form_field_names ||=
|
92
|
+
form_fields.reject do |field_name, metadata|
|
93
|
+
field_name == primary_key \
|
94
|
+
|| FORM_EXCLUSIVE_DATA_TYPES.include?(field_name) \
|
95
|
+
|| metadata[:has_scope] || metadata[:is_through]
|
96
|
+
end.keys
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [ActiveModel::Errors] errors for resource
|
100
|
+
def form_active_errors(resource)
|
101
|
+
resource.errors
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [String] primary key for the resource
|
105
|
+
def primary_key
|
106
|
+
@primary_key ||= @model_class.primary_key
|
107
|
+
end
|
108
|
+
|
109
|
+
# To guess the title for resource.
|
110
|
+
#
|
111
|
+
# It will go through the fields and try to find out the one that looks
|
112
|
+
# like a name or text to represent this resource. Otherwise, it will fall
|
113
|
+
# back to primary key.
|
114
|
+
#
|
115
|
+
# @param resource [Object]
|
116
|
+
# @return [String] the title of given resource
|
117
|
+
def guess_title(resource)
|
118
|
+
ModuleUtils.try_to resource, title_field_finder.find
|
119
|
+
end
|
120
|
+
|
121
|
+
protected
|
122
|
+
|
123
|
+
# @return [Wallaby::ActiveRecord::ModelDecorator::FieldsBuilder]
|
124
|
+
def field_builder
|
125
|
+
@field_builder ||= FieldsBuilder.new @model_class
|
126
|
+
end
|
127
|
+
|
128
|
+
# @return [Wallaby::ActiveRecord::ModelDecorator::TitleFieldFinder]
|
129
|
+
def title_field_finder
|
130
|
+
@title_field_finder ||=
|
131
|
+
TitleFieldFinder.new @model_class, general_fields
|
132
|
+
end
|
133
|
+
|
134
|
+
# @!method general_fields
|
135
|
+
# (see Wallaby::ActiveRecord::ModelDecorator::FieldsBuilder#general_fields)
|
136
|
+
# @see Wallaby::ActiveRecord::ModelDecorator::FieldsBuilder#general_fields
|
137
|
+
|
138
|
+
# @!method association_fields
|
139
|
+
# (see Wallaby::ActiveRecord::ModelDecorator::FieldsBuilder#association_fields)
|
140
|
+
# @see Wallaby::ActiveRecord::ModelDecorator::FieldsBuilder#association_fields
|
141
|
+
delegate :general_fields, :association_fields, to: :field_builder
|
142
|
+
|
143
|
+
# Find out all the foreign keys for association fields
|
144
|
+
# @param fields [Hash] metadata of fields
|
145
|
+
# @return [Array<String>] a list of foreign keys
|
146
|
+
def foreign_keys_from_associations(fields = association_fields)
|
147
|
+
fields.each_with_object([]) do |(_field_name, metadata), keys|
|
148
|
+
keys << metadata[:foreign_key] if metadata[:foreign_key]
|
149
|
+
keys << metadata[:polymorphic_type] if metadata[:polymorphic_type]
|
150
|
+
keys
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
class ActiveRecord
|
5
|
+
# Model finder
|
6
|
+
class ModelFinder < ::Wallaby::ModelFinder
|
7
|
+
# @return [Array<Class>] a list of ActiveRecord subclasses
|
8
|
+
def all
|
9
|
+
self.class.base.descendants.reject do |model_class|
|
10
|
+
abstract?(model_class) || anonymous?(model_class) || habtm?(model_class)
|
11
|
+
end.sort_by(&:to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
# This is only for ActiveRecord
|
15
|
+
# @return [ApplicationRecord, ActiveRecord::Base] base ActiveRecord class
|
16
|
+
def self.base
|
17
|
+
return ::ApplicationRecord if defined? ::ApplicationRecord
|
18
|
+
|
19
|
+
::ActiveRecord::Base
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Is model class abstract?
|
25
|
+
# @param model_class [Class]
|
26
|
+
# @return [Boolean]
|
27
|
+
def abstract?(model_class)
|
28
|
+
model_class.abstract_class?
|
29
|
+
end
|
30
|
+
|
31
|
+
# @see Wallaby::ModuleUtils.anonymous_class?
|
32
|
+
def anonymous?(model_class)
|
33
|
+
ModuleUtils.anonymous_class? model_class
|
34
|
+
end
|
35
|
+
|
36
|
+
# Check and see if given model class is intermediate class that generated
|
37
|
+
# for has and belongs to many assocation
|
38
|
+
# @param model_class [Class]
|
39
|
+
# @return [Boolean]
|
40
|
+
def habtm?(model_class)
|
41
|
+
model_class.name.index('HABTM')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
class ActiveRecord
|
5
|
+
# Model pagination provider
|
6
|
+
class ModelPaginationProvider < ::Wallaby::ModelPaginationProvider
|
7
|
+
# Check if collection has pagination feature
|
8
|
+
# @return [Boolean]
|
9
|
+
def paginatable?
|
10
|
+
# `total_count` is a method that kaminari uses
|
11
|
+
(@collection && @collection.respond_to?(:total_count)).tap do |paginatable|
|
12
|
+
next if paginatable
|
13
|
+
|
14
|
+
Rails.logger.warn I18n.t('errors.activerecord.paginatable', collection: @collection.inspect)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Integer] total count for the query
|
19
|
+
def total
|
20
|
+
@collection.total_count
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Integer] page size from parameters or configuration
|
24
|
+
def page_size
|
25
|
+
@params[:per].try(:to_i) || Wallaby.configuration.pagination.page_size
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Integer] page number from parameters
|
29
|
+
def page_number
|
30
|
+
[@params[:page].to_i, 1].max
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
class ActiveRecord
|
5
|
+
class ModelServiceProvider
|
6
|
+
# Normalize the values for a model
|
7
|
+
class Normalizer
|
8
|
+
# @param model_decorator [Wallaby::ModelDecorator]
|
9
|
+
def initialize(model_decorator)
|
10
|
+
@model_decorator = model_decorator
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param params [ActionController::Parameters]
|
14
|
+
def normalize(params)
|
15
|
+
params.each do |field_name, values|
|
16
|
+
metadata =
|
17
|
+
@model_decorator.metadata_of(field_name).presence || @model_decorator.form_metadata_of(field_name)
|
18
|
+
type = metadata[:type].try(:[], /range|point|binary/)
|
19
|
+
next unless type
|
20
|
+
|
21
|
+
public_send "normalize_#{type}_values", params, field_name, values
|
22
|
+
end
|
23
|
+
params
|
24
|
+
end
|
25
|
+
|
26
|
+
# Turn values into range
|
27
|
+
# @param params [ActionController::Parameters]
|
28
|
+
# @param field_name [String]
|
29
|
+
# @param values [Array]
|
30
|
+
def normalize_range_values(params, field_name, values)
|
31
|
+
normalized = Array(values).map(&:presence).compact
|
32
|
+
params[field_name] = (normalized.present? && values.length == 2) && (values.first...values.last) || nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Turn values into points
|
36
|
+
# @param params [ActionController::Parameters]
|
37
|
+
# @param field_name [String]
|
38
|
+
# @param values [Array]
|
39
|
+
def normalize_point_values(params, field_name, values)
|
40
|
+
normalized = Array(values).map(&:presence).compact
|
41
|
+
params[field_name] = normalized.present? && values.map(&:to_f) || nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Turn values into binary
|
45
|
+
# @param params [ActionController::Parameters]
|
46
|
+
# @param field_name [String]
|
47
|
+
# @param values [Object]
|
48
|
+
def normalize_binary_values(params, field_name, values)
|
49
|
+
params[field_name] = values.is_a?(::ActionDispatch::Http::UploadedFile) && values.read || nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|