Skip to content

Commit

Permalink
experimental nested fields enhanced attributes mappings handling feature
Browse files Browse the repository at this point in the history
  • Loading branch information
vala committed Mar 24, 2017
1 parent fffe278 commit e843d42
Show file tree
Hide file tree
Showing 33 changed files with 298 additions and 86 deletions.
8 changes: 4 additions & 4 deletions app/assets/javascripts/para/inputs/multi-select-input.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ class Para.MultiSelectInput extends Vertebra.View
else
@showEmptyListHint(@noSelectedItemsTemplate, @$selectedItems)

showEmptyListHint: (template, appendTo) ->
$(template).appendTo(appendTo)
showEmptyListHint: (template, $container) ->
$(template).appendTo($container)

onItemRemoved: (selectedItem) =>
itemIndex = index for item, index in @selectedItems when item.id is selectedItem.id
Expand Down Expand Up @@ -155,5 +155,5 @@ class Para.MultiSelectSelectedItem extends Vertebra.View
@trigger('remove', this)


$.simpleForm.onDomReady ->
$('[data-multi-select-input]').each (i, el) -> new Para.MultiSelectInput(el: el)
$.simpleForm.onDomReady ($document) ->
$document.find('[data-multi-select-input]').each (i, el) -> new Para.MultiSelectInput(el: el)
4 changes: 2 additions & 2 deletions app/assets/javascripts/para/inputs/nested_many.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ class Para.NestedManyField
$target.find('input, textarea, select').eq('0').focus()


$.simpleForm.onDomReady ->
$('.nested-many-field').each (i, el) -> new Para.NestedManyField($(el))
$.simpleForm.onDomReady ($document) ->
$document.find('.nested-many-field').each (i, el) -> new Para.NestedManyField($(el))
7 changes: 7 additions & 0 deletions app/controllers/para/admin/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ def current_ability

private

# Override cancan controlelr resource class to avoid attributes
# assignation issues with resources
#
def self.cancan_resource_class
Para::ControllerResource
end

def authorize_admin_access
authorize! :access, :admin
end
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/para/admin/resources_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Para
module Admin
class ResourcesController < Para::Admin::BaseController
include Para::ModelHelper
include Para::AttributesMappingsHelper

class_attribute :resource_name, :resource_class
load_and_authorize_component
Expand Down Expand Up @@ -117,7 +118,7 @@ def resource_params
end

def parse_resource_params(hash)
model_field_mappings(resource_model).fields.each do |field|
model_field_mappings(resource_model, mappings: attributes_mappings_for(hash)).fields.each do |field|
field.parse_input(hash, resource)
end

Expand Down
2 changes: 2 additions & 0 deletions app/helpers/para/form_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def para_form_for(resource, options = {}, &block)
if @component.subclassable? && resource.new_record?
content << form.hidden_field(:type, value: resource.type)
end

content << form.attributes_mappings_field
end
end
end
Expand Down
14 changes: 12 additions & 2 deletions app/helpers/para/model_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@ def attribute_field_mappings_for(component, relation)
model_field_mappings(model).fields
end

def model_field_mappings(model, attributes = nil)
# Second argument can be the whitelist_attributes array or keyword
# arguments. This is to ensure backwards compatibility with old plugins.
#
def model_field_mappings(model, options = {})
if Array == options
whitelist_attributes = options
else
whitelist_attributes = options.fetch(:whitelist_attributes, nil)
mappings = options.fetch(:mappings, {})
end

store_key = ['model', 'mappings', model.name.underscore].join(':')

Para.store[store_key] ||= Para::AttributeFieldMappings.new(
model, whitelist_attributes: attributes
model, whitelist_attributes: whitelist_attributes, mappings: mappings
)
end

Expand Down
5 changes: 5 additions & 0 deletions app/models/para/page.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Para::Page
def self.table_name_prefix
'para_page_'
end
end
41 changes: 41 additions & 0 deletions app/models/para/page/section.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,47 @@ class Section < ActiveRecord::Base
def css_class
@css_class ||= self.class.name.demodulize.underscore.gsub(/_/, '-')
end

class << self
# This method is a shortcut to create a has_many through relation
def section_resources(*args, &block)
_ensure_section_resources_relation
_create_section_resource_relation_for(*args, &block)
end

private

def _ensure_section_resources_relation
return if @section_resources_already_initialized

has_many :section_resources, foreign_key: 'section_id',
class_name: '::Para::Page::SectionResource',
dependent: :destroy
accepts_nested_attributes_for :section_resources, allow_destroy: true

@section_resources_already_initialized = true
end

def _create_section_resource_relation_for(*args, &block)
options = args.extract_options!

# Allow using :class_name option instead of :source_type to feel more
# like a direct has_many relation macro, and try to deduce the target
# class name from the relation name.
target_class_name = options.fetch(:class_name) do
args.first.to_s.singularize.camelize
end

# Fill the through relation options but let the user override them
options.reverse_merge!(
through: :section_resources,
source: :resource,
source_type: target_class_name
)

has_many(*args, options, &block)
end
end
end
end
end
12 changes: 12 additions & 0 deletions app/models/para/page/section_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Para
module Page
class SectionResource < ActiveRecord::Base
self.table_name = 'para_page_section_resources'

acts_as_orderable

belongs_to :section
belongs_to :resource, polymorphic: true
end
end
end
2 changes: 1 addition & 1 deletion app/views/para/inputs/nested_many/_add.html.haml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
= link_to_add_association form, attribute_name, partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: 'btn btn-primary btn-shadow', data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: model, nested_locals: nested_locals, inset: inset } } do
= link_to_add_association form, attribute_name, partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: 'btn btn-primary btn-shadow add-button nested-many-inset-add-button', data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: model, nested_locals: nested_locals, inset: inset } } do
= t('para.form.nested.add')
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.add-button.dropdown
.add-button.nested-many-inset-add-button.dropdown
%button.btn.btn-primary.btn-shadow.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } }
= t('para.form.nested.add')
%i.fa.fa-angle-down
Expand Down
2 changes: 1 addition & 1 deletion app/views/para/inputs/nested_many/_container.html.haml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- nested_locals ||= {}

.panel.panel-default.form-fields
.panel.panel-default.form-fields{ class: ('nested-many-inset-panel' if inset) }
.panel-heading
= form.reorder_anchor

Expand Down
15 changes: 15 additions & 0 deletions db/migrate/20170324125547_create_para_page_section_resources.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class CreateParaPageSectionResources < ActiveRecord::Migration
def change
create_table :para_page_section_resources do |t|
t.references :section
t.references :resource, polymorphic: true, index: false
t.integer :position, default: 0
t.jsonb :data

t.timestamps
end

add_index :para_page_section_resources, [:resource_type, :resource_id], name: :index_para_section_resources_on_resource_type_and_id
add_foreign_key :para_page_section_resources, :para_page_sections, column: 'section_id'
end
end
15 changes: 13 additions & 2 deletions lib/generators/para/page/section/section_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,26 @@ class SectionGenerator < Rails::Generators::NamedBase

source_root File.expand_path("../templates", __FILE__)

argument :attributes, type: :array
argument :attributes, type: :array, required: false, default: []

def generate_form
def generate_model
template(
"section.rb.erb",
"app/models/page_section/#{ singular_namespaced_path }.rb"
)
end

def generate_fields
generate 'para:nested_fields', "page_section/#{ singular_namespaced_path }"
end

def generate_template
template(
"section.html.haml.erb",
"app/views/page_section/#{ plural_namespaced_path }/_#{ singular_namespaced_path }.html.haml"
)
end

private

def attributes_separated_with_commas
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%- attributes.each do |attribute| -%>
= <%= singular_namespaced_path %>.<%= attribute.name %>
<%- end -%>
2 changes: 2 additions & 0 deletions lib/para.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
require 'para/plugins'
require 'para/config'
require 'para/model_field_parsers'
require 'para/attributes_mappings_helper'
require 'para/attribute_field'
require 'para/attribute_field_mappings'
require 'para/inputs'
Expand All @@ -62,6 +63,7 @@ module Para
eager_autoload do
autoload :Config
autoload :Component
autoload :ControllerResource
autoload :Generators
autoload :SimpleFormConfig
autoload :Routing
Expand Down
13 changes: 9 additions & 4 deletions lib/para/attribute_field/has_many.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,22 @@ def assign_multi_select_field(params, resource)
end
end

def assign_ordered_through_reflection_ids(reflection, resource, ids)
association = resource.association(reflection.name)
def assign_ordered_through_reflection_ids(through_reflection, resource, ids)
association = resource.association(through_reflection.name)
join_resources = association.load_target

return association.replace([]) if ids.empty?

new_resources = ids.each_with_index.map do |id, position|
join_resource = join_resources.find { |res| res.send(through_relation_source_foreign_key) == id }
join_resource = join_resources.find do |res|
res.send(through_relation_source_foreign_key) == id &&
(!polymorphic_through_reflection? || res.send(reflection.foreign_type) == reflection.klass.name)
end

unless join_resource
join_resource = association.build(through_relation_source_foreign_key => id)
attributes = { through_relation_source_foreign_key => id }
attributes[reflection.foreign_type] = reflection.klass.name if polymorphic_through_reflection?
join_resource = association.build(attributes)
end

join_resource.position = position
Expand Down
30 changes: 26 additions & 4 deletions lib/para/attribute_field/nested_many.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
module Para
module AttributeField
class NestedManyField < AttributeField::HasManyField
include Para::AttributesMappingsHelper

register :nested_many, self

def parse_input(params, resource)
if (nested_attributes = params[nested_attributes_key])
nested_attributes.each do |index, attributes|
nested_model_mappings.fields.each do |field|
field.parse_input(attributes, resource)
mappings = nested_model_mappings(attributes)

nested_resource = fetch_or_build_nested_resource_for(resource, index, attributes)

mappings.fields.each do |field|
field.parse_input(attributes, nested_resource)
end

params[nested_attributes_key][index] = attributes
Expand All @@ -17,13 +23,29 @@ def parse_input(params, resource)
end
end

def nested_model_mappings
@nested_model_mappings ||= AttributeFieldMappings.new(reflection.klass)
def nested_model_mappings(nested_attributes)
model = nested_attributes[:type].try(:constantize) || reflection.klass
mappings = attributes_mappings_for(nested_attributes)

@nested_model_mappings ||= AttributeFieldMappings.new(model, mappings: mappings)
end

def nested_attributes_key
@nested_attributes_key ||= :"#{ name }_attributes"
end

def fetch_or_build_nested_resource_for(parent, index, attributes)
nested_resources = parent.association(name).load_target

if (id = attributes['id'].presence)
nested_resources.find { |res| res.id == id.to_i }
else
parent.association(name).build(attributes.slice('type')).tap do |resource|
attributes['id'] = "__#{ index }"
resource.define_singleton_method(:id) { attributes['id'] }
end
end
end
end
end
end
13 changes: 10 additions & 3 deletions lib/para/attribute_field/nested_one.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
module Para
module AttributeField
class NestedOneField < AttributeField::BelongsToField
include Para::AttributesMappingsHelper

register :nested_one, self

def parse_input(params, resource)
if (nested_attributes = params[nested_attributes_key])
nested_model_mappings.fields.each do |field|
mappings = nested_model_mappings(nested_attributes)

mappings.fields.each do |field|
field.parse_input(nested_attributes)
end

Expand All @@ -15,8 +19,11 @@ def parse_input(params, resource)
end
end

def nested_model_mappings
@nested_model_mappings ||= AttributeFieldMappings.new(reflection.klass)
def nested_model_mappings(nested_attributes)
model = nested_attributes[:type].try(:constantize) || reflection.klass
mappings = attributes_mappings_for(nested_attributes)

@nested_model_mappings ||= AttributeFieldMappings.new(model, mappings: mappings)
end

def nested_attributes_key
Expand Down
6 changes: 5 additions & 1 deletion lib/para/attribute_field/relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ def through_relation_source_foreign_key
@through_relation_source_foreign_key ||= reflection.source_reflection.foreign_key
end

def polymorphic_through_reflection?
!!(through_relation && reflection.source_reflection.options[:polymorphic])
end

private

def resource_name(resource)
Para.config.resource_name_methods.each do |method|
return resource.send(method) if resource.respond_to?(method)
Expand Down
7 changes: 4 additions & 3 deletions lib/para/attribute_field_mappings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ module Para
class AttributeFieldMappings
UNEDITABLE_ATTRIBUTES = %w(id component_id created_at updated_at type)

attr_reader :model, :fields_hash, :whitelist_attributes
attr_reader :model, :fields_hash, :whitelist_attributes, :mappings

def initialize(model, whitelist_attributes: nil)
def initialize(model, whitelist_attributes: nil, mappings: nil)
@model = model
@whitelist_attributes = whitelist_attributes
@mappings = mappings || {}

process_fields!
end
Expand Down Expand Up @@ -52,7 +53,7 @@ def process_fields!
end
end.with_indifferent_access

Para::ModelFieldParsers.parse!(model, fields_hash)
Para::ModelFieldParsers.parse!(model, fields_hash, mappings)
end

def build_field_for(attribute_name, type)
Expand Down
Loading

0 comments on commit e843d42

Please sign in to comment.