wallaby-active_record 0.1.1
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 +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
|