avo 3.10.6 → 3.10.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +4 -9
- data/app/components/avo/fields/boolean_field/index_component.html.erb +1 -1
- data/app/components/avo/fields/boolean_field/show_component.html.erb +1 -1
- data/app/components/avo/fields/boolean_group_field/edit_component.rb +1 -1
- data/app/components/avo/fields/common/boolean_group_component.html.erb +3 -6
- data/app/components/avo/resource_component.rb +0 -2
- data/app/components/avo/turbo_frame_wrapper_component.html.erb +1 -1
- data/app/controllers/avo/base_controller.rb +9 -2
- data/lib/avo/asset_manager.rb +8 -3
- data/lib/avo/base_resource.rb +2 -601
- data/lib/avo/fields/boolean_group_field.rb +1 -1
- data/lib/avo/licensing/request.rb +3 -1
- data/lib/avo/resources/base.rb +607 -0
- data/lib/avo/resources/items/item_group.rb +2 -0
- data/lib/avo/resources/items/tab.rb +2 -0
- data/lib/avo/version.rb +1 -1
- data/lib/avo.rb +1 -0
- data/lib/generators/avo/eject_generator.rb +1 -1
- data/lib/generators/avo/templates/locales/avo.de.yml +120 -0
- data/lib/generators/avo/templates/locales/avo.it.yml +120 -0
- data/lib/generators/avo/templates/locales/avo.nl.yml +120 -0
- data/lib/generators/avo/templates/locales/avo.pl.yml +120 -0
- data/lib/generators/avo/templates/locales/avo.ru.yml +120 -0
- data/lib/generators/avo/templates/locales/avo.uk.yml +120 -0
- data/lib/generators/avo/templates/locales/avo.zh.yml +120 -0
- data/public/avo-assets/avo.base.css +3 -71
- data/public/avo-assets/avo.base.js +230 -227
- data/public/avo-assets/avo.base.js.map +4 -4
- data/public/avo-assets/logo-on-white.png +0 -0
- metadata +10 -2
@@ -0,0 +1,607 @@
|
|
1
|
+
module Avo
|
2
|
+
module Resources
|
3
|
+
class Base
|
4
|
+
extend ActiveSupport::DescendantsTracker
|
5
|
+
|
6
|
+
include ActionView::Helpers::UrlHelper
|
7
|
+
include Avo::Concerns::HasItems
|
8
|
+
include Avo::Concerns::CanReplaceItems
|
9
|
+
include Avo::Concerns::HasControls
|
10
|
+
include Avo::Concerns::HasResourceStimulusControllers
|
11
|
+
include Avo::Concerns::ModelClassConstantized
|
12
|
+
include Avo::Concerns::HasDescription
|
13
|
+
include Avo::Concerns::HasCoverPhoto
|
14
|
+
include Avo::Concerns::HasProfilePhoto
|
15
|
+
include Avo::Concerns::HasHelpers
|
16
|
+
include Avo::Concerns::Hydration
|
17
|
+
include Avo::Concerns::Pagination
|
18
|
+
|
19
|
+
# Avo::Current methods
|
20
|
+
delegate :context, to: Avo::Current
|
21
|
+
def current_user
|
22
|
+
Avo::Current.user
|
23
|
+
end
|
24
|
+
delegate :params, to: Avo::Current
|
25
|
+
delegate :request, to: Avo::Current
|
26
|
+
delegate :view_context, to: Avo::Current
|
27
|
+
|
28
|
+
# view_context methods
|
29
|
+
delegate :simple_format, :content_tag, to: :view_context
|
30
|
+
delegate :main_app, to: :view_context
|
31
|
+
delegate :avo, to: :view_context
|
32
|
+
delegate :resource_path, to: :view_context
|
33
|
+
delegate :resources_path, to: :view_context
|
34
|
+
|
35
|
+
# I18n methods
|
36
|
+
delegate :t, to: ::I18n
|
37
|
+
|
38
|
+
# class methods
|
39
|
+
delegate :class_name, to: :class
|
40
|
+
delegate :route_key, to: :class
|
41
|
+
delegate :singular_route_key, to: :class
|
42
|
+
|
43
|
+
attr_accessor :view
|
44
|
+
attr_accessor :reflection
|
45
|
+
attr_accessor :user
|
46
|
+
attr_accessor :record
|
47
|
+
|
48
|
+
class_attribute :id, default: :id
|
49
|
+
class_attribute :title
|
50
|
+
class_attribute :search, default: {}
|
51
|
+
class_attribute :includes, default: []
|
52
|
+
class_attribute :attachments, default: []
|
53
|
+
class_attribute :single_includes, default: []
|
54
|
+
class_attribute :single_attachments, default: []
|
55
|
+
class_attribute :authorization_policy
|
56
|
+
class_attribute :translation_key
|
57
|
+
class_attribute :default_view_type, default: :table
|
58
|
+
class_attribute :devise_password_optional, default: false
|
59
|
+
class_attribute :scopes_loader
|
60
|
+
class_attribute :filters_loader
|
61
|
+
class_attribute :view_types
|
62
|
+
class_attribute :grid_view
|
63
|
+
class_attribute :visible_on_sidebar, default: true
|
64
|
+
class_attribute :index_query, default: -> {
|
65
|
+
query
|
66
|
+
}
|
67
|
+
class_attribute :find_record_method, default: -> {
|
68
|
+
query.find id
|
69
|
+
}
|
70
|
+
class_attribute :after_create_path, default: :show
|
71
|
+
class_attribute :after_update_path, default: :show
|
72
|
+
class_attribute :record_selector, default: true
|
73
|
+
class_attribute :keep_filters_panel_open, default: false
|
74
|
+
class_attribute :extra_params
|
75
|
+
class_attribute :link_to_child_resource, default: false
|
76
|
+
class_attribute :map_view
|
77
|
+
class_attribute :components, default: {}
|
78
|
+
class_attribute :default_sort_column, default: :created_at
|
79
|
+
|
80
|
+
# EXTRACT:
|
81
|
+
class_attribute :ordering
|
82
|
+
|
83
|
+
class << self
|
84
|
+
delegate :t, to: ::I18n
|
85
|
+
delegate :context, to: ::Avo::Current
|
86
|
+
|
87
|
+
def action(action_class, arguments: {})
|
88
|
+
deprecated_dsl_api __method__, "actions"
|
89
|
+
end
|
90
|
+
|
91
|
+
def filter(filter_class, arguments: {})
|
92
|
+
deprecated_dsl_api __method__, "filters"
|
93
|
+
end
|
94
|
+
|
95
|
+
def scope(scope_class)
|
96
|
+
deprecated_dsl_api __method__, "scopes"
|
97
|
+
end
|
98
|
+
|
99
|
+
# This resolves the scope when doing "where" queries (not find queries)
|
100
|
+
#
|
101
|
+
# It's used to apply the authorization feature.
|
102
|
+
def query_scope
|
103
|
+
authorization.apply_policy Avo::ExecutionContext.new(
|
104
|
+
target: index_query,
|
105
|
+
query: model_class
|
106
|
+
).handle
|
107
|
+
end
|
108
|
+
|
109
|
+
# This resolves the scope when finding records (not "where" queries)
|
110
|
+
#
|
111
|
+
# It's used to apply the authorization feature.
|
112
|
+
def find_scope
|
113
|
+
authorization.apply_policy model_class
|
114
|
+
end
|
115
|
+
|
116
|
+
def authorization
|
117
|
+
Avo::Services::AuthorizationService.new Avo::Current.user, model_class, policy_class: authorization_policy
|
118
|
+
end
|
119
|
+
|
120
|
+
def valid_association_name(record, association_name)
|
121
|
+
association_name if record._reflections.with_indifferent_access[association_name].present?
|
122
|
+
end
|
123
|
+
|
124
|
+
def valid_attachment_name(record, association_name)
|
125
|
+
association_name if record.class.reflect_on_attachment(association_name).present?
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_available_models
|
129
|
+
ApplicationRecord.descendants
|
130
|
+
end
|
131
|
+
|
132
|
+
def get_model_by_name(model_name)
|
133
|
+
get_available_models.find do |m|
|
134
|
+
m.to_s == model_name.to_s
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the model class being used for this resource.
|
139
|
+
#
|
140
|
+
# The Resource instance has a model_class method too so it can support the STI use cases
|
141
|
+
# where we figure out the model class from the record
|
142
|
+
def model_class(record_class: nil)
|
143
|
+
# get the model class off of the static property
|
144
|
+
return @model_class if @model_class.present?
|
145
|
+
|
146
|
+
# get the model class off of the record for STI models
|
147
|
+
return record_class if record_class.present?
|
148
|
+
|
149
|
+
# generate a model class
|
150
|
+
class_name.safe_constantize
|
151
|
+
end
|
152
|
+
|
153
|
+
# This is used as the model class ID
|
154
|
+
# We use this instead of the route_key to maintain compatibility with uncountable models
|
155
|
+
# With uncountable models route key appends an _index suffix (Fish->fish_index)
|
156
|
+
# Example: User->users, MediaItem->media_items, Fish->fish
|
157
|
+
def model_key
|
158
|
+
@model_key ||= model_class.model_name.plural
|
159
|
+
end
|
160
|
+
|
161
|
+
def class_name
|
162
|
+
@class_name ||= to_s.demodulize
|
163
|
+
end
|
164
|
+
|
165
|
+
def route_key
|
166
|
+
class_name.underscore.pluralize
|
167
|
+
end
|
168
|
+
|
169
|
+
def singular_route_key
|
170
|
+
route_key.singularize
|
171
|
+
end
|
172
|
+
|
173
|
+
def translation_key
|
174
|
+
@translation_key || "avo.resource_translations.#{class_name.underscore}"
|
175
|
+
end
|
176
|
+
|
177
|
+
def name
|
178
|
+
@name ||= name_from_translation_key(count: 1, default: class_name.underscore.humanize)
|
179
|
+
end
|
180
|
+
alias_method :singular_name, :name
|
181
|
+
|
182
|
+
def plural_name
|
183
|
+
name_from_translation_key(count: 2, default: name.pluralize)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Get the name from the translation_key and fallback to default
|
187
|
+
# It can raise I18n::InvalidPluralizationData when using only resource_translation without pluralization keys like: one, two or other key
|
188
|
+
# Example:
|
189
|
+
# ---
|
190
|
+
# en:
|
191
|
+
# avo:
|
192
|
+
# resource_translations:
|
193
|
+
# product:
|
194
|
+
# save: "Save product"
|
195
|
+
def name_from_translation_key(count:, default:)
|
196
|
+
t(translation_key, count:, default:).humanize
|
197
|
+
rescue I18n::InvalidPluralizationData
|
198
|
+
default
|
199
|
+
end
|
200
|
+
|
201
|
+
def underscore_name
|
202
|
+
return @name if @name.present?
|
203
|
+
|
204
|
+
name.demodulize.underscore
|
205
|
+
end
|
206
|
+
|
207
|
+
def navigation_label
|
208
|
+
plural_name.humanize
|
209
|
+
end
|
210
|
+
|
211
|
+
def find_record(id, query: nil, params: nil)
|
212
|
+
query ||= find_scope # If no record is given we'll use the default
|
213
|
+
|
214
|
+
if single_includes.present?
|
215
|
+
query = query.includes(*single_includes)
|
216
|
+
end
|
217
|
+
|
218
|
+
if single_attachments.present?
|
219
|
+
single_attachments.each do |attachment|
|
220
|
+
query = query.send(:"with_attached_#{attachment}")
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
Avo::ExecutionContext.new(
|
225
|
+
target: find_record_method,
|
226
|
+
query: query,
|
227
|
+
id: id,
|
228
|
+
params: params
|
229
|
+
).handle
|
230
|
+
end
|
231
|
+
|
232
|
+
def search_query
|
233
|
+
search.dig(:query)
|
234
|
+
end
|
235
|
+
|
236
|
+
def search_results_count
|
237
|
+
search.dig(:results_count)
|
238
|
+
end
|
239
|
+
|
240
|
+
def fetch_search(key, record: nil)
|
241
|
+
# self.class.fetch_search
|
242
|
+
Avo::ExecutionContext.new(target: search[key], resource: self, record: record).handle
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
delegate :context, to: ::Avo::Current
|
247
|
+
delegate :name, to: :class
|
248
|
+
delegate :singular_name, to: :class
|
249
|
+
delegate :plural_name, to: :class
|
250
|
+
delegate :underscore_name, to: :class
|
251
|
+
delegate :to_param, to: :class
|
252
|
+
delegate :find_record, to: :class
|
253
|
+
delegate :model_key, to: :class
|
254
|
+
delegate :tab, to: :items_holder
|
255
|
+
|
256
|
+
def initialize(record: nil, view: nil, user: nil, params: nil)
|
257
|
+
@view = Avo::ViewInquirer.new(view) if view.present?
|
258
|
+
@user = user if user.present?
|
259
|
+
@params = params if params.present?
|
260
|
+
|
261
|
+
if record.present?
|
262
|
+
@record = record
|
263
|
+
|
264
|
+
hydrate_model_with_default_values if @view&.new?
|
265
|
+
end
|
266
|
+
|
267
|
+
unless self.class.model_class.present?
|
268
|
+
if model_class.present? && model_class.respond_to?(:base_class)
|
269
|
+
self.class.model_class = model_class.base_class
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def detect_fields
|
275
|
+
self.items_holder = Avo::Resources::Items::Holder.new(parent: self)
|
276
|
+
|
277
|
+
# Used in testing to replace items
|
278
|
+
if temporary_items.present?
|
279
|
+
instance_eval(&temporary_items)
|
280
|
+
else
|
281
|
+
fetch_fields
|
282
|
+
end
|
283
|
+
|
284
|
+
self
|
285
|
+
end
|
286
|
+
|
287
|
+
VIEW_METHODS_MAPPING = {
|
288
|
+
index: [:index_fields, :display_fields],
|
289
|
+
show: [:show_fields, :display_fields],
|
290
|
+
edit: [:edit_fields, :form_fields],
|
291
|
+
update: [:edit_fields, :form_fields],
|
292
|
+
new: [:new_fields, :form_fields],
|
293
|
+
create: [:new_fields, :form_fields]
|
294
|
+
} unless defined? VIEW_METHODS_MAPPING
|
295
|
+
|
296
|
+
def fetch_fields
|
297
|
+
possible_methods_for_view = VIEW_METHODS_MAPPING[view.to_sym]
|
298
|
+
|
299
|
+
# Safe navigation operator is used because the view can be "destroy" or "preview"
|
300
|
+
possible_methods_for_view&.each do |method_for_view|
|
301
|
+
return send(method_for_view) if respond_to?(method_for_view)
|
302
|
+
end
|
303
|
+
|
304
|
+
fields
|
305
|
+
end
|
306
|
+
|
307
|
+
def fetch_cards
|
308
|
+
cards
|
309
|
+
end
|
310
|
+
|
311
|
+
def divider(label = nil)
|
312
|
+
entity_loader(:action).use({class: Divider, label: label}.compact)
|
313
|
+
end
|
314
|
+
|
315
|
+
# def fields / def cards
|
316
|
+
[:fields, :cards].each do |method_name|
|
317
|
+
define_method method_name do
|
318
|
+
# Empty method
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
[:action, :filter, :scope].each do |entity|
|
323
|
+
plural_entity = entity.to_s.pluralize
|
324
|
+
|
325
|
+
# def actions / def filters / def scopes
|
326
|
+
define_method plural_entity do
|
327
|
+
# blank entity method
|
328
|
+
end
|
329
|
+
|
330
|
+
# def action / def filter / def scope
|
331
|
+
define_method entity do |entity_class, arguments: {}, icon: nil|
|
332
|
+
entity_loader(entity).use({class: entity_class, arguments: arguments, icon: icon}.compact)
|
333
|
+
end
|
334
|
+
|
335
|
+
# def get_actions / def get_filters / def get_scopes
|
336
|
+
define_method "get_#{plural_entity}" do
|
337
|
+
return entity_loader(entity).bag if entity_loader(entity).present?
|
338
|
+
|
339
|
+
# ex: @actions_loader = Avo::Loaders::ActionsLoader.new
|
340
|
+
instance_variable_set(
|
341
|
+
"@#{plural_entity}_loader",
|
342
|
+
"Avo::Loaders::#{plural_entity.humanize}Loader".constantize.new
|
343
|
+
)
|
344
|
+
|
345
|
+
send plural_entity
|
346
|
+
|
347
|
+
entity_loader(entity).bag
|
348
|
+
end
|
349
|
+
|
350
|
+
# def get_action_arguments / def get_filter_arguments / def get_scope_arguments
|
351
|
+
define_method "get_#{entity}_arguments" do |entity_class|
|
352
|
+
klass = send("get_#{plural_entity}").find { |entity| entity[:class].to_s == entity_class.to_s }
|
353
|
+
|
354
|
+
raise "Couldn't find '#{entity_class}' in the 'def #{plural_entity}' method on your '#{self.class}' resource." if klass.nil?
|
355
|
+
|
356
|
+
klass[:arguments]
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def hydrate(...)
|
361
|
+
super(...)
|
362
|
+
|
363
|
+
if @record.present?
|
364
|
+
hydrate_model_with_default_values if @view&.new?
|
365
|
+
end
|
366
|
+
|
367
|
+
self
|
368
|
+
end
|
369
|
+
|
370
|
+
def default_panel_name
|
371
|
+
return @params[:related_name].capitalize if @params.present? && @params[:related_name].present?
|
372
|
+
|
373
|
+
case @view.to_sym
|
374
|
+
when :show
|
375
|
+
record_title
|
376
|
+
when :edit
|
377
|
+
record_title
|
378
|
+
when :new
|
379
|
+
t("avo.create_new_item", item: name.humanize(capitalize: false)).upcase_first
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Returns the model class being used for this resource.
|
384
|
+
#
|
385
|
+
# We use the class method as a fallback but we pass it the record too so it can support the STI use cases
|
386
|
+
# where we figure out the model class from that record.
|
387
|
+
def model_class
|
388
|
+
record_class = @record&.class
|
389
|
+
|
390
|
+
self.class.model_class record_class: record_class
|
391
|
+
end
|
392
|
+
|
393
|
+
def record_title
|
394
|
+
return name if @record.nil?
|
395
|
+
|
396
|
+
# Get the title from the record if title is not set, try to get the name, title or label, or fallback to the id
|
397
|
+
return @record.try(:name) || @record.try(:title) || @record.try(:label) || @record.id if title.nil?
|
398
|
+
|
399
|
+
# If the title is a symbol, get the value from the record else execute the block/string
|
400
|
+
case title
|
401
|
+
when Symbol
|
402
|
+
@record.send title
|
403
|
+
when Proc
|
404
|
+
Avo::ExecutionContext.new(target: title, resource: self, record: @record).handle
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def available_view_types
|
409
|
+
if self.class.view_types.present?
|
410
|
+
return Array(
|
411
|
+
Avo::ExecutionContext.new(
|
412
|
+
target: self.class.view_types,
|
413
|
+
resource: self,
|
414
|
+
record: record
|
415
|
+
).handle
|
416
|
+
)
|
417
|
+
end
|
418
|
+
|
419
|
+
view_types = [:table]
|
420
|
+
|
421
|
+
view_types << :grid if self.class.grid_view.present?
|
422
|
+
view_types << :map if map_view.present?
|
423
|
+
|
424
|
+
view_types
|
425
|
+
end
|
426
|
+
|
427
|
+
def attachment_fields
|
428
|
+
get_field_definitions.select do |field|
|
429
|
+
[Avo::Fields::FileField, Avo::Fields::FilesField].include? field.class
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# Map the received params to their actual fields
|
434
|
+
def fields_by_database_id
|
435
|
+
get_field_definitions
|
436
|
+
.reject do |field|
|
437
|
+
field.computed
|
438
|
+
end
|
439
|
+
.map do |field|
|
440
|
+
[field.database_id.to_s, field]
|
441
|
+
end
|
442
|
+
.to_h
|
443
|
+
end
|
444
|
+
|
445
|
+
def fill_record(record, params, extra_params: [])
|
446
|
+
# Write the field values
|
447
|
+
params.each do |key, value|
|
448
|
+
field = fields_by_database_id[key]
|
449
|
+
|
450
|
+
next unless field.present?
|
451
|
+
|
452
|
+
record = field.fill_field record, key, value, params
|
453
|
+
end
|
454
|
+
|
455
|
+
# Write the user configured extra params to the record
|
456
|
+
if extra_params.present?
|
457
|
+
# Let Rails fill in the rest of the params
|
458
|
+
record.assign_attributes params.permit(extra_params)
|
459
|
+
end
|
460
|
+
|
461
|
+
record
|
462
|
+
end
|
463
|
+
|
464
|
+
def authorization(user: nil)
|
465
|
+
current_user = user || Avo::Current.user
|
466
|
+
Avo::Services::AuthorizationService.new(current_user, record || model_class, policy_class: authorization_policy)
|
467
|
+
end
|
468
|
+
|
469
|
+
def file_hash
|
470
|
+
content_to_be_hashed = ""
|
471
|
+
|
472
|
+
resource_path = Rails.root.join("app", "avo", "resources", "#{file_name}.rb").to_s
|
473
|
+
if File.file? resource_path
|
474
|
+
content_to_be_hashed += File.read(resource_path)
|
475
|
+
end
|
476
|
+
|
477
|
+
# policy file hash
|
478
|
+
policy_path = Rails.root.join("app", "policies", "#{file_name.gsub("_resource", "")}_policy.rb").to_s
|
479
|
+
if File.file? policy_path
|
480
|
+
content_to_be_hashed += File.read(policy_path)
|
481
|
+
end
|
482
|
+
|
483
|
+
Digest::MD5.hexdigest(content_to_be_hashed)
|
484
|
+
end
|
485
|
+
|
486
|
+
def file_name
|
487
|
+
@file_name ||= self.class.underscore_name.tr(" ", "_")
|
488
|
+
end
|
489
|
+
|
490
|
+
def cache_hash(parent_record)
|
491
|
+
result = [record, file_hash]
|
492
|
+
|
493
|
+
if parent_record.present?
|
494
|
+
result << parent_record
|
495
|
+
end
|
496
|
+
|
497
|
+
result
|
498
|
+
end
|
499
|
+
|
500
|
+
# We will not overwrite any attributes that come pre-filled in the record.
|
501
|
+
def hydrate_model_with_default_values
|
502
|
+
default_values = get_fields
|
503
|
+
.select do |field|
|
504
|
+
!field.computed && !field.is_a?(Avo::Fields::HeadingField)
|
505
|
+
end
|
506
|
+
.map do |field|
|
507
|
+
value = field.value
|
508
|
+
|
509
|
+
if field.type == "belongs_to"
|
510
|
+
|
511
|
+
reflection = @record._reflections.with_indifferent_access[@params[:via_relation]]
|
512
|
+
|
513
|
+
if field.polymorphic_as.present? && field.types.map(&:to_s).include?(@params[:via_relation_class])
|
514
|
+
# set the value to the actual record
|
515
|
+
via_resource = Avo.resource_manager.get_resource_by_model_class(@params[:via_relation_class])
|
516
|
+
value = via_resource.find_record(@params[:via_record_id])
|
517
|
+
elsif reflection.present? && reflection.foreign_key.present? && field.id.to_s == @params[:via_relation].to_s
|
518
|
+
resource = Avo.resource_manager.get_resource_by_model_class params[:via_relation_class]
|
519
|
+
record = resource.find_record @params[:via_record_id], params: params
|
520
|
+
id_param = reflection.options[:primary_key] || :id
|
521
|
+
|
522
|
+
value = record.send(id_param)
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
[field, value]
|
527
|
+
end
|
528
|
+
.to_h
|
529
|
+
.select do |_, value|
|
530
|
+
value.present?
|
531
|
+
end
|
532
|
+
|
533
|
+
default_values.each do |field, value|
|
534
|
+
field.assign_value record: @record, value: value
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def model_name
|
539
|
+
model_class.model_name
|
540
|
+
end
|
541
|
+
|
542
|
+
def singular_model_key
|
543
|
+
model_class.model_name.singular
|
544
|
+
end
|
545
|
+
|
546
|
+
def record_path
|
547
|
+
resource_path(record: record, resource: self)
|
548
|
+
end
|
549
|
+
|
550
|
+
def records_path
|
551
|
+
resources_path(resource: self)
|
552
|
+
end
|
553
|
+
|
554
|
+
def avatar_field
|
555
|
+
get_field_definitions.find do |field|
|
556
|
+
field.as_avatar.present?
|
557
|
+
end
|
558
|
+
rescue
|
559
|
+
nil
|
560
|
+
end
|
561
|
+
|
562
|
+
def avatar
|
563
|
+
return avatar_field.to_image if avatar_field.respond_to? :to_image
|
564
|
+
|
565
|
+
return avatar_field.value.variant(resize_to_limit: [480, 480]) if avatar_field.type == "file"
|
566
|
+
|
567
|
+
avatar_field.value
|
568
|
+
rescue
|
569
|
+
nil
|
570
|
+
end
|
571
|
+
|
572
|
+
def avatar_type
|
573
|
+
avatar_field.as_avatar
|
574
|
+
rescue
|
575
|
+
nil
|
576
|
+
end
|
577
|
+
|
578
|
+
def form_scope
|
579
|
+
model_class.base_class.to_s.underscore.downcase
|
580
|
+
end
|
581
|
+
|
582
|
+
def has_record_id?
|
583
|
+
record.present? && record_id.present?
|
584
|
+
end
|
585
|
+
|
586
|
+
def id_attribute
|
587
|
+
:id
|
588
|
+
end
|
589
|
+
|
590
|
+
def record_id
|
591
|
+
record.send(id_attribute)
|
592
|
+
end
|
593
|
+
|
594
|
+
def description_attributes
|
595
|
+
{
|
596
|
+
view: view,
|
597
|
+
resource: self,
|
598
|
+
record: record
|
599
|
+
}
|
600
|
+
end
|
601
|
+
|
602
|
+
def entity_loader(entity)
|
603
|
+
instance_variable_get("@#{entity.to_s.pluralize}_loader")
|
604
|
+
end
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|
@@ -5,6 +5,7 @@ class Avo::Resources::Items::ItemGroup
|
|
5
5
|
include Avo::Concerns::HasItemType
|
6
6
|
include Avo::Concerns::VisibleItems
|
7
7
|
include Avo::Concerns::VisibleInDifferentViews
|
8
|
+
include Avo::Concerns::IsVisible
|
8
9
|
|
9
10
|
attr_reader :name
|
10
11
|
attr_reader :description
|
@@ -17,6 +18,7 @@ class Avo::Resources::Items::ItemGroup
|
|
17
18
|
@description = description
|
18
19
|
@items_holder = Avo::Resources::Items::Holder.new
|
19
20
|
@args = args
|
21
|
+
@visible = args[:visible]
|
20
22
|
|
21
23
|
post_initialize if respond_to?(:post_initialize)
|
22
24
|
end
|
@@ -4,6 +4,7 @@ class Avo::Resources::Items::Tab
|
|
4
4
|
include Avo::Concerns::HasItems
|
5
5
|
include Avo::Concerns::HasItemType
|
6
6
|
include Avo::Concerns::VisibleItems
|
7
|
+
include Avo::Concerns::IsVisible
|
7
8
|
include Avo::Concerns::VisibleInDifferentViews
|
8
9
|
|
9
10
|
delegate :items, :add_item, to: :items_holder
|
@@ -16,6 +17,7 @@ class Avo::Resources::Items::Tab
|
|
16
17
|
@items_holder = Avo::Resources::Items::Holder.new
|
17
18
|
@view = Avo::ViewInquirer.new view
|
18
19
|
@args = args
|
20
|
+
@visible = args[:visible]
|
19
21
|
|
20
22
|
post_initialize if respond_to?(:post_initialize)
|
21
23
|
end
|
data/lib/avo/version.rb
CHANGED
data/lib/avo.rb
CHANGED
@@ -146,7 +146,7 @@ module Generators
|
|
146
146
|
[dest_rb, dest_erb].each do |path|
|
147
147
|
if component.starts_with?("avo/views/")
|
148
148
|
modified_content = File.read(path).gsub("Avo::Views::", "Avo::Views::#{options[:scope].camelize}::")
|
149
|
-
elsif component.starts_with?("avo/fields/")
|
149
|
+
elsif component.starts_with?("avo/fields/") && options["field-components"].present?
|
150
150
|
modified_content = File.read(path).gsub("#{options["field-components"].camelize}Field", "#{options[:scope].camelize}::#{options["field-components"].camelize}Field")
|
151
151
|
end
|
152
152
|
|