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.

Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -4
  3. data/Gemfile.lock +1 -5
  4. data/README.md +4 -0
  5. data/app/components/avo/index/grid_item_component.html.erb +8 -8
  6. data/app/components/avo/index/grid_item_component.rb +6 -12
  7. data/app/components/avo/index/resource_controls_component.html.erb +1 -1
  8. data/app/components/avo/index/resource_grid_component.html.erb +1 -1
  9. data/app/components/avo/index/resource_table_component.html.erb +2 -2
  10. data/app/components/avo/index/table_row_component.html.erb +1 -1
  11. data/app/controllers/avo/actions_controller.rb +2 -4
  12. data/app/controllers/avo/application_controller.rb +6 -3
  13. data/app/controllers/avo/base_controller.rb +10 -7
  14. data/app/controllers/avo/relations_controller.rb +1 -1
  15. data/app/controllers/avo/search_controller.rb +1 -1
  16. data/app/frontend/packs/application.js +1 -1
  17. data/app/views/avo/base/_actions.html.erb +1 -7
  18. data/app/views/avo/base/_boolean_filter.html.erb +1 -1
  19. data/app/views/avo/base/_filters.html.erb +3 -11
  20. data/app/views/avo/base/_select_filter.html.erb +1 -1
  21. data/app/views/avo/home/_resources.html.erb +0 -1
  22. data/app/views/avo/partials/_table_header.html.erb +1 -1
  23. data/app/views/avo/sidebar/_sidebar.html.erb +1 -1
  24. data/app/views/layouts/avo/_filter_wrapper.html.erb +1 -1
  25. data/app/views/layouts/avo/application.html.erb +5 -0
  26. data/avo.gemspec +0 -1
  27. data/config/webpacker.yml +10 -23
  28. data/lib/avo.rb +5 -16
  29. data/lib/avo/app.rb +190 -0
  30. data/lib/avo/base_action.rb +123 -0
  31. data/lib/avo/base_resource.rb +323 -0
  32. data/lib/avo/configuration.rb +8 -0
  33. data/lib/avo/engine.rb +12 -31
  34. data/lib/avo/{app/fields → fields}/badge_field.rb +1 -1
  35. data/lib/avo/{app/fields/field.rb → fields/base_field.rb} +3 -7
  36. data/lib/avo/{app/fields/belongs_to.rb → fields/belongs_to_field.rb} +3 -3
  37. data/lib/avo/{app/fields → fields}/boolean_field.rb +1 -1
  38. data/lib/avo/{app/fields → fields}/boolean_group_field.rb +1 -1
  39. data/lib/avo/{app/fields → fields}/code_field.rb +1 -1
  40. data/lib/avo/{app/fields → fields}/country_field.rb +1 -1
  41. data/lib/avo/{app/fields → fields}/currency_field.rb +1 -1
  42. data/lib/avo/{app/fields → fields}/date_field.rb +0 -2
  43. data/lib/avo/{app/fields → fields}/date_time_field.rb +0 -2
  44. data/lib/avo/{app/fields → fields}/external_image_field.rb +5 -4
  45. data/lib/avo/{app/fields → fields}/field_extensions/has_field_name.rb +0 -0
  46. data/lib/avo/{app/fields → fields}/field_extensions/visible_in_different_views.rb +1 -1
  47. data/lib/avo/{app/fields → fields}/file_field.rb +1 -2
  48. data/lib/avo/{app/fields → fields}/files_field.rb +1 -2
  49. data/lib/avo/{app/fields → fields}/gravatar_field.rb +1 -1
  50. data/lib/avo/{app/fields/has_and_belongs_to_many.rb → fields/has_and_belongs_to_many_field.rb} +1 -1
  51. data/lib/avo/{app/fields/has_many.rb → fields/has_many_field.rb} +1 -1
  52. data/lib/avo/{app/fields/has_one.rb → fields/has_one_field.rb} +1 -1
  53. data/lib/avo/{app/fields → fields}/heading_field.rb +1 -1
  54. data/lib/avo/{app/fields → fields}/hidden_field.rb +0 -2
  55. data/lib/avo/{app/fields → fields}/id_field.rb +1 -1
  56. data/lib/avo/{app/fields → fields}/key_value_field.rb +1 -1
  57. data/lib/avo/{app/fields → fields}/markdown_field.rb +1 -3
  58. data/lib/avo/{app/fields → fields}/number_field.rb +0 -2
  59. data/lib/avo/{app/fields → fields}/password_field.rb +0 -2
  60. data/lib/avo/{app/fields → fields}/select_field.rb +1 -1
  61. data/lib/avo/{app/fields → fields}/status_field.rb +1 -1
  62. data/lib/avo/{app/fields → fields}/text_field.rb +1 -1
  63. data/lib/avo/{app/fields → fields}/textarea_field.rb +0 -2
  64. data/lib/avo/{app/fields → fields}/trix_field.rb +1 -3
  65. data/lib/avo/filters/base_filter.rb +20 -0
  66. data/lib/avo/filters/boolean_filter.rb +7 -0
  67. data/lib/avo/filters/select_filter.rb +7 -0
  68. data/lib/avo/{app/grid_fields → grid_fields}/body_field.rb +0 -2
  69. data/lib/avo/{app/grid_fields → grid_fields}/grid_field.rb +0 -0
  70. data/lib/avo/{app/grid_fields → grid_fields}/preview_field.rb +0 -2
  71. data/lib/avo/{app/grid_fields → grid_fields}/title_field.rb +0 -2
  72. data/lib/avo/licensing/community_license.rb +6 -0
  73. data/lib/avo/licensing/h_q.rb +88 -0
  74. data/lib/avo/licensing/license.rb +50 -0
  75. data/lib/avo/licensing/license_manager.rb +22 -0
  76. data/lib/avo/licensing/null_license.rb +14 -0
  77. data/lib/avo/licensing/pro_license.rb +11 -0
  78. data/lib/avo/loaders/actions_loader.rb +6 -0
  79. data/lib/avo/loaders/fields_loader.rb +27 -0
  80. data/lib/avo/loaders/filters_loader.rb +6 -0
  81. data/lib/avo/loaders/loader.rb +15 -0
  82. data/lib/avo/services/authorization_service.rb +93 -0
  83. data/lib/avo/services/panel_service.rb +27 -0
  84. data/lib/avo/{app/tools_manager.rb → tools_manager.rb} +0 -0
  85. data/lib/avo/version.rb +1 -1
  86. data/lib/generators/avo/action_generator.rb +11 -5
  87. data/lib/generators/avo/controller_generator.rb +14 -8
  88. data/lib/generators/avo/filter_generator.rb +14 -8
  89. data/lib/generators/avo/install_generator.rb +14 -10
  90. data/lib/generators/avo/locales_generator.rb +16 -0
  91. data/lib/generators/avo/partials_generator.rb +14 -0
  92. data/lib/generators/avo/resource_generator.rb +33 -15
  93. data/lib/generators/avo/templates/{action.rb → action.tt} +2 -4
  94. data/lib/generators/avo/templates/filters/boolean_filter.tt +11 -0
  95. data/lib/generators/avo/templates/filters/select_filter.tt +11 -0
  96. data/lib/generators/avo/templates/initializer/{avo.rb → avo.tt} +6 -4
  97. data/lib/generators/avo/templates/locales/avo.en.yml +17 -0
  98. data/lib/generators/avo/templates/partials/_footer.html.erb +3 -0
  99. data/lib/generators/avo/templates/partials/_header.html.erb +1 -0
  100. data/lib/generators/avo/templates/{views → partials}/_logo.html.erb +0 -0
  101. data/lib/generators/avo/templates/{views → partials}/_scripts.html.erb +0 -0
  102. data/lib/generators/avo/templates/resource/controller.tt +2 -0
  103. data/lib/generators/avo/templates/resource/resource.tt +17 -0
  104. data/public/avo-packs/css/{application-3598cfbb.css → application-c75ac28c.css} +1 -1
  105. data/public/avo-packs/css/application-c75ac28c.css.br +0 -0
  106. data/public/avo-packs/css/{application-3598cfbb.css.gz → application-c75ac28c.css.gz} +0 -0
  107. data/public/avo-packs/js/{application-6ba2bd1bca05d69b96e0.js → application-8849c6e2c8f75d55c666.js} +3 -3
  108. data/public/avo-packs/js/{application-6ba2bd1bca05d69b96e0.js.LICENSE.txt → application-8849c6e2c8f75d55c666.js.LICENSE.txt} +0 -0
  109. data/public/avo-packs/js/application-8849c6e2c8f75d55c666.js.br +0 -0
  110. data/public/avo-packs/js/application-8849c6e2c8f75d55c666.js.gz +0 -0
  111. data/public/avo-packs/js/{application-6ba2bd1bca05d69b96e0.js.map → application-8849c6e2c8f75d55c666.js.map} +1 -1
  112. data/public/avo-packs/js/application-8849c6e2c8f75d55c666.js.map.br +0 -0
  113. data/public/avo-packs/js/{application-6ba2bd1bca05d69b96e0.js.map.gz → application-8849c6e2c8f75d55c666.js.map.gz} +0 -0
  114. data/public/avo-packs/manifest.json +6 -6
  115. data/public/avo-packs/manifest.json.br +0 -0
  116. data/public/avo-packs/manifest.json.gz +0 -0
  117. metadata +78 -93
  118. data/app/controllers/avo/resource_overview_controller.rb +0 -26
  119. data/config/webpacker_packed.yml +0 -86
  120. data/lib/avo/app/action.rb +0 -162
  121. data/lib/avo/app/actions_loader.rb +0 -11
  122. data/lib/avo/app/app.rb +0 -181
  123. data/lib/avo/app/fields_loader.rb +0 -29
  124. data/lib/avo/app/filter.rb +0 -34
  125. data/lib/avo/app/filters/boolean_filter.rb +0 -13
  126. data/lib/avo/app/filters/select_filter.rb +0 -13
  127. data/lib/avo/app/licensing/community_license.rb +0 -4
  128. data/lib/avo/app/licensing/hq.rb +0 -86
  129. data/lib/avo/app/licensing/license.rb +0 -48
  130. data/lib/avo/app/licensing/license_manager.rb +0 -25
  131. data/lib/avo/app/licensing/null_license.rb +0 -12
  132. data/lib/avo/app/licensing/pro_license.rb +0 -9
  133. data/lib/avo/app/resource.rb +0 -291
  134. data/lib/avo/app/resource_grid_fields.rb +0 -30
  135. data/lib/avo/app/services/authorization_service.rb +0 -110
  136. data/lib/avo/app/services/panel_service.rb +0 -25
  137. data/lib/avo/app/tool.rb +0 -5
  138. data/lib/generators/avo/templates/filters/boolean_filter.rb +0 -17
  139. data/lib/generators/avo/templates/filters/select_filter.rb +0 -17
  140. data/lib/generators/avo/templates/resource/%plural_name%_controller.tt +0 -2
  141. data/lib/generators/avo/templates/resource/%singular_name%.tt +0 -20
  142. data/lib/generators/avo/templates/views/_footer.html.erb +0 -3
  143. data/lib/generators/avo/templates/views/_header.html.erb +0 -1
  144. data/lib/generators/avo/views_generator.rb +0 -8
  145. data/public/avo-packs/css/application-3598cfbb.css.br +0 -0
  146. data/public/avo-packs/js/application-6ba2bd1bca05d69b96e0.js.br +0 -0
  147. data/public/avo-packs/js/application-6ba2bd1bca05d69b96e0.js.gz +0 -0
  148. 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
- # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules
59
- check_yarn_integrity: true
60
-
61
- # Reference: https://webpack.js.org/configuration/dev-server/
62
- dev_server:
63
- https: false
64
- host: localhost
65
- port: 3039
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
- require_relative 'avo/configuration'
1
+ require 'zeitwerk'
2
2
  require_relative 'avo/version'
3
+ require_relative 'avo/engine' if defined?(Rails)
3
4
 
4
- require_relative 'avo/app/fields/field'
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
- require_relative 'avo/engine' if defined?(Rails)
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