Skip to content

Commit

Permalink
Introduce SingularAssociation and CollectionAssociation to tidy up th…
Browse files Browse the repository at this point in the history
…e views
  • Loading branch information
mshibuya committed Jul 28, 2024
1 parent d20b024 commit 876be11
Show file tree
Hide file tree
Showing 18 changed files with 262 additions and 177 deletions.
4 changes: 4 additions & 0 deletions app/helpers/rails_admin/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ def authorized?(action_name, abstract_model = nil, object = nil)
action(action_name, abstract_model, object).try(:authorized?)
end

def current_action
params[:action].in?(%w[create new]) ? 'create' : 'update'
end

def current_action?(action, abstract_model = @abstract_model, object = @object)
@action.custom_key == action.custom_key &&
abstract_model.try(:to_param) == @abstract_model.try(:to_param) &&
Expand Down
41 changes: 5 additions & 36 deletions app/views/rails_admin/main/_form_filtering_multiselect.html.erb
Original file line number Diff line number Diff line change
@@ -1,45 +1,14 @@
<%
config = field.associated_model_config
source_abstract_model = RailsAdmin.config(form.object.class).abstract_model

selected = form.object.send(field.name)
selected_ids = selected.map{|s| s.send(field.associated_primary_key).to_s}

current_action = params[:action].in?(['create', 'new']) ? 'create' : 'update'

xhr = !field.associated_collection_cache_all

collection = if xhr
selected.map { |o| [o.send(field.associated_object_label_method), o.send(field.associated_primary_key)] }
else
i = 0
controller.list_entries(config, :index, field.associated_collection_scope, false).map { |o| [o.send(field.associated_object_label_method), o.send(field.associated_primary_key).to_s] }.sort_by {|a| [selected_ids.index(a[1]) || selected_ids.size, i+=1] }
end

js_data = {
xhr: xhr,
:'edit-url' => (field.inline_edit && authorized?(:edit, config.abstract_model) ? edit_path(model_name: config.abstract_model.to_param, id: '__ID__') : ''),
remote_source: index_path(config.abstract_model, source_object_id: form.object.id, source_abstract_model: source_abstract_model.to_param, associated_collection: field.name, current_action: current_action, compact: true),
scopeBy: field.dynamic_scope_relationships,
sortable: !!field.orderable,
removable: !!field.removable,
cacheAll: !!field.associated_collection_cache_all,
regional: {
add: t('admin.misc.add_new'),
chooseAll: t('admin.misc.chose_all'),
clearAll: t('admin.misc.clear_all'),
down: t('admin.misc.down'),
remove: t('admin.misc.remove'),
search: t('admin.misc.search'),
up: t('admin.misc.up')
}
}
%>

<div class="row">
<div class="col-auto">
<input name="<%= form.dom_name(field) %>" type="hidden" />
<% selected_ids = (hdv = field.form_default_value).nil? ? selected_ids : hdv %>
<%= form.select field.method_name, collection, { selected: selected_ids, object: form.object }, field.html_attributes.reverse_merge({data: { filteringmultiselect: true, options: js_data.to_json }, multiple: true}) %>
<%=
form.select field.method_name, field.collection, { selected: field.form_value, object: form.object },
field.html_attributes.reverse_merge({data: { filteringmultiselect: true, options: field.widget_options.to_json }, multiple: true})
%>
</div>
<% if authorized?(:new, config.abstract_model) && field.inline_add %>
<div class="col-sm-4 modal-actions">
Expand Down
25 changes: 6 additions & 19 deletions app/views/rails_admin/main/_form_filtering_select.html.erb
Original file line number Diff line number Diff line change
@@ -1,34 +1,21 @@
<%
config = field.associated_model_config
source_abstract_model = RailsAdmin.config(form.object.class).abstract_model

current_action = params[:action].in?(['create', 'new']) ? 'create' : 'update'

edit_url = authorized?(:edit, config.abstract_model) ? edit_path(model_name: config.abstract_model.to_param, modal: true, id: '__ID__') : ''

xhr = !field.associated_collection_cache_all

collection = xhr ? [[field.formatted_value, field.selected_id]] : controller.list_entries(config, :index, field.associated_collection_scope, false).map { |o| [o.send(field.associated_object_label_method), o.send(field.associated_primary_key).to_s] }

js_data = {
xhr: xhr,
remote_source: index_path(config.abstract_model.to_param, source_object_id: form.object.id, source_abstract_model: source_abstract_model.to_param, associated_collection: field.name, current_action: current_action, compact: true),
scopeBy: field.dynamic_scope_relationships
}
%>

<div class="row">
<div class="col-sm-4">
<% selected_id = (hdv = field.form_default_value).nil? ? field.selected_id : hdv %>
<%= form.select field.method_name, collection, { selected: selected_id, include_blank: true }, field.html_attributes.reverse_merge({ data: { filteringselect: true, options: js_data.to_json }, placeholder: t('admin.misc.search') }) %>
<%=
form.select field.method_name, field.collection, { selected: field.form_value, include_blank: true },
field.html_attributes.reverse_merge({ data: { filteringselect: true, options: field.widget_options.to_json }, placeholder: t('admin.misc.search') })
%>
</div>
<div class="col-sm-8 mt-2 mt-md-0 modal-actions">
<% if authorized?(:new, config.abstract_model) && field.inline_add %>
<% path_hash = { model_name: config.abstract_model.to_param, modal: true }.merge!(field.associated_prepopulate_params) %>
<%= link_to "<i class=\"fas fa-plus\"></i> ".html_safe + wording_for(:link, :new, config.abstract_model), '#', data: { link: new_path(path_hash) }, class: "btn btn-info create" %>
<% end %>
<% if edit_url.present? && field.inline_edit %>
<%= link_to "<i class=\"fas fa-pencil-alt\"></i> ".html_safe + wording_for(:link, :edit, config.abstract_model), '#', data: { link: edit_url }, class: "btn btn-info update ms-2#{' disabled' if field.value.nil?}" %>
<% if authorized?(:edit, config.abstract_model) && field.inline_edit %>
<%= link_to "<i class=\"fas fa-pencil-alt\"></i> ".html_safe + wording_for(:link, :edit, config.abstract_model), '#', data: { link: edit_path(model_name: config.abstract_model.to_param, modal: true, id: '__ID__') }, class: "btn btn-info update ms-2#{' disabled' if field.value.nil?}" %>
<% end %>
</div>
</div>
33 changes: 12 additions & 21 deletions app/views/rails_admin/main/_form_polymorphic_association.html.erb
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
<%
type_collection = field.polymorphic_type_collection
type_column = field.association.foreign_type.to_s
selected_type = field.bindings[:object].send(type_column)
selected = field.bindings[:object].send(field.association.name)
collection = selected ? [[field.formatted_value, selected.id]] : [[]]
column_type_dom_id = form.dom_id(field).sub(field.method_name.to_s, type_column)
current_action = params[:action].in?(['create', 'new']) ? 'create' : 'update'

default_options = { float_left: false }

js_data = type_collection.inject({}) do |options, model|
source_abstract_model = RailsAdmin.config(form.object.class).abstract_model
options.merge(model.second.downcase.gsub('::', '-') => {
xhr: true,
remote_source: index_path(model.second.underscore, source_object_id: form.object.id, source_abstract_model: source_abstract_model.to_param, current_action: current_action, compact: true),
float_left: false
})
end
column_type_dom_id = form.dom_id(field).sub(field.method_name.to_s, field.type_column)
%>

<div class="row">
<div class="col-sm-3">
<% js_data.each do |model, value| %>
<% field.widget_options_for_types.each do |model, value| %>
<div data-options="<%= value.to_json %>" id="<%= model %>-js-options"></div>
<% end %>
<%= form.select type_column, type_collection, {include_blank: true, selected: selected_type}, class: "form-select", id: column_type_dom_id, data: { polymorphic: true, urls: field.polymorphic_type_urls.to_json }, style: "float: left; margin-right: 10px;" %>
<%=
form.select field.type_column, field.type_collection, {include_blank: true, selected: field.selected_type},
class: "form-select", id: column_type_dom_id, data: { polymorphic: true, urls: field.type_urls.to_json },
style: "float: left; margin-right: 10px;"
%>
</div>
<div class="col-sm-4">
<%= form.select field.method_name, collection, {include_blank: true, selected: selected.try(:id)}, class: "form-control", data: { filteringselect: true, options: js_data[selected_type.try(:downcase)] || default_options }, placeholder: 'Search' %>
<%=
form.select field.method_name, field.collection, {include_blank: true, selected: field.selected_id},
class: "form-control", data: { filteringselect: true, options: field.widget_options },
placeholder: 'Search'
%>
</div>
</div>
8 changes: 5 additions & 3 deletions lib/rails_admin/config/actions/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ class Index < RailsAdmin::Config::Actions::Base
format.json do
output =
if params[:compact]
primary_key_method = @association ? @association.associated_primary_key : @model_config.abstract_model.primary_key
label_method = @model_config.object_label_method
@objects.collect { |o| {id: o.send(primary_key_method).to_s, label: o.send(label_method).to_s} }
if @association
@association.collection(@objects).collect { |(label, id)| {id: id, label: label} }
else
@objects.collect { |object| {id: object.id.to_s, label: object.send(@model_config.object_label_method).to_s} }
end
else
@objects.to_json(@schema)
end
Expand Down
8 changes: 7 additions & 1 deletion lib/rails_admin/config/fields/association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def association
end

def method_name
association.key_accessor
nested_form ? :"#{name}_attributes" : association.key_accessor
end

register_instance_option :pretty_value do
Expand Down Expand Up @@ -134,6 +134,12 @@ def value
bindings[:object].send(association.name)
end

# Returns collection of all selectable records
def collection(scope = nil)
(scope || bindings[:controller].list_entries(associated_model_config, :index, associated_collection_scope, false)).
map { |o| [o.send(associated_object_label_method), o.send(associated_primary_key).to_s] }
end

# has many?
def multiple?
true
Expand Down
73 changes: 73 additions & 0 deletions lib/rails_admin/config/fields/collection_association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

require 'rails_admin/config/fields/association'

module RailsAdmin
module Config
module Fields
class CollectionAssociation < Association
# orderable associated objects
register_instance_option :orderable do
false
end

register_instance_option :partial do
nested_form ? :form_nested_many : :form_filtering_multiselect
end

def collection(scope = nil)
if scope
super
elsif associated_collection_cache_all
selected = selected_ids
i = 0
super.sort_by { |a| [selected.index(a[1]) || selected.size, i += 1] }
else
value.map { |o| [o.send(associated_object_label_method), o.send(associated_primary_key)] }
end
end

def associated_prepopulate_params
{associated_model_config.abstract_model.param_key => {association.foreign_key => bindings[:object].try(:id)}}
end

def multiple?
true
end

def selected_ids
value.map { |s| s.send(associated_primary_key).to_s }
end

def form_default_value
(default_value if bindings[:object].new_record? && value.empty?)
end

def form_value
form_default_value.nil? ? selected_ids : form_default_value
end

def widget_options
{
xhr: !associated_collection_cache_all,
'edit-url': (inline_edit && bindings[:view].authorized?(:edit, associated_model_config.abstract_model) ? bindings[:view].edit_path(model_name: associated_model_config.abstract_model.to_param, id: '__ID__') : ''),
remote_source: bindings[:view].index_path(associated_model_config.abstract_model, source_object_id: bindings[:object].id, source_abstract_model: abstract_model.to_param, associated_collection: name, current_action: bindings[:view].current_action, compact: true),
scopeBy: dynamic_scope_relationships,
sortable: !!orderable,
removable: !!removable,
cacheAll: !!associated_collection_cache_all,
regional: {
add: ::I18n.t('admin.misc.add_new'),
chooseAll: ::I18n.t('admin.misc.chose_all'),
clearAll: ::I18n.t('admin.misc.clear_all'),
down: ::I18n.t('admin.misc.down'),
remove: ::I18n.t('admin.misc.remove'),
search: ::I18n.t('admin.misc.search'),
up: ::I18n.t('admin.misc.up'),
},
}
end
end
end
end
end
51 changes: 51 additions & 0 deletions lib/rails_admin/config/fields/singular_association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require 'rails_admin/config/fields/association'

module RailsAdmin
module Config
module Fields
class SingularAssociation < Association
register_instance_option :filter_operators do
%w[_discard like not_like is starts_with ends_with] + (required? ? [] : %w[_separator _present _blank])
end

register_instance_option :formatted_value do
(o = value) && o.send(associated_model_config.object_label_method)
end

register_instance_option :partial do
nested_form ? :form_nested_one : :form_filtering_select
end

def collection(scope = nil)
if associated_collection_cache_all || scope
super
else
[[formatted_value, selected_id]]
end
end

def multiple?
false
end

def selected_id
raise NoMethodError # abstract
end

def form_value
form_default_value.nil? ? selected_id : form_default_value
end

def widget_options
{
xhr: !associated_collection_cache_all,
remote_source: bindings[:view].index_path(associated_model_config.abstract_model, source_object_id: bindings[:object].id, source_abstract_model: abstract_model.to_param, associated_collection: name, current_action: bindings[:view].current_action, compact: true),
scopeBy: dynamic_scope_relationships,
}
end
end
end
end
end
24 changes: 2 additions & 22 deletions lib/rails_admin/config/fields/types/belongs_to_association.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
# frozen_string_literal: true

require 'rails_admin/config/fields/association'
require 'rails_admin/config/fields/singular_association'

module RailsAdmin
module Config
module Fields
module Types
class BelongsToAssociation < RailsAdmin::Config::Fields::Association
class BelongsToAssociation < RailsAdmin::Config::Fields::SingularAssociation
RailsAdmin::Config::Fields::Types.register(self)

register_instance_option :filter_operators do
%w[_discard like not_like is starts_with ends_with] + (required? ? [] : %w[_separator _present _blank])
end

register_instance_option :formatted_value do
(o = value) && o.send(associated_model_config.object_label_method)
end

register_instance_option :sortable do
@sortable ||= abstract_model.adapter_supports_joins? && associated_model_config.abstract_model.properties.collect(&:name).include?(associated_model_config.object_label_method) ? associated_model_config.object_label_method : {abstract_model.table_name => method_name}
end
Expand All @@ -25,25 +17,13 @@ class BelongsToAssociation < RailsAdmin::Config::Fields::Association
@searchable ||= associated_model_config.abstract_model.properties.collect(&:name).include?(associated_model_config.object_label_method) ? [associated_model_config.object_label_method, {abstract_model.model => method_name}] : {abstract_model.model => method_name}
end

register_instance_option :partial do
nested_form ? :form_nested_one : :form_filtering_select
end

register_instance_option :eager_load do
true
end

def selected_id
bindings[:object].safe_send(association.key_accessor)
end

def method_name
nested_form ? :"#{name}_attributes" : super
end

def multiple?
false
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# frozen_string_literal: true

require 'rails_admin/config/fields/types/has_many_association'
require 'rails_admin/config/fields/collection_association'

module RailsAdmin
module Config
module Fields
module Types
class HasAndBelongsToManyAssociation < RailsAdmin::Config::Fields::Types::HasManyAssociation
class HasAndBelongsToManyAssociation < RailsAdmin::Config::Fields::CollectionAssociation
# Register field type for the type loader
RailsAdmin::Config::Fields::Types.register(self)
end
Expand Down
Loading

0 comments on commit 876be11

Please sign in to comment.