avo 0.5.0.beta1 → 0.5.0.beta6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of avo might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +4 -4
- data/Gemfile.lock +1 -5
- data/README.md +4 -0
- data/app/components/avo/index/grid_item_component.html.erb +8 -8
- data/app/components/avo/index/grid_item_component.rb +6 -12
- data/app/components/avo/index/resource_controls_component.html.erb +1 -1
- data/app/components/avo/index/resource_grid_component.html.erb +1 -1
- data/app/components/avo/index/resource_table_component.html.erb +2 -2
- data/app/components/avo/index/table_row_component.html.erb +1 -1
- data/app/controllers/avo/actions_controller.rb +2 -4
- data/app/controllers/avo/application_controller.rb +6 -3
- data/app/controllers/avo/base_controller.rb +10 -7
- data/app/controllers/avo/relations_controller.rb +1 -1
- data/app/controllers/avo/search_controller.rb +1 -1
- data/app/frontend/packs/application.js +1 -1
- data/app/views/avo/base/_actions.html.erb +1 -7
- data/app/views/avo/base/_boolean_filter.html.erb +1 -1
- data/app/views/avo/base/_filters.html.erb +3 -11
- data/app/views/avo/base/_select_filter.html.erb +1 -1
- data/app/views/avo/home/_resources.html.erb +0 -1
- data/app/views/avo/partials/_table_header.html.erb +1 -1
- data/app/views/avo/sidebar/_sidebar.html.erb +1 -1
- data/app/views/layouts/avo/_filter_wrapper.html.erb +1 -1
- data/app/views/layouts/avo/application.html.erb +5 -0
- data/avo.gemspec +0 -1
- data/config/webpacker.yml +10 -23
- data/lib/avo.rb +5 -16
- data/lib/avo/app.rb +190 -0
- data/lib/avo/base_action.rb +123 -0
- data/lib/avo/base_resource.rb +323 -0
- data/lib/avo/configuration.rb +8 -0
- data/lib/avo/engine.rb +12 -31
- data/lib/avo/{app/fields → fields}/badge_field.rb +1 -1
- data/lib/avo/{app/fields/field.rb → fields/base_field.rb} +3 -7
- data/lib/avo/{app/fields/belongs_to.rb → fields/belongs_to_field.rb} +3 -3
- data/lib/avo/{app/fields → fields}/boolean_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/boolean_group_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/code_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/country_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/currency_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/date_field.rb +0 -2
- data/lib/avo/{app/fields → fields}/date_time_field.rb +0 -2
- data/lib/avo/{app/fields → fields}/external_image_field.rb +5 -4
- data/lib/avo/{app/fields → fields}/field_extensions/has_field_name.rb +0 -0
- data/lib/avo/{app/fields → fields}/field_extensions/visible_in_different_views.rb +1 -1
- data/lib/avo/{app/fields → fields}/file_field.rb +1 -2
- data/lib/avo/{app/fields → fields}/files_field.rb +1 -2
- data/lib/avo/{app/fields → fields}/gravatar_field.rb +1 -1
- data/lib/avo/{app/fields/has_and_belongs_to_many.rb → fields/has_and_belongs_to_many_field.rb} +1 -1
- data/lib/avo/{app/fields/has_many.rb → fields/has_many_field.rb} +1 -1
- data/lib/avo/{app/fields/has_one.rb → fields/has_one_field.rb} +1 -1
- data/lib/avo/{app/fields → fields}/heading_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/hidden_field.rb +0 -2
- data/lib/avo/{app/fields → fields}/id_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/key_value_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/markdown_field.rb +1 -3
- data/lib/avo/{app/fields → fields}/number_field.rb +0 -2
- data/lib/avo/{app/fields → fields}/password_field.rb +0 -2
- data/lib/avo/{app/fields → fields}/select_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/status_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/text_field.rb +1 -1
- data/lib/avo/{app/fields → fields}/textarea_field.rb +0 -2
- data/lib/avo/{app/fields → fields}/trix_field.rb +1 -3
- data/lib/avo/filters/base_filter.rb +20 -0
- data/lib/avo/filters/boolean_filter.rb +7 -0
- data/lib/avo/filters/select_filter.rb +7 -0
- data/lib/avo/{app/grid_fields → grid_fields}/body_field.rb +0 -2
- data/lib/avo/{app/grid_fields → grid_fields}/grid_field.rb +0 -0
- data/lib/avo/{app/grid_fields → grid_fields}/preview_field.rb +0 -2
- data/lib/avo/{app/grid_fields → grid_fields}/title_field.rb +0 -2
- data/lib/avo/licensing/community_license.rb +6 -0
- data/lib/avo/licensing/h_q.rb +88 -0
- data/lib/avo/licensing/license.rb +50 -0
- data/lib/avo/licensing/license_manager.rb +22 -0
- data/lib/avo/licensing/null_license.rb +14 -0
- data/lib/avo/licensing/pro_license.rb +11 -0
- data/lib/avo/loaders/actions_loader.rb +6 -0
- data/lib/avo/loaders/fields_loader.rb +27 -0
- data/lib/avo/loaders/filters_loader.rb +6 -0
- data/lib/avo/loaders/loader.rb +15 -0
- data/lib/avo/services/authorization_service.rb +93 -0
- data/lib/avo/services/panel_service.rb +27 -0
- data/lib/avo/{app/tools_manager.rb → tools_manager.rb} +0 -0
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/action_generator.rb +11 -5
- data/lib/generators/avo/controller_generator.rb +14 -8
- data/lib/generators/avo/filter_generator.rb +14 -8
- data/lib/generators/avo/install_generator.rb +14 -10
- data/lib/generators/avo/locales_generator.rb +16 -0
- data/lib/generators/avo/partials_generator.rb +14 -0
- data/lib/generators/avo/resource_generator.rb +33 -15
- data/lib/generators/avo/templates/{action.rb → action.tt} +2 -4
- data/lib/generators/avo/templates/filters/boolean_filter.tt +11 -0
- data/lib/generators/avo/templates/filters/select_filter.tt +11 -0
- data/lib/generators/avo/templates/initializer/{avo.rb → avo.tt} +6 -4
- data/lib/generators/avo/templates/locales/avo.en.yml +17 -0
- data/lib/generators/avo/templates/partials/_footer.html.erb +3 -0
- data/lib/generators/avo/templates/partials/_header.html.erb +1 -0
- data/lib/generators/avo/templates/{views → partials}/_logo.html.erb +0 -0
- data/lib/generators/avo/templates/{views → partials}/_scripts.html.erb +0 -0
- data/lib/generators/avo/templates/resource/controller.tt +2 -0
- data/lib/generators/avo/templates/resource/resource.tt +17 -0
- data/public/avo-packs/css/{application-3598cfbb.css → application-c75ac28c.css} +1 -1
- data/public/avo-packs/css/application-c75ac28c.css.br +0 -0
- data/public/avo-packs/css/{application-3598cfbb.css.gz → application-c75ac28c.css.gz} +0 -0
- data/public/avo-packs/js/{application-6ba2bd1bca05d69b96e0.js → application-8849c6e2c8f75d55c666.js} +3 -3
- data/public/avo-packs/js/{application-6ba2bd1bca05d69b96e0.js.LICENSE.txt → application-8849c6e2c8f75d55c666.js.LICENSE.txt} +0 -0
- data/public/avo-packs/js/application-8849c6e2c8f75d55c666.js.br +0 -0
- data/public/avo-packs/js/application-8849c6e2c8f75d55c666.js.gz +0 -0
- data/public/avo-packs/js/{application-6ba2bd1bca05d69b96e0.js.map → application-8849c6e2c8f75d55c666.js.map} +1 -1
- data/public/avo-packs/js/application-8849c6e2c8f75d55c666.js.map.br +0 -0
- data/public/avo-packs/js/{application-6ba2bd1bca05d69b96e0.js.map.gz → application-8849c6e2c8f75d55c666.js.map.gz} +0 -0
- data/public/avo-packs/manifest.json +6 -6
- data/public/avo-packs/manifest.json.br +0 -0
- data/public/avo-packs/manifest.json.gz +0 -0
- metadata +78 -93
- data/app/controllers/avo/resource_overview_controller.rb +0 -26
- data/config/webpacker_packed.yml +0 -86
- data/lib/avo/app/action.rb +0 -162
- data/lib/avo/app/actions_loader.rb +0 -11
- data/lib/avo/app/app.rb +0 -181
- data/lib/avo/app/fields_loader.rb +0 -29
- data/lib/avo/app/filter.rb +0 -34
- data/lib/avo/app/filters/boolean_filter.rb +0 -13
- data/lib/avo/app/filters/select_filter.rb +0 -13
- data/lib/avo/app/licensing/community_license.rb +0 -4
- data/lib/avo/app/licensing/hq.rb +0 -86
- data/lib/avo/app/licensing/license.rb +0 -48
- data/lib/avo/app/licensing/license_manager.rb +0 -25
- data/lib/avo/app/licensing/null_license.rb +0 -12
- data/lib/avo/app/licensing/pro_license.rb +0 -9
- data/lib/avo/app/resource.rb +0 -291
- data/lib/avo/app/resource_grid_fields.rb +0 -30
- data/lib/avo/app/services/authorization_service.rb +0 -110
- data/lib/avo/app/services/panel_service.rb +0 -25
- data/lib/avo/app/tool.rb +0 -5
- data/lib/generators/avo/templates/filters/boolean_filter.rb +0 -17
- data/lib/generators/avo/templates/filters/select_filter.rb +0 -17
- data/lib/generators/avo/templates/resource/%plural_name%_controller.tt +0 -2
- data/lib/generators/avo/templates/resource/%singular_name%.tt +0 -20
- data/lib/generators/avo/templates/views/_footer.html.erb +0 -3
- data/lib/generators/avo/templates/views/_header.html.erb +0 -1
- data/lib/generators/avo/views_generator.rb +0 -8
- data/public/avo-packs/css/application-3598cfbb.css.br +0 -0
- data/public/avo-packs/js/application-6ba2bd1bca05d69b96e0.js.br +0 -0
- data/public/avo-packs/js/application-6ba2bd1bca05d69b96e0.js.gz +0 -0
- data/public/avo-packs/js/application-6ba2bd1bca05d69b96e0.js.map.br +0 -0
data/config/webpacker.yml
CHANGED
@@ -51,32 +51,19 @@ default: &default
|
|
51
51
|
- .jpeg
|
52
52
|
- .jpg
|
53
53
|
|
54
|
+
# We override the development env. when packed.
|
55
|
+
# Avo needs to be packed and compiled for when the dev uses it in his development env.
|
54
56
|
development:
|
55
57
|
<<: *default
|
56
|
-
compile: true
|
57
58
|
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
public: localhost:3039
|
67
|
-
hmr: true
|
68
|
-
# Inline should be set to true if using HMR
|
69
|
-
inline: true
|
70
|
-
overlay: true
|
71
|
-
compress: true
|
72
|
-
disable_host_check: true
|
73
|
-
use_local_ip: false
|
74
|
-
quiet: false
|
75
|
-
headers:
|
76
|
-
'Access-Control-Allow-Origin': '*'
|
77
|
-
watch_options:
|
78
|
-
ignored: '**/node_modules/**'
|
79
|
-
env_prefix: "AVO_WEBPACKER_DEV_SERVER"
|
59
|
+
# Production depends on precompilation of packs prior to booting for performance.
|
60
|
+
compile: false
|
61
|
+
|
62
|
+
# Extract and emit a css file
|
63
|
+
extract_css: true
|
64
|
+
|
65
|
+
# Cache manifest.json for performance
|
66
|
+
cache_manifest: true
|
80
67
|
|
81
68
|
test:
|
82
69
|
<<: *default
|
data/lib/avo.rb
CHANGED
@@ -1,20 +1,9 @@
|
|
1
|
-
|
1
|
+
require 'zeitwerk'
|
2
2
|
require_relative 'avo/version'
|
3
|
+
require_relative 'avo/engine' if defined?(Rails)
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
require_relative 'avo/app/action'
|
7
|
-
|
8
|
-
require_relative 'avo/app/filter'
|
9
|
-
require_relative 'avo/app/filters/boolean_filter'
|
10
|
-
require_relative 'avo/app/filters/select_filter'
|
11
|
-
|
12
|
-
require_relative 'avo/app/fields_loader'
|
13
|
-
require_relative 'avo/app/actions_loader'
|
14
|
-
# require_relative 'avo/app/fields_loader_helper'
|
15
|
-
require_relative 'avo/app/resource'
|
16
|
-
|
17
|
-
require_relative 'avo/app/licensing/license_manager'
|
5
|
+
loader = Zeitwerk::Loader.for_gem
|
6
|
+
loader.setup
|
18
7
|
|
19
8
|
module Avo
|
20
9
|
ROOT_PATH = Pathname.new(File.join(__dir__, '..'))
|
@@ -31,4 +20,4 @@ module Avo
|
|
31
20
|
end
|
32
21
|
end
|
33
22
|
|
34
|
-
|
23
|
+
loader.eager_load
|
data/lib/avo/app.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
module Avo
|
2
|
+
class App
|
3
|
+
class_attribute :app, default: {
|
4
|
+
resources: [],
|
5
|
+
cache_store: nil
|
6
|
+
}
|
7
|
+
class_attribute :fields, default: []
|
8
|
+
class_attribute :request, default: nil
|
9
|
+
class_attribute :context, default: nil
|
10
|
+
class_attribute :license, default: nil
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# def load_controllers
|
14
|
+
# BaseResource.descendants.each do |resource|
|
15
|
+
# controller_name = "#{resource.to_s.gsub('Resource', '').pluralize}Controller"
|
16
|
+
# puts controller_name.inspect
|
17
|
+
# # eval <<DYNAMIC
|
18
|
+
# # class #{controller_name} < Avo::ResourcesController
|
19
|
+
# # # title :Person
|
20
|
+
# # # attribute :name, String
|
21
|
+
# # # ...or substitute other stuff in here.
|
22
|
+
# # end
|
23
|
+
# # DYNAMIC
|
24
|
+
|
25
|
+
# Avo.const_set(controller_name.to_sym, Class.new(Avo::ResourcesController))
|
26
|
+
# # puts controller_name.inspect
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
|
30
|
+
def boot
|
31
|
+
init_fields
|
32
|
+
|
33
|
+
I18n.locale = Avo.configuration.language_code
|
34
|
+
|
35
|
+
if Rails.cache.class == ActiveSupport::Cache::NullStore
|
36
|
+
self.app[:cache_store] ||= ActiveSupport::Cache::MemoryStore.new
|
37
|
+
else
|
38
|
+
self.app[:cache_store] = Rails.cache
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def init(request:, context:)
|
43
|
+
self.request = request
|
44
|
+
self.context = context
|
45
|
+
|
46
|
+
# Set the current host for ActiveStorage
|
47
|
+
ActiveStorage::Current.host = request.base_url
|
48
|
+
|
49
|
+
init_resources
|
50
|
+
|
51
|
+
self.license = Licensing::LicenseManager.new(Licensing::HQ.new(request).response).license
|
52
|
+
end
|
53
|
+
|
54
|
+
def cache_store
|
55
|
+
self.app[:cache_store]
|
56
|
+
end
|
57
|
+
|
58
|
+
# This method will find all fields available in the Avo::Fields namespace and add them to the fields class_variable array
|
59
|
+
# so later we can instantiate them on our resources.
|
60
|
+
#
|
61
|
+
# If the field has their `def_method` set up it will follow that convention, if not it will snake_case the name:
|
62
|
+
#
|
63
|
+
# Avo::Fields::TextField -> text
|
64
|
+
# Avo::Fields::DateTimeField -> date_time
|
65
|
+
def init_fields
|
66
|
+
Avo::Fields::BaseField.descendants.each do |class_name|
|
67
|
+
next if class_name.to_s == 'BaseField'
|
68
|
+
|
69
|
+
if class_name.to_s.end_with? 'Field'
|
70
|
+
load_field class_name.get_field_name, class_name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def load_field(method_name, klass)
|
76
|
+
self.fields.push(
|
77
|
+
name: method_name,
|
78
|
+
class: klass,
|
79
|
+
)
|
80
|
+
|
81
|
+
# Avo::Loaders::FieldsLoader.define_method method_name.to_sym do |*args, &block|
|
82
|
+
# puts ['Avo::Loaders::FieldsLoader.define_method->', args, block.present?].inspect
|
83
|
+
|
84
|
+
# if block.present?
|
85
|
+
# puts '111->'.inspect
|
86
|
+
# field = klass.new(args[0], **args[1] || {}, &block)
|
87
|
+
# else
|
88
|
+
# puts '222->'.inspect
|
89
|
+
# field = klass.new(args[0], **args[1] || {})
|
90
|
+
# end
|
91
|
+
|
92
|
+
# puts field.inspect
|
93
|
+
|
94
|
+
# self.bag.push field
|
95
|
+
|
96
|
+
# # field
|
97
|
+
# # if block.present?
|
98
|
+
# # field_class = klass::new(args[0], **args[1] || {}, &block)
|
99
|
+
# # else
|
100
|
+
# # field_class = klass::new(args[0], **args[1] || {})
|
101
|
+
# # end
|
102
|
+
|
103
|
+
# # klass_entity.add_field(self, field_class)
|
104
|
+
# end
|
105
|
+
end
|
106
|
+
|
107
|
+
def init_resources
|
108
|
+
self.app[:resources] = BaseResource.descendants
|
109
|
+
.select do |resource|
|
110
|
+
resource != BaseResource
|
111
|
+
end
|
112
|
+
.map do |resource|
|
113
|
+
if resource.is_a? Class
|
114
|
+
resource.new
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_resources
|
120
|
+
self.app[:resources]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns the Avo resource by camelized name
|
124
|
+
#
|
125
|
+
# get_resource_by_name('User') => Avo::Resources::User
|
126
|
+
def get_resource(resource)
|
127
|
+
self.app[:resources].find do |available_resource|
|
128
|
+
"#{resource}Resource".safe_constantize == available_resource.class
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the Avo resource by singular snake_cased name
|
133
|
+
#
|
134
|
+
# get_resource_by_name('user') => Avo::Resources::User
|
135
|
+
def get_resource_by_name(name)
|
136
|
+
self.get_resource name.singularize.camelize
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns the Avo resource by singular snake_cased name
|
140
|
+
#
|
141
|
+
# get_resource_by_name('User') => Avo::Resources::User
|
142
|
+
# get_resource_by_name(User) => Avo::Resources::User
|
143
|
+
def get_resource_by_model_name(name)
|
144
|
+
get_resources.find do |resource|
|
145
|
+
resource.model_class.model_name.name == name.to_s
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns the Rails model class by singular snake_cased name
|
150
|
+
#
|
151
|
+
# get_model_class_by_name('user') => User
|
152
|
+
def get_model_class_by_name(name)
|
153
|
+
name.to_s.camelize.singularize
|
154
|
+
end
|
155
|
+
|
156
|
+
def get_available_resources(user = nil)
|
157
|
+
App.get_resources
|
158
|
+
.select do |resource|
|
159
|
+
Services::AuthorizationService.authorize user, resource.model, Avo.configuration.authorization_methods.stringify_keys['index'], raise_exception: false
|
160
|
+
end
|
161
|
+
.sort_by { |r| r.name }
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_navigation_items(user = nil)
|
165
|
+
get_available_resources(user).select do |resource|
|
166
|
+
resource.model_class.present?
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def draw_routes
|
171
|
+
# We should eager load all the classes so we find all descendants
|
172
|
+
Rails.application.eager_load!
|
173
|
+
|
174
|
+
Proc.new do
|
175
|
+
BaseResource.descendants
|
176
|
+
.select do |resource|
|
177
|
+
resource != :Resource
|
178
|
+
end
|
179
|
+
.map do |resource|
|
180
|
+
if resource.is_a? Class
|
181
|
+
route_key = resource.to_s.underscore.gsub('_resource', '').downcase.pluralize.to_sym
|
182
|
+
|
183
|
+
resources route_key
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Avo
|
2
|
+
class BaseAction
|
3
|
+
class_attribute :name, default: self.class.to_s.demodulize.underscore.humanize(keep_id_suffix: true)
|
4
|
+
class_attribute :message
|
5
|
+
class_attribute :confirm_text
|
6
|
+
class_attribute :cancel_text
|
7
|
+
class_attribute :no_confirmation, default: false
|
8
|
+
class_attribute :fields_loader
|
9
|
+
|
10
|
+
attr_accessor :response
|
11
|
+
attr_accessor :model
|
12
|
+
attr_accessor :resource
|
13
|
+
attr_accessor :user
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def fields(&block)
|
17
|
+
self.fields_loader ||= Avo::Loaders::FieldsLoader.new
|
18
|
+
|
19
|
+
yield(fields_loader)
|
20
|
+
end
|
21
|
+
|
22
|
+
def context
|
23
|
+
App.context
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
self.class.message ||= I18n.t('avo.are_you_sure_you_want_to_run_this_option')
|
29
|
+
self.class.confirm_text ||= I18n.t('avo.run')
|
30
|
+
self.class.cancel_text ||= I18n.t('avo.cancel')
|
31
|
+
|
32
|
+
@response ||= {}
|
33
|
+
@response[:message_type] ||= :notice
|
34
|
+
@response[:message] ||= I18n.t('avo.action_ran_successfully')
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_fields(view_type: :table)
|
38
|
+
get_field_definitions.map do |field|
|
39
|
+
field.hydrate(action: self, model: @model)
|
40
|
+
end
|
41
|
+
.select do |field|
|
42
|
+
field.can_see.present? ? field.can_see.call : true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_field_definitions
|
47
|
+
return [] if self.class.fields_loader.blank?
|
48
|
+
|
49
|
+
self.class.fields_loader.bag.map do |field|
|
50
|
+
field.hydrate(action: self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def hydrate(model: nil, resource: nil, user: nil)
|
55
|
+
@model = model if model.present?
|
56
|
+
@resource = resource if resource.present?
|
57
|
+
@user = user if user.present?
|
58
|
+
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_action(request, models, raw_fields)
|
63
|
+
avo_fields = get_fields.map { |field| [field.id, field] }.to_h
|
64
|
+
|
65
|
+
if raw_fields.present?
|
66
|
+
fields = raw_fields.to_unsafe_h.map do |name, value|
|
67
|
+
[name, avo_fields[name.to_sym].resolve_attribute(value)]
|
68
|
+
end
|
69
|
+
|
70
|
+
fields = fields.to_h
|
71
|
+
else
|
72
|
+
fields = {}
|
73
|
+
end
|
74
|
+
|
75
|
+
result = self.handle request, models, fields
|
76
|
+
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def param_id
|
81
|
+
self.class.to_s.demodulize.underscore.gsub '/', '_'
|
82
|
+
end
|
83
|
+
|
84
|
+
def succeed(text)
|
85
|
+
self.response[:message_type] = :notice
|
86
|
+
self.response[:message] = text
|
87
|
+
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
def fail(text)
|
92
|
+
self.response[:message_type] = :alert
|
93
|
+
self.response[:message] = text
|
94
|
+
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def redirect_to(path = nil, &block)
|
99
|
+
self.response[:type] = :redirect
|
100
|
+
if block.present?
|
101
|
+
self.response[:path] = block
|
102
|
+
else
|
103
|
+
self.response[:path] = path
|
104
|
+
end
|
105
|
+
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def reload
|
110
|
+
self.response[:type] = :reload
|
111
|
+
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
def download(path, filename)
|
116
|
+
self.response[:type] = :download
|
117
|
+
self.response[:path] = path
|
118
|
+
self.response[:filename] = filename
|
119
|
+
|
120
|
+
self
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,323 @@
|
|
1
|
+
module Avo
|
2
|
+
class BaseResource
|
3
|
+
extend ActiveSupport::DescendantsTracker
|
4
|
+
|
5
|
+
attr_accessor :view
|
6
|
+
attr_accessor :model
|
7
|
+
attr_accessor :user
|
8
|
+
attr_accessor :params
|
9
|
+
|
10
|
+
class_attribute :id, default: :id
|
11
|
+
class_attribute :title, default: :id
|
12
|
+
class_attribute :search, default: [:id]
|
13
|
+
class_attribute :includes, default: []
|
14
|
+
class_attribute :translation_key
|
15
|
+
class_attribute :default_view_type, default: :table
|
16
|
+
class_attribute :devise_password_optional, default: false
|
17
|
+
class_attribute :fields_loader
|
18
|
+
class_attribute :actions_loader
|
19
|
+
class_attribute :filters_loader
|
20
|
+
class_attribute :grid_cover_loader
|
21
|
+
class_attribute :grid_title_loader
|
22
|
+
class_attribute :grid_body_loader
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def fields(&block)
|
26
|
+
self.fields_loader ||= Avo::Loaders::FieldsLoader.new
|
27
|
+
|
28
|
+
yield(fields_loader)
|
29
|
+
end
|
30
|
+
|
31
|
+
def actions(&block)
|
32
|
+
self.actions_loader ||= Avo::Loaders::ActionsLoader.new
|
33
|
+
|
34
|
+
yield(actions_loader)
|
35
|
+
end
|
36
|
+
|
37
|
+
def filters(&block)
|
38
|
+
self.filters_loader ||= Avo::Loaders::FiltersLoader.new
|
39
|
+
|
40
|
+
yield(filters_loader)
|
41
|
+
end
|
42
|
+
|
43
|
+
def grid(&block)
|
44
|
+
self.grid_cover_loader ||= Avo::Loaders::FieldsLoader.new
|
45
|
+
self.grid_title_loader ||= Avo::Loaders::FieldsLoader.new
|
46
|
+
self.grid_body_loader ||= Avo::Loaders::FieldsLoader.new
|
47
|
+
|
48
|
+
yield(grid_cover_loader, grid_title_loader, grid_body_loader)
|
49
|
+
end
|
50
|
+
|
51
|
+
def context
|
52
|
+
App.context
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def hydrate(model: nil, view: nil, user: nil, params: nil)
|
57
|
+
@view = view if view.present?
|
58
|
+
@user = user if user.present?
|
59
|
+
@params = params if params.present?
|
60
|
+
|
61
|
+
if model.present?
|
62
|
+
@model = model
|
63
|
+
|
64
|
+
hydrate_model_with_default_values if @view == :new
|
65
|
+
end
|
66
|
+
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_fields(panel: nil, reflection: nil)
|
71
|
+
fields = get_field_definitions.select do |field|
|
72
|
+
field.send("show_on_#{@view.to_s}")
|
73
|
+
end
|
74
|
+
.select do |field|
|
75
|
+
field.can_see.present? ? field.can_see.call : true
|
76
|
+
end
|
77
|
+
.select do |field|
|
78
|
+
unless field.respond_to?(:foreign_key) &&
|
79
|
+
reflection.present? &&
|
80
|
+
reflection.respond_to?(:foreign_key) &&
|
81
|
+
reflection.foreign_key == field.foreign_key
|
82
|
+
true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
if panel.present?
|
87
|
+
fields = fields.select do |field|
|
88
|
+
field.panel_name == panel
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
fields = fields.map do |field|
|
93
|
+
field.hydrate(model: @model, view: @view, resource: self)
|
94
|
+
end
|
95
|
+
|
96
|
+
fields
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_grid_fields
|
100
|
+
return if self.class.grid_title_loader.blank?
|
101
|
+
|
102
|
+
card = {
|
103
|
+
cover: [*self.class.grid_cover_loader.bag],
|
104
|
+
title: [*self.class.grid_title_loader.bag],
|
105
|
+
body: [*self.class.grid_body_loader.bag],
|
106
|
+
}
|
107
|
+
|
108
|
+
card.each do |section, fields|
|
109
|
+
fields = fields.map do |field|
|
110
|
+
field.hydrate(model: @model, view: @view, resource: self)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_filters
|
116
|
+
return [] if self.class.filters_loader.blank?
|
117
|
+
|
118
|
+
self.class.filters_loader.bag
|
119
|
+
end
|
120
|
+
|
121
|
+
def get_actions
|
122
|
+
return [] if self.class.actions_loader.blank?
|
123
|
+
|
124
|
+
self.class.actions_loader.bag
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_field_definitions
|
128
|
+
return [] if self.class.fields_loader.blank?
|
129
|
+
|
130
|
+
self.class.fields_loader.bag.map do |field|
|
131
|
+
field.hydrate(resource: self, panel_name: default_panel_name, user: user)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def default_panel_name
|
136
|
+
return @params[:related_name].capitalize if @params[:related_name].present?
|
137
|
+
|
138
|
+
case @view
|
139
|
+
when :show
|
140
|
+
I18n.t('avo.resource_details', item: self.name.downcase, title: model_title).upcase_first
|
141
|
+
when :edit
|
142
|
+
I18n.t('avo.update_item', item: self.name.downcase, title: model_title).upcase_first
|
143
|
+
when :new
|
144
|
+
I18n.t('avo.create_new_item', item: self.name.downcase).upcase_first
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def panels
|
149
|
+
panels = [
|
150
|
+
{
|
151
|
+
name: default_panel_name,
|
152
|
+
type: :fields,
|
153
|
+
in_panel: true,
|
154
|
+
}
|
155
|
+
]
|
156
|
+
|
157
|
+
panels
|
158
|
+
end
|
159
|
+
|
160
|
+
def model_class
|
161
|
+
return @model.class if @model.present?
|
162
|
+
|
163
|
+
self.class.name.demodulize.chomp('Resource').safe_constantize
|
164
|
+
end
|
165
|
+
|
166
|
+
def model_title
|
167
|
+
return @model.send title if @model.present?
|
168
|
+
|
169
|
+
name
|
170
|
+
end
|
171
|
+
|
172
|
+
def name
|
173
|
+
return @name if @name.present?
|
174
|
+
|
175
|
+
return I18n.t(@translation_key, count: 1).capitalize if @translation_key
|
176
|
+
|
177
|
+
self.class.name.demodulize.chomp('Resource').titlecase
|
178
|
+
end
|
179
|
+
|
180
|
+
def singular_name
|
181
|
+
name
|
182
|
+
end
|
183
|
+
|
184
|
+
def plural_name
|
185
|
+
return I18n.t(@translation_key, count: 2).capitalize if @translation_key
|
186
|
+
|
187
|
+
name.pluralize
|
188
|
+
end
|
189
|
+
|
190
|
+
def underscore_name
|
191
|
+
return @name if @name.present?
|
192
|
+
|
193
|
+
self.class.name.demodulize.underscore
|
194
|
+
end
|
195
|
+
|
196
|
+
def available_view_types
|
197
|
+
view_types = [:table]
|
198
|
+
|
199
|
+
view_types << :grid if get_grid_fields.present?
|
200
|
+
|
201
|
+
view_types
|
202
|
+
end
|
203
|
+
|
204
|
+
def route_key
|
205
|
+
model_class.model_name.route_key
|
206
|
+
end
|
207
|
+
|
208
|
+
def context
|
209
|
+
App.context
|
210
|
+
end
|
211
|
+
|
212
|
+
# def fields
|
213
|
+
# raise NotImplementedError
|
214
|
+
# end
|
215
|
+
|
216
|
+
def query_search(query: '', via_resource_name: , via_resource_id:, user:)
|
217
|
+
# model_class = self.model
|
218
|
+
|
219
|
+
db_query = AuthorizationService.apply_policy(user, model_class)
|
220
|
+
|
221
|
+
if via_resource_name.present?
|
222
|
+
related_model = App.get_resource_by_name(via_resource_name).model
|
223
|
+
|
224
|
+
db_query = related_model.find(via_resource_id).public_send(self.plural_name.downcase)
|
225
|
+
end
|
226
|
+
|
227
|
+
new_query = []
|
228
|
+
|
229
|
+
[self.search].flatten.each_with_index do |search_by, index|
|
230
|
+
new_query.push 'or' if index != 0
|
231
|
+
|
232
|
+
new_query.push "text(#{search_by}) ILIKE '%#{query}%'"
|
233
|
+
end
|
234
|
+
|
235
|
+
db_query.where(new_query.join(' '))
|
236
|
+
end
|
237
|
+
|
238
|
+
def attached_file_fields
|
239
|
+
get_field_definitions.select do |field|
|
240
|
+
[Avo::Fields::FileField, Avo::Fields::FilesField].include? field.class
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def fill_model(model, params)
|
245
|
+
# Map the received params to their actual fields
|
246
|
+
fields_by_database_id = self.get_field_definitions.map { |field| [field.database_id(model).to_s, field] }.to_h
|
247
|
+
|
248
|
+
params.each do |key, value|
|
249
|
+
field = fields_by_database_id[key]
|
250
|
+
|
251
|
+
next unless field
|
252
|
+
|
253
|
+
model = field.fill_field model, key, value
|
254
|
+
end
|
255
|
+
|
256
|
+
model
|
257
|
+
end
|
258
|
+
|
259
|
+
def authorization
|
260
|
+
Avo::Services::AuthorizationService.new(user, model)
|
261
|
+
end
|
262
|
+
|
263
|
+
def file_hash
|
264
|
+
content_to_be_hashed = ''
|
265
|
+
|
266
|
+
# resource file hash
|
267
|
+
resource_path = Rails.root.join('app', 'avo', 'resources', "#{name.underscore}.rb").to_s
|
268
|
+
if File.file? resource_path
|
269
|
+
content_to_be_hashed += File.read(resource_path)
|
270
|
+
end
|
271
|
+
|
272
|
+
# policy file hash
|
273
|
+
policy_path = Rails.root.join('app', 'policies', "#{name.underscore}_policy.rb").to_s
|
274
|
+
if File.file? policy_path
|
275
|
+
content_to_be_hashed += File.read(policy_path)
|
276
|
+
end
|
277
|
+
|
278
|
+
Digest::MD5.hexdigest(content_to_be_hashed)
|
279
|
+
end
|
280
|
+
|
281
|
+
def cache_hash(parent_model)
|
282
|
+
if parent_model.present?
|
283
|
+
[self.model, self.file_hash, parent_model]
|
284
|
+
else
|
285
|
+
[self.model, self.file_hash]
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# For :new views we're hydrating the model with the values from the resource's default attribute.
|
290
|
+
# We will not overwrite any attributes that come pre-filled in the model.
|
291
|
+
def hydrate_model_with_default_values
|
292
|
+
default_values = get_fields.select do |field|
|
293
|
+
!field.computed
|
294
|
+
end
|
295
|
+
.map do |field|
|
296
|
+
id = field.id
|
297
|
+
value = field.value
|
298
|
+
|
299
|
+
if field.respond_to? :foreign_key
|
300
|
+
id = field.foreign_key.to_sym
|
301
|
+
|
302
|
+
reflection = @model._reflections[@params[:via_relation]]
|
303
|
+
|
304
|
+
if reflection.present? && reflection.foreign_key.present?
|
305
|
+
value = @params[:via_resource_id]
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
[id, value]
|
310
|
+
end
|
311
|
+
.to_h
|
312
|
+
.select do |id, value|
|
313
|
+
value.present?
|
314
|
+
end
|
315
|
+
|
316
|
+
default_values.each do |id, value|
|
317
|
+
if @model.send(id).nil?
|
318
|
+
@model.send("#{id}=", value)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|