<%= @form.check_box @field.id,
+ value: @field.value,
checked: @field.value,
class: "text-lg h-4 w-4 checked:bg-primary-400 focus:checked:!bg-primary-400 #{@field.get_html(:classes, view: view, element: :input)}",
data: @field.get_html(:data, view: view, element: :input),
diff --git a/app/components/avo/fields/code_field/edit_component.html.erb b/app/components/avo/fields/code_field/edit_component.html.erb
index eb9184f99d..39823519af 100644
--- a/app/components/avo/fields/code_field/edit_component.html.erb
+++ b/app/components/avo/fields/code_field/edit_component.html.erb
@@ -1,6 +1,7 @@
<%= field_wrapper **field_wrapper_args, full_width: true do %>
<%= @form.text_area @field.id,
+ value: @field.value,
class: classes("w-full"),
data: {
'code-field-target': 'element',
diff --git a/app/components/avo/fields/country_field/edit_component.html.erb b/app/components/avo/fields/country_field/edit_component.html.erb
index e17dc3c386..eceb336c1b 100644
--- a/app/components/avo/fields/country_field/edit_component.html.erb
+++ b/app/components/avo/fields/country_field/edit_component.html.erb
@@ -1,5 +1,6 @@
<%= field_wrapper **field_wrapper_args do %>
<%= @form.select @field.id, @field.select_options, {
+ value: @field.value,
selected: @field.value,
include_blank: @field.include_blank
},
diff --git a/app/components/avo/fields/markdown_field/edit_component.html.erb b/app/components/avo/fields/markdown_field/edit_component.html.erb
index f3ebb4eb13..3985405f60 100644
--- a/app/components/avo/fields/markdown_field/edit_component.html.erb
+++ b/app/components/avo/fields/markdown_field/edit_component.html.erb
@@ -1,6 +1,7 @@
<%= field_wrapper **field_wrapper_args, full_width: true do %>
<%= @form.text_area @field.id,
+ value: @field.value,
class: classes("w-full js-has-easy-mde-editor"),
data: {
view: view,
diff --git a/app/components/avo/fields/number_field/edit_component.html.erb b/app/components/avo/fields/number_field/edit_component.html.erb
index 0a23e7131d..173b997214 100644
--- a/app/components/avo/fields/number_field/edit_component.html.erb
+++ b/app/components/avo/fields/number_field/edit_component.html.erb
@@ -1,5 +1,6 @@
<%= field_wrapper **field_wrapper_args do %>
<%= @form.number_field @field.id,
+ value: @field.value,
class: classes("w-full"),
data: @field.get_html(:data, view: view, element: :input),
disabled: disabled?,
diff --git a/app/components/avo/fields/password_field/edit_component.html.erb b/app/components/avo/fields/password_field/edit_component.html.erb
index 2b41112ed4..d333690853 100644
--- a/app/components/avo/fields/password_field/edit_component.html.erb
+++ b/app/components/avo/fields/password_field/edit_component.html.erb
@@ -1,5 +1,6 @@
<%= field_wrapper **field_wrapper_args do %>
<%= @form.password_field @field.id,
+ value: @field.value,
class: classes("w-full"),
data: @field.get_html(:data, view: view, element: :input),
disabled: disabled?,
diff --git a/app/components/avo/fields/progress_bar_field/edit_component.html.erb b/app/components/avo/fields/progress_bar_field/edit_component.html.erb
index 59bae9bd99..3caaba3183 100644
--- a/app/components/avo/fields/progress_bar_field/edit_component.html.erb
+++ b/app/components/avo/fields/progress_bar_field/edit_component.html.erb
@@ -6,6 +6,7 @@
<% end %>
<%= @form.range_field @field.id,
+ value: @field.value,
class: "w-full #{@field.get_html(:classes, view: view, element: :input)}",
data: {
action: "input->progress-bar-field#update",
diff --git a/app/components/avo/fields/status_field/edit_component.html.erb b/app/components/avo/fields/status_field/edit_component.html.erb
index 745443558f..72853f5ce7 100644
--- a/app/components/avo/fields/status_field/edit_component.html.erb
+++ b/app/components/avo/fields/status_field/edit_component.html.erb
@@ -5,6 +5,6 @@
disabled: disabled?,
placeholder: @field.placeholder,
style: @field.get_html(:style, view: view, element: :input),
- value: @resource.model.present? ? @resource.model[@field.id] : @field.value
+ value: @field.value
%>
<% end %>
diff --git a/app/components/avo/fields/text_field/edit_component.html.erb b/app/components/avo/fields/text_field/edit_component.html.erb
index a74713606d..0283bbd7c5 100644
--- a/app/components/avo/fields/text_field/edit_component.html.erb
+++ b/app/components/avo/fields/text_field/edit_component.html.erb
@@ -1,11 +1,11 @@
<%= field_wrapper **field_wrapper_args do %>
<%= form.text_field @field.id,
+ value: @field.value,
class: classes("w-full"),
data: @field.get_html(:data, view: view, element: :input),
disabled: disabled?,
placeholder: @field.placeholder,
style: @field.get_html(:style, view: view, element: :input),
- # value: @field.value,
multiple: multiple,
autocomplete: @field.autocomplete
%>
diff --git a/app/components/avo/fields/textarea_field/edit_component.html.erb b/app/components/avo/fields/textarea_field/edit_component.html.erb
index 45ebdc2298..88f9d279b5 100644
--- a/app/components/avo/fields/textarea_field/edit_component.html.erb
+++ b/app/components/avo/fields/textarea_field/edit_component.html.erb
@@ -1,5 +1,6 @@
<%= field_wrapper **field_wrapper_args do %>
<%= @form.text_area @field.id,
+ value: @field.value,
class: classes("w-full"),
data: @field.get_html(:data, view: view, element: :input),
disabled: disabled?,
diff --git a/app/components/avo/fields/trix_field/edit_component.html.erb b/app/components/avo/fields/trix_field/edit_component.html.erb
index d106cfef55..8fe18ef815 100644
--- a/app/components/avo/fields/trix_field/edit_component.html.erb
+++ b/app/components/avo/fields/trix_field/edit_component.html.erb
@@ -23,6 +23,7 @@
<%= sanitize @field.value.to_s %>
<% end %>
<%= @form.text_area @field.id,
+ value: @field.value,
class: classes("w-full hidden"),
data: @field.get_html(:data, view: view, element: :input),
disabled: disabled?,
diff --git a/app/components/avo/views/resource_edit_component.html.erb b/app/components/avo/views/resource_edit_component.html.erb
index 23add17c99..6f13568b2d 100644
--- a/app/components/avo/views/resource_edit_component.html.erb
+++ b/app/components/avo/views/resource_edit_component.html.erb
@@ -7,7 +7,7 @@
selected_resources: [@resource.model.id],
**@resource.stimulus_data_attributes
} do %>
- <%= form_with model: @resource.model,
+ <%= form_with model: @resource.record,
scope: @resource.form_scope,
url: form_url,
method: form_method,
diff --git a/lib/avo/base_resource.rb b/lib/avo/base_resource.rb
index bbfc86df6d..27e1b7cbff 100644
--- a/lib/avo/base_resource.rb
+++ b/lib/avo/base_resource.rb
@@ -151,7 +151,8 @@ def initialize
def record
@model
end
- alias :model :record
+ alias_method :model, :record
+
def hydrate(model: nil, view: nil, user: nil, params: nil)
@view = view if view.present?
@@ -307,9 +308,9 @@ def attachment_fields
end
end
- def fill_model(model, params, extra_params: [])
- # Map the received params to their actual fields
- fields_by_database_id = get_field_definitions
+ # Map the received params to their actual fields.
+ def fields_by_database_id
+ get_field_definitions
.reject do |field|
field.computed
end
@@ -317,7 +318,9 @@ def fill_model(model, params, extra_params: [])
[field.database_id.to_s, field]
end
.to_h
+ end
+ def fill_model(model, params, extra_params: [])
# Write the field values
params.each do |key, value|
field = fields_by_database_id[key]
diff --git a/lib/avo/execution_context.rb b/lib/avo/execution_context.rb
new file mode 100644
index 0000000000..e082aefb20
--- /dev/null
+++ b/lib/avo/execution_context.rb
@@ -0,0 +1,43 @@
+module Avo
+ class ExecutionContext
+ attr_accessor :target, :context, :params, :view_context, :current_user, :request, :include, :main_app, :avo
+
+ def initialize(**args)
+ # Extend the class with custom modules if required.
+ if args[:include].present?
+ args[:include].each do |mod|
+ self.class.send(:include, mod)
+ end
+ end
+
+ # If you want this block to behave like a view you can delegate the missing methods to the view_context
+ #
+ # Ex: Avo::ExecutionContext.new(target: ..., delegate_missing_to: :view_context).handle
+ if args[:delegate_missing_to].present?
+ self.class.send(:delegate_missing_to, args[:delegate_missing_to])
+ end
+
+ # If target doesn't respond to call, we don't need to initialize the others attr_accessors.
+ return unless (@target = args[:target]).respond_to? :call
+
+ args.except(:target).each do |key, value|
+ singleton_class.class_eval { attr_accessor key }
+ instance_variable_set("@#{key}", value)
+ end
+
+ # Set defaults on not initialized accessors
+ @context ||= Avo::App.context
+ @params ||= Avo::App.params
+ @view_context ||= Avo::App.view_context
+ @current_user ||= Avo::App.current_user
+ @request ||= view_context.request
+ @main_app ||= view_context.main_app
+ @avo ||= view_context.avo
+ end
+
+ # Return target if target is not callable, otherwise, execute target on this instance context
+ def handle
+ target.respond_to?(:call) ? instance_exec(&target) : target
+ end
+ end
+end
diff --git a/lib/avo/fields/base_field.rb b/lib/avo/fields/base_field.rb
index 6441a8d64f..a6a6a68fb1 100644
--- a/lib/avo/fields/base_field.rb
+++ b/lib/avo/fields/base_field.rb
@@ -70,6 +70,7 @@ def initialize(id, **args, &block)
@nullable = args[:nullable] || false
@null_values = args[:null_values] || [nil, ""]
@format_using = args[:format_using] || nil
+ @update_using = args[:update_using] || nil
@placeholder = args[:placeholder]
@autocomplete = args[:autocomplete] || nil
@help = args[:help] || nil
@@ -183,20 +184,31 @@ def value(property = nil)
final_value = instance_exec(@model, @resource, @view, self, &block)
end
- # Run the value through resolver if present
- final_value = instance_exec(final_value, &@format_using) if @format_using.present?
-
- final_value
+ if @format_using.present?
+ # Apply the changes in the
+ Avo::ExecutionContext.new(target: @format_using, model: model, key: property, value: final_value, resource: resource, view: view, field: self, delegate_missing_to: :view_context).handle
+ else
+ final_value
+ end
end
+ # Fills the model with the received value on create and update actions.
def fill_field(model, key, value, params)
return model unless model.methods.include? key.to_sym
- model.send("#{key}=", value)
+ if @update_using.present?
+ value = update_using(model, key, value, params)
+ end
+
+ model.public_send("#{key}=", value)
model
end
+ def update_using(model, key, value, params)
+ Avo::ExecutionContext.new(target: @update_using, model: model, key: key, value: value, resource: resource, field: self).handle
+ end
+
# Try to see if the field has a different database ID than it's name
def database_id
foreign_key
diff --git a/spec/dummy/app/avo/resources/city_resource.rb b/spec/dummy/app/avo/resources/city_resource.rb
index f6846e72ae..083cc28c61 100644
--- a/spec/dummy/app/avo/resources/city_resource.rb
+++ b/spec/dummy/app/avo/resources/city_resource.rb
@@ -9,7 +9,7 @@ class CityResource < Avo::BaseResource
self.search_result_path = -> {
avo.resources_city_path record, custom: "yup"
}
- self.extra_params = [:fish_type, :something_else, properties: [], information: [:name, :history]]
+ self.extra_params = [city: [:name, :metadata, :coordinates, :city_center_area, :description, :population, :is_capital, :image_url, :tiny_description, :status, features: {}, metadata: {}]]
self.default_view_type = :map
self.map_view = {
mapkick_options: {
@@ -43,12 +43,23 @@ class CityResource < Avo::BaseResource
color: "#009099"
}
field :description, as: :trix, attachment_key: :description_file, visible: ->(resource:) { resource.params[:show_native_fields].blank? }
+ field :metadata,
+ as: :code,
+ format_using: -> {
+ if view == :edit
+ JSON.generate(value)
+ else
+ value
+ end
+ },
+ update_using: -> do
+ ActiveSupport::JSON.decode(value)
+ end
with_options hide_on: :forms do
field :name, as: :text, help: "The name of your city"
field :population, as: :number
field :is_capital, as: :boolean
field :features, as: :key_value
- field :metadata, as: :code
field :image_url, as: :external_image
field :tiny_description, as: :markdown
field :status, as: :badge, enum: ::City.statuses
diff --git a/spec/dummy/app/avo/resources/comment_resource.rb b/spec/dummy/app/avo/resources/comment_resource.rb
index 242b9d4fc1..9b25799b6d 100644
--- a/spec/dummy/app/avo/resources/comment_resource.rb
+++ b/spec/dummy/app/avo/resources/comment_resource.rb
@@ -10,7 +10,7 @@ class CommentResource < Avo::BaseResource
self.after_update_path = :index
field :id, as: :id
- field :body, as: :textarea, format_using: ->(value) do
+ field :body, as: :textarea, format_using: -> do
if view == :show
content_tag(:div, style: "white-space: pre-line") { value }
else
diff --git a/spec/dummy/app/avo/resources/photo_comment_resource.rb b/spec/dummy/app/avo/resources/photo_comment_resource.rb
index 346169b0d3..2b310fc27d 100644
--- a/spec/dummy/app/avo/resources/photo_comment_resource.rb
+++ b/spec/dummy/app/avo/resources/photo_comment_resource.rb
@@ -14,7 +14,7 @@ class PhotoCommentResource < Avo::BaseResource
end
field :id, as: :id
- field :body, as: :textarea, format_using: ->(value) do
+ field :body, as: :textarea, format_using: -> do
if view == :show
content_tag(:div, style: "white-space: pre-line") { value }
else
diff --git a/spec/dummy/app/avo/resources/post_resource.rb b/spec/dummy/app/avo/resources/post_resource.rb
index a6c2b13970..1437d57dd3 100644
--- a/spec/dummy/app/avo/resources/post_resource.rb
+++ b/spec/dummy/app/avo/resources/post_resource.rb
@@ -36,7 +36,7 @@ class PostResource < Avo::BaseResource
enforce_suggestions: true,
help: "The only allowed values here are `one`, `two`, and `three`"
field :cover_photo, as: :file, is_image: true, as_avatar: :rounded, full_width: true, hide_on: [], accept: "image/*", display_filename: false
- field :cover_photo, as: :external_image, name: "Cover photo", required: true, hide_on: :all, link_to_resource: true, as_avatar: :rounded, format_using: ->(value) { value.present? ? value&.url : nil }
+ field :cover_photo, as: :external_image, name: "Cover photo", required: true, hide_on: :all, link_to_resource: true, as_avatar: :rounded, format_using: -> { value.present? ? value&.url : nil }
field :audio, as: :file, is_audio: true, accept: "audio/*"
field :excerpt, as: :text, hide_on: :all, as_description: true do |model|
extract_excerpt model.body
diff --git a/spec/dummy/app/avo/resources/product_resource.rb b/spec/dummy/app/avo/resources/product_resource.rb
index c97ce34cf6..e4523db310 100644
--- a/spec/dummy/app/avo/resources/product_resource.rb
+++ b/spec/dummy/app/avo/resources/product_resource.rb
@@ -31,7 +31,7 @@ class ProductResource < Avo::BaseResource
}
}
title :title, as: :text
- body :description, as: :textarea, format_using: ->(value) {
+ body :description, as: :textarea, format_using: -> {
simple_format value
}
end
diff --git a/spec/dummy/app/avo/resources/team_resource.rb b/spec/dummy/app/avo/resources/team_resource.rb
index 224d0d00ee..48ac415787 100644
--- a/spec/dummy/app/avo/resources/team_resource.rb
+++ b/spec/dummy/app/avo/resources/team_resource.rb
@@ -31,7 +31,7 @@ class TeamResource < Avo::BaseResource
rows: 5,
readonly: false,
hide_on: :index,
- format_using: ->(value) { value.to_s.truncate 30 },
+ format_using: -> { value.to_s.truncate 30 },
default: "This is a wonderful team!",
nullable: true,
null_values: ["0", "", "null", "nil"]
diff --git a/spec/dummy/app/avo/resources/user_resource.rb b/spec/dummy/app/avo/resources/user_resource.rb
index 06d8d791a0..b9065c6660 100644
--- a/spec/dummy/app/avo/resources/user_resource.rb
+++ b/spec/dummy/app/avo/resources/user_resource.rb
@@ -85,7 +85,7 @@ class UserResource < Avo::BaseResource
hide_on: :edit do |model, resource, view, field|
model.posts.to_a.size > 0 ? "yes" : "no"
end
- field :outside_link, as: :text, only_on: [:show], format_using: ->(url) { link_to("hey", url, target: "_blank") } do |model, *args|
+ field :outside_link, as: :text, only_on: [:show], format_using: -> { link_to("hey", value, target: "_blank") } do |model, *args|
main_app.hey_url
end
end
diff --git a/spec/dummy/app/avo/resources/z_post_resource.rb b/spec/dummy/app/avo/resources/z_post_resource.rb
index 3ad098a98a..bc1b1b923e 100644
--- a/spec/dummy/app/avo/resources/z_post_resource.rb
+++ b/spec/dummy/app/avo/resources/z_post_resource.rb
@@ -34,7 +34,7 @@ class ZPostResource < Avo::BaseResource
enforce_suggestions: true,
help: "The only allowed values here are `one`, `two`, and `three`"
field :cover_photo, as: :file, is_image: true, as_avatar: :rounded, full_width: true, hide_on: [], accept: "image/*"
- field :cover_photo, as: :external_image, name: "Cover photo", required: true, hide_on: :all, link_to_resource: true, as_avatar: :rounded, format_using: ->(value) { value.present? ? value&.url : nil }
+ field :cover_photo, as: :external_image, name: "Cover photo", required: true, hide_on: :all, link_to_resource: true, as_avatar: :rounded, format_using: -> { value.present? ? value&.url : nil }
field :audio, as: :file, is_audio: true, accept: "audio/*"
field :excerpt, as: :text, hide_on: :all, as_description: true do |model|
ActionView::Base.full_sanitizer.sanitize(model.body).truncate 130
diff --git a/spec/dummy/app/models/city.rb b/spec/dummy/app/models/city.rb
index ee19fa1375..be16db0adf 100644
--- a/spec/dummy/app/models/city.rb
+++ b/spec/dummy/app/models/city.rb
@@ -38,4 +38,13 @@ def coordinates=(value)
self.latitude = value.first
self.longitude = value.last
end
+
+ # alternative to format_using and update_using
+ # def json_metadata
+ # ActiveSupport::JSON.encode(metadata)
+ # end
+
+ # def json_metadata=(value)
+ # self.metadata = ActiveSupport::JSON.decode(value)
+ # end
end
diff --git a/spec/dummy/app/views/avo/resource_tools/_city_editor.html.erb b/spec/dummy/app/views/avo/resource_tools/_city_editor.html.erb
index 3cc8686453..0fe7921df3 100644
--- a/spec/dummy/app/views/avo/resource_tools/_city_editor.html.erb
+++ b/spec/dummy/app/views/avo/resource_tools/_city_editor.html.erb
@@ -7,7 +7,6 @@
<%= avo_edit_field(:number, :population, form: form, help: "The population of the city.") %>
<%= avo_edit_field(:is_capital, as: :boolean, form: form) %>
<%= avo_edit_field(:features, as: :key_value, form: form) %>
- <%= avo_show_field(:metadata, as: :code, form: form) %>
<%= avo_show_field(:image_url, as: :external_image, form: form) do |model|
'https://images.unsplash.com/photo-1660061993776-098c0ee403ac?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY2MDMxMzc4NA&ixlib=rb-1.2.1&q=80&w=1080'
end %>
diff --git a/spec/features/avo/native_fields_spec.rb b/spec/features/avo/native_fields_spec.rb
index 0e0e49e67d..d4b5ed2d94 100644
--- a/spec/features/avo/native_fields_spec.rb
+++ b/spec/features/avo/native_fields_spec.rb
@@ -10,7 +10,6 @@
expect(find_field('Population').value).to eq city.population.to_s
expect(find_field('Is capital').value).to eq "1"
expect(find_field('Features').value).to eq "\"#{city.features}\""
- expect(find_field('metadata', visible: false, disabled: true).value).to eq city.metadata
expect(page).to have_css 'img[src="https://images.unsplash.com/photo-1660061993776-098c0ee403ac?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY2MDMxMzc4NA&ixlib=rb-1.2.1&q=80&w=1080"]'
expect(find_field('Image url').value).to eq city.image_url
@@ -31,7 +30,6 @@
fill_in 'city[population]', with: 101
find('[name="city[is_capital]"]').set(false)
fill_in 'city[features]', with: "{\"hey\": \"features\"}"
- find_field('metadata', visible: false, disabled: true).set("{\"hey\": \"metadata\"}")
click_on "Save"
diff --git a/spec/system/avo/date_time_fields/date_time_eastern_spec.rb b/spec/system/avo/date_time_fields/date_time_eastern_spec.rb
index 05e6aab820..9f80c9f97c 100644
--- a/spec/system/avo/date_time_fields/date_time_eastern_spec.rb
+++ b/spec/system/avo/date_time_fields/date_time_eastern_spec.rb
@@ -7,7 +7,7 @@
subject(:text_input) { find '[data-field-id="posted_at"] [data-controller="date-field"] input[type="text"]' }
before do
CommentResource.with_temporary_items do
- field :body, as: :textarea, format_using: ->(value) do
+ field :body, as: :textarea, format_using: -> do
if view == :show
content_tag(:div, style: "white-space: pre-line") { value }
else
diff --git a/spec/system/avo/date_time_fields/date_time_utc_spec.rb b/spec/system/avo/date_time_fields/date_time_utc_spec.rb
index 3c51529505..5389e33af6 100644
--- a/spec/system/avo/date_time_fields/date_time_utc_spec.rb
+++ b/spec/system/avo/date_time_fields/date_time_utc_spec.rb
@@ -8,7 +8,7 @@
before do
CommentResource.with_temporary_items do
- field :body, as: :textarea, format_using: ->(value) do
+ field :body, as: :textarea, format_using: -> do
if view == :show
content_tag(:div, style: "white-space: pre-line") { value }
else
diff --git a/spec/system/avo/date_time_fields/date_time_western_spec.rb b/spec/system/avo/date_time_fields/date_time_western_spec.rb
index 03bb0dee88..3dfae8dfa6 100644
--- a/spec/system/avo/date_time_fields/date_time_western_spec.rb
+++ b/spec/system/avo/date_time_fields/date_time_western_spec.rb
@@ -8,7 +8,7 @@
before do
CommentResource.with_temporary_items do
- field :body, as: :textarea, format_using: ->(value) do
+ field :body, as: :textarea, format_using: -> do
if view == :show
content_tag(:div, style: "white-space: pre-line") { value }
else