para 0.8.15 → 0.9.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eaabbc24f0c6d28e69cf3ebc0d837ffb52dc6abd1286a2ab6c23b0c8767e7a9d
4
- data.tar.gz: 5f7e0609618d87d13d27b8132c2a6a7aa6a46e365c4dc9c7ac1a687f843ceca7
3
+ metadata.gz: 6deb45940599a543b4257463561e645256df1db47b9cef62f1d287b97e841abd
4
+ data.tar.gz: a37ab4aca7ab0c23baf41a65083c84bdbdb6d6fe12437621b08f9a4f49baf22d
5
5
  SHA512:
6
- metadata.gz: 2d16ba804ed07daac026531e17c0f669a2cada924db5a22d87bff4b44018c3720dcf2177600bd6133dcba9c6da97a2a0a0253e3fbe6111ae24036f12596010be
7
- data.tar.gz: 31cf72e15c979a0b24f1aa18cb736b9d1c56b4af88057fe730b5375dd3b8e7fe4bbd034f5f38232ffb900cc6b591480dacfdff4742fdd36f23d3c56a14a116c6
6
+ metadata.gz: 229be291a706963be79ef902fb99fdedad27092a62f8c855d912b57c21d10f94ed8e9ffd254a9bb1f29ae465ab6ec1cbe3753af53096778ce6131bdca83ef3b3
7
+ data.tar.gz: 10e5675bb29448fd949112632bda77b2dc897d7ca3b2d2dd8dfbac4ef47a19d8ddf629aa6ef40cea98dbfed05bfc716afe8fe06b1c57b950e2af8b541f859672
@@ -5,7 +5,7 @@ class Para.NestedManyField
5
5
  @initializeOrderable()
6
6
  @initializeCocoon()
7
7
 
8
- @$field.on 'shown.bs.collapse', $.proxy(@collapseShown, this)
8
+ @$field.on 'shown.bs.collapse', @stoppingPropagation(@collapseShown)
9
9
 
10
10
  initializeOrderable: ->
11
11
  @orderable = @$field.hasClass('orderable')
@@ -21,11 +21,17 @@ class Para.NestedManyField
21
21
  $(el).find('.resource-position-field').val(i)
22
22
 
23
23
  initializeCocoon: ->
24
- @$fieldsList.on 'cocoon:after-insert', $.proxy(@afterInsertField, this)
25
- @$fieldsList.on 'cocoon:before-remove', $.proxy(@beforeRemoveField, this)
26
- @$fieldsList.on 'cocoon:after-remove', $.proxy(@afterRemoveField, this)
24
+ @$fieldsList.on 'cocoon:after-insert', @stoppingPropagation(@afterInsertField)
25
+ @$fieldsList.on 'cocoon:before-remove', @stoppingPropagation(@beforeRemoveField)
26
+ @$fieldsList.on 'cocoon:after-remove', @stoppingPropagation(@afterRemoveField)
27
27
 
28
- afterInsertField: (e, $element) ->
28
+ stoppingPropagation: (callback) =>
29
+ (e, args...) =>
30
+ e.stopPropagation()
31
+ callback(e, args...)
32
+
33
+
34
+ afterInsertField: (e, $element) =>
29
35
  if ($collapsible = $element.find('[data-open-on-insert="true"]')).length
30
36
  @openInsertedField($collapsible)
31
37
 
@@ -36,21 +42,21 @@ class Para.NestedManyField
36
42
 
37
43
  $element.simpleForm()
38
44
 
39
- beforeRemoveField: (e, $element) ->
45
+ beforeRemoveField: (e, $element) =>
40
46
  $nextEl = $element.next()
41
47
  # Remove attributes mappings field for new records since it will try to
42
48
  # create an empty nested resource otherwise
43
49
  $nextEl.remove() if $nextEl.is('[data-attributes-mappings]') and not $element.is('[data-persisted]')
44
50
 
45
51
  # When a sub field is removed, update every sub field position
46
- afterRemoveField: ->
52
+ afterRemoveField: =>
47
53
  @handleOrderingUpdated();
48
54
 
49
55
  openInsertedField: ($field) ->
50
56
  $target = $($field.attr('href'))
51
57
  $target.collapse('show')
52
58
 
53
- collapseShown: (e) ->
59
+ collapseShown: (e) =>
54
60
  $target = $(e.target)
55
61
 
56
62
  if $target.is("[data-rendered]") or $target.data("rendered")
@@ -8,7 +8,7 @@ module Para
8
8
  def create
9
9
  job = @exporter.perform_later(
10
10
  model_name: @component.try(:model).try(:name),
11
- search: params[:q],
11
+ search: params[:q]&.permit!,
12
12
  params: params.permit(@exporter.params_whitelist).to_h
13
13
  )
14
14
 
@@ -5,6 +5,7 @@ module Para
5
5
  @model = params[:model_name].constantize
6
6
  @object = params[:id] ? @model.find(params[:id]) : @model.new
7
7
  @object_name = params[:object_name]
8
+ @builder_options = params[:builder_options]&.permit! || {}
8
9
 
9
10
  render layout: false
10
11
  end
@@ -1,6 +1,15 @@
1
1
  - nested_form = nil
2
2
 
3
- - para_form_for(@object, url: "") do |form|
3
+ -# Initialize an unredered form builder that will only be used to create the nested fields
4
+ -# by using the yielded block form builder argument.
5
+ -#
6
+ -# The form's object name is set through the provided :object_name param to ensure that
7
+ -# the form inputs are named correctly in the loading form.
8
+ -#
9
+ -# Builder options allow to customize the options passed to the form builder to customize
10
+ -# the rendered partials.
11
+ -#
12
+ - para_form_for(@object, @builder_options.merge(url: "")) do |form|
4
13
  - form.object_name = @object_name
5
14
  - nested_form = capture do
6
15
  = render(partial: find_partial_for(@model, :remote_nested_form), locals: { form: form, model: @model, object: @object, object_name: @object_name })
@@ -1,10 +1,13 @@
1
1
  .top-nav-tabs-affix-placeholder
2
2
  %ul.top-nav-tabs-affix.nav.nav-tabs{ role: "tablist", "data-top-level-affix": true }
3
- %li{ class: ("active" if active_component == parent_component) }
4
- = link_to parent_component.path do
5
- = parent_component.name
3
+ - if can?(:manage, parent_component) && show_component?(parent_component)
4
+ %li{ class: ("active" if active_component == parent_component) }
5
+ = link_to parent_component.path do
6
+ = parent_component.name
6
7
 
7
8
  - parent_component.child_components.each do |child_component|
9
+ - next unless can?(:manage, child_component) && show_component?(child_component)
10
+
8
11
  %li{ class: ("active" if active_component == child_component) }
9
12
  = link_to child_component.path do
10
13
  = child_component.name
@@ -1 +1 @@
1
- = render partial: find_partial_for(model, :fields), locals: { form: form }
1
+ = render partial: find_partial_for(model, form.nested_fields_partial_name), locals: { form: form }
@@ -2,6 +2,7 @@
2
2
  .fields-list{ id: dom_identifier }
3
3
  = form.simple_fields_for attribute_name, resources, nested_attribute_name: attribute_name, orderable: orderable, track_attribute_mappings: render_partial do |nested_form|
4
4
  = render partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), locals: { form: nested_form, model: nested_form.object.class, subclass: subclass, nested_locals: nested_locals, inset: inset, uncollapsed: uncollapsed, render_partial: render_partial, remote_partial_params: remote_partial_params }
5
+
5
6
  -# Add button
6
7
  - if add_button
7
8
  - if subclass
@@ -6,7 +6,7 @@
6
6
  = render partial: find_partial_for(model, 'nested_one/container', partial_dir: 'inputs'), locals: { nested_form: nested_form, form: form, model: nested_form.object.class, subclass: subclass, nested_locals: nested_locals }
7
7
 
8
8
  - else
9
- = render partial: find_partial_for(model, :fields), locals: { form: nested_form, parent: form.object }.merge(nested_locals)
9
+ = render partial: find_partial_for(model, form.nested_fields_partial_name), locals: { form: nested_form, parent: form.object }.merge(nested_locals)
10
10
 
11
11
  - if defined?(subclass) && subclass
12
- = render partial: 'para/inputs/nested_one/add_with_subclasses', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, subclasses: subclasses, add_button_label: add_button_label, add_button_class: add_button_class, subclass: subclass }
12
+ = render partial: 'para/inputs/nested_one/add_with_subclasses', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, subclasses: subclasses, add_button_label: add_button_label, add_button_class: add_button_class, subclass: subclass }
@@ -14,7 +14,7 @@
14
14
  .panel-collapse.form-inputs.collapse{ id: form.nested_resource_dom_id, class: ('in' if uncollapsed && form.object.persisted?), data: { rendered: render_partial, render_path: @component.path(remote_partial_params), id: form.object.id, :"object-name" => form.object_name, :"model-name" => model.name } }
15
15
  .panel-body{ data: { :"nested-form-container" => true } }
16
16
  - if render_partial
17
- = render partial: find_partial_for(model, :fields), locals: { form: form }.merge(nested_locals)
17
+ = render partial: find_partial_for(model, form.nested_fields_partial_name), locals: { form: form }.merge(nested_locals)
18
18
  - else
19
19
  = fa_icon "spinner spin"
20
20
 
@@ -6,4 +6,4 @@
6
6
  %i.fa.fa-angle-up
7
7
 
8
8
  .panel-body.panel-collapse.form-inputs.collapse{ id: nested_form.nested_resource_dom_id }
9
- = render partial: find_partial_for(model, :fields), locals: { form: nested_form, parent: nested_form.object }.merge(nested_locals)
9
+ = render partial: find_partial_for(model, form.nested_fields_partial_name), locals: { form: nested_form, parent: nested_form.object }.merge(nested_locals)
@@ -1,36 +1,90 @@
1
1
  module Para
2
2
  module Cloneable
3
3
  class AttachmentsCloner
4
- attr_reader :original, :clone
4
+ attr_reader :original, :clone, :dictionary
5
5
 
6
- def initialize(original, clone)
6
+ # Handle both one and many attachment relations
7
+ ATTACHMENTS_RELATION_REGEX = /_attachments?\z/
8
+
9
+ def initialize(original, clone, dictionary)
7
10
  @original = original
8
11
  @clone = clone
12
+ @dictionary = dictionary
9
13
  end
10
-
14
+
11
15
  def clone!
12
16
  return unless defined?(ActiveStorage)
13
-
14
- attachment_reflections = original.class.reflections.select { |k, v|
15
- k.to_s.match(/_attachment\z/) &&
16
- v.options[:class_name] == "ActiveStorage::Attachment"
17
- }
17
+
18
+ attachment_reflections = original.class.reflections.select do |name, reflection|
19
+ name.to_s.match(ATTACHMENTS_RELATION_REGEX) &&
20
+ reflection.options[:class_name] == "ActiveStorage::Attachment"
21
+ end
18
22
 
19
23
  attachment_reflections.each do |name, reflection|
20
- original_attachment = original.send(name)
21
- next unless original_attachment
22
-
23
- association_name = name.gsub(/_attachment\z/, "")
24
-
25
- Para::ActiveStorageDownloader.new(original_attachment).download_blob_to_tempfile do |tempfile|
26
- clone.send(association_name).attach({
27
- io: tempfile,
28
- filename: original_attachment.blob.filename,
29
- content_type: original_attachment.blob.content_type
30
- })
24
+ association_target = original.send(name)
25
+ next unless association_target
26
+
27
+ if reflection.collection?
28
+ association_target.each do |attachment|
29
+ clone_attachment(name, attachment)
30
+ end
31
+ else
32
+ clone_attachment(name, association_target)
31
33
  end
32
34
  end
33
35
  end
36
+
37
+ def clone_attachment(name, original_attachment)
38
+ association_name = name.gsub(ATTACHMENTS_RELATION_REGEX, "")
39
+ original_blob = original_attachment.blob
40
+
41
+ # Handle missing file in storage service by bypassing the attachment cloning
42
+ return unless ActiveStorage::Blob.service.exist?(original_blob&.key)
43
+
44
+ Para::ActiveStorageDownloader.new(original_attachment).download_blob_to_tempfile do |tempfile|
45
+ attachment_target = clone.send(association_name)
46
+
47
+ attachment_target.attach({
48
+ io: tempfile,
49
+ filename: original_blob.filename,
50
+ content_type: original_blob.content_type
51
+ })
52
+
53
+ cloned_attachment = find_cloned_attachment(attachment_target, original_blob)
54
+
55
+ # Store the cloned attachment and blob into the deep_cloneable dictionary used
56
+ # by the `deep_clone` method to ensure that, if needed during the cloning
57
+ # operation, they won't be cloned once more and are accessible for processing
58
+ store_cloned(original_attachment, cloned_attachment)
59
+ store_cloned(original_blob, cloned_attachment.blob)
60
+ end
61
+ end
62
+
63
+ # Seemlessly handle one and many attachment relations return values and fetch
64
+ # the attachment that we just cloned by comparing blobs checksum, as depending
65
+ # which ActiveStorage version we're on (Rails 5.2 or 6), the `#attach` method
66
+ # doesn't always return the same, so for now we still handle the Rails 5.2 case.
67
+ def find_cloned_attachment(attachment_target, original_blob)
68
+ attachments = if attachment_target.attachments.any?
69
+ attachment_target.attachments
70
+ else
71
+ [attachment_target.attachment]
72
+ end
73
+
74
+ attachment = attachments.find do |att|
75
+ att.blob.checksum == original_blob.checksum
76
+ end
77
+ end
78
+
79
+ # This stores the source and clone resources into the deep_clone dictionary, which
80
+ # simulates what the deep_cloneable gem does when it clones a resource
81
+ #
82
+ def store_cloned(source, clone)
83
+ store_key = source.class.name.tableize.to_sym
84
+
85
+ dictionary[store_key] ||= {}
86
+ dictionary[store_key][source] = clone
87
+ end
34
88
  end
35
89
  end
36
- end
90
+ end
@@ -1,29 +1,91 @@
1
1
  module Para
2
2
  module Cloneable
3
+ # This object acts as a service to compile a nested cloneable options hash to be
4
+ # provided to the `deep_clone` method from the `deep_cloneable` gem. It iterates over
5
+ # every reflections that must be included for a given model when it's cloned, and
6
+ # creates a nested hash of :include and :except directives based on the tree that
7
+ # is created by nested `acts_as_cloneable` calls on the different models of the
8
+ # application
9
+ #
10
+ # Example :
11
+ #
12
+ # Given the following model structure :
13
+ #
14
+ # class Article < ApplicationRecord
15
+ # acts_as_cloneable :category, :comments, except: [:publication_date]
16
+ #
17
+ # belongs_to :category
18
+ # has_many :comments
19
+ # end
20
+ #
21
+ # class Category < ApplicationRecord
22
+ # acts_as_cloneable :category, except: [:articles_count]
23
+ #
24
+ # has_many :articles
25
+ # end
26
+ #
27
+ # class Comment < ApplicationRecord
28
+ # acts_as_cloneable :author
29
+ #
30
+ # belongs_to :article
31
+ # belongs_to :author
32
+ # end
33
+ #
34
+ # class Author < ApplicationRecord
35
+ # acts_as_cloneable except: [:email]
36
+ #
37
+ # has_many :articles
38
+ # end
39
+ #
40
+ # The behavior would be :
41
+ #
42
+ # Para::Cloneable::IncludeTreeBuilder.new(article).build
43
+ # # => {
44
+ # include: [:category, { comments: :author }],
45
+ # except: [:publication_date, {
46
+ # category: [:articles_count],
47
+ # comments: { author: [:email] }
48
+ # }]
49
+ # }
50
+ #
3
51
  class IncludeTreeBuilder
4
52
  attr_reader :resource, :cloneable_options
5
53
 
6
- def initialize(resource, cloneable_options)
54
+ def initialize(resource)
7
55
  @resource = resource
8
- @cloneable_options = cloneable_options.deep_dup
56
+ @cloneable_options = resource.cloneable_options.deep_dup
9
57
  end
10
-
58
+
11
59
  def build
12
- include_tree = build_cloneable_tree(resource, cloneable_options[:include])
13
- cloneable_options[:include] = clean_include_tree(include_tree)
14
- cloneable_options
60
+ options_tree = build_cloneable_options_tree(resource)
61
+ exceptions = extract_exceptions_from(options_tree)
62
+ inclusions = clean_options_tree(options_tree)
63
+ cloneable_options.merge(include: inclusions, except: exceptions)
15
64
  end
16
-
65
+
17
66
  private
18
67
 
19
- def build_cloneable_tree(resource, include)
20
- include.each_with_object({}) do |reflection_name, hash|
68
+ # The cloneable options tree iterates over the resources' relations that are
69
+ # declared as included in the cloneable_options of the provided resource, and
70
+ # recursively checks included relations for its associated resources.
71
+ #
72
+ # It returns a nested hash with the included relations and their :except array
73
+ # if it exist, which include the attributes that shouldn't be duplicated when
74
+ # the resource is cloned.
75
+ #
76
+ def build_cloneable_options_tree(resource)
77
+ cloneable_options = resource.cloneable_options
78
+ options = {}
79
+
80
+ # Iterate over the resource's cloneable options' :include array and recursively
81
+ # add nested included resources to its own included resources.
82
+ options = cloneable_options[:include].each_with_object({}) do |reflection_name, hash|
21
83
  hash[reflection_name] = {}
22
84
 
23
85
  if (reflection = resource.class.reflections[reflection_name.to_s])
24
86
  reflection_options = hash[reflection_name]
25
87
  association_target = resource.send(reflection_name)
26
-
88
+
27
89
  if reflection.collection?
28
90
  association_target.each do |nested_resource|
29
91
  add_reflection_options(reflection_options, nested_resource)
@@ -31,37 +93,72 @@ module Para
31
93
  else
32
94
  add_reflection_options(reflection_options, association_target)
33
95
  end
34
- end
96
+ end
35
97
  end
98
+
99
+ # Add the :except array from the resource to the current options hash and merge
100
+ # it if one already exist from another resource of the same class.
101
+ options[:except] ||= []
102
+ options[:except] |= Array.wrap(cloneable_options[:except])
103
+
104
+ options
36
105
  end
37
106
 
38
107
  def add_reflection_options(reflection_options, nested_resource)
39
108
  options = nested_resource.class.try(:cloneable_options)
40
109
  return reflection_options unless options
41
-
42
- include_options = options[:include]
43
- target_options = build_cloneable_tree(nested_resource, include_options)
110
+
111
+ target_options = build_cloneable_options_tree(nested_resource)
44
112
  reflection_options.deep_merge!(target_options)
45
113
  end
46
114
 
47
- def clean_include_tree(tree)
115
+ # Iterates over the generated options tree to extract all the nested :except options
116
+ # into their own separate hash, removing :except keys from the original options
117
+ # tree hash.
118
+ #
119
+ def extract_exceptions_from(tree)
120
+ exceptions = tree.delete(:except) || []
121
+ nested_exceptions = {}
122
+
123
+ tree.each do |key, value|
124
+ next unless value.is_a?(Hash) && !value.empty?
125
+
126
+ sub_exceptions = extract_exceptions_from(value)
127
+ nested_exceptions[key] = sub_exceptions unless sub_exceptions.empty?
128
+ end
129
+
130
+ exceptions += [nested_exceptions] unless nested_exceptions.empty?
131
+ exceptions
132
+ end
133
+
134
+ # Iterates over the remaining options tree hash and converts empty hash values' keys
135
+ # to be stored in an array, and returns an array of symbols and hashes that is
136
+ # compatible with what is expected as argument for the :include option of the
137
+ # `deep_clone` method.
138
+ #
139
+ # Example :
140
+ #
141
+ # clean_options_tree({ category: {}, comments: { author: {} } })
142
+ # # => [:category, { comments: [:author] }]
143
+ #
144
+ def clean_options_tree(tree)
48
145
  shallow_relations = []
49
146
  deep_relations = {}
50
-
147
+
51
148
  tree.each do |key, value|
149
+ # If the value is an empty hash, consider it as a shallow relation and add
150
+ # it to the shallow relations array
52
151
  if !value || value.empty?
53
152
  shallow_relations << key
153
+ # If the value is a hash with nested keys, process its nested values and add
154
+ # the result to the deep relations hash
54
155
  else
55
- deep_relations[key] = clean_include_tree(value)
156
+ deep_relations[key] = clean_options_tree(value)
56
157
  end
57
158
  end
58
159
 
59
- if deep_relations.empty?
60
- shallow_relations
61
- else
62
- shallow_relations + [deep_relations]
63
- end
160
+ deep_relations.empty? ? shallow_relations : shallow_relations + [deep_relations]
64
161
  end
65
162
  end
66
163
  end
67
- end
164
+ end
@@ -17,12 +17,14 @@ module Para
17
17
  # macro.
18
18
  #
19
19
  def deep_clone!(options = {})
20
- processed_options = Para::Cloneable::IncludeTreeBuilder.new(self, cloneable_options).build
20
+ dictionary = options[:dictionary] ||= {}
21
+
22
+ processed_options = Para::Cloneable::IncludeTreeBuilder.new(self).build
21
23
  options = options.reverse_merge(processed_options)
22
24
  callback = build_clone_callback(options.delete(:prepare))
23
-
25
+
24
26
  deep_clone(options) do |original, clone|
25
- Para::Cloneable::AttachmentsCloner.new(original, clone).clone!
27
+ Para::Cloneable::AttachmentsCloner.new(original, clone, dictionary).clone!
26
28
  callback&.call(original, clone)
27
29
  end
28
30
  end
@@ -63,9 +65,23 @@ module Para
63
65
  # if other sibling models don't define those relations
64
66
  options[:skip_missing_associations] = true
65
67
 
66
- self.cloneable_options = options.reverse_merge({
68
+ # If `acts_as_cloneable` is called multiple times, for example by a parent class
69
+ # and the by its subclass, ensure that we don't lose the previously defined
70
+ # cloneable options, to avoid having to repeat the same include options in each
71
+ # subclass, if it has to define subclass specific cloneable options.
72
+ previous_cloneable_options = cloneable_options || {}
73
+
74
+ # Prepare the new cloneable options hash with the provided arguments
75
+ new_cloneable_options = options.reverse_merge({
67
76
  include: args
68
77
  })
78
+
79
+ # Merges previous and new cloneable options into the cloneable_options class
80
+ # attribute, also merging the `:include` array
81
+ self.cloneable_options =
82
+ previous_cloneable_options.merge(new_cloneable_options) do |key, a, b|
83
+ a.is_a?(Array) && b.is_a?(Array) ? (a + b).uniq : b
84
+ end
69
85
  end
70
86
 
71
87
  def cloneable?
@@ -34,16 +34,24 @@ module Para
34
34
  def section_for(identifier)
35
35
  if (section = sections_cache[identifier])
36
36
  section
37
- elsif (section_id = sections_ids_hash[identifier])
38
- sections_cache[identifier] = Para::ComponentSection.find(section_id)
37
+ else
38
+ sections_cache[identifier] = if (section_id = sections_ids_hash[identifier])
39
+ Para::ComponentSection.find(section_id)
40
+ else
41
+ Para::ComponentSection.find_by(identifier: identifier)
42
+ end
39
43
  end
40
44
  end
41
45
 
42
46
  def component_for(identifier)
43
47
  if (component = components_cache[identifier])
44
48
  component
45
- elsif (component_id = components_ids_hash[identifier])
46
- components_cache[identifier] = Para::Component::Base.find(component_id)
49
+ else
50
+ components_cache[identifier] = if (component_id = components_ids_hash[identifier])
51
+ Para::Component::Base.find(component_id)
52
+ else
53
+ Para::Component::Base.find_by(identifier: identifier)
54
+ end
47
55
  end
48
56
  end
49
57
 
data/lib/para/engine.rb CHANGED
@@ -24,6 +24,7 @@ module Para
24
24
 
25
25
  initializer 'Para Cloneable' do
26
26
  ActiveSupport.on_load(:active_record) do
27
+ prepend Para::Ext::DeepCloneExtension
27
28
  include Para::Cloneable
28
29
  end
29
30
  end
@@ -0,0 +1,26 @@
1
+ require "deep_cloneable/deep_clone"
2
+
3
+ module Para
4
+ module Ext
5
+ module DeepCloneExtension
6
+ # Override the default deep_cloneable method to avoid nested :except rules that target
7
+ # polymorphic relations to try to assign default values to unexisting attributes on
8
+ # models that don't define the excluded attribute
9
+ #
10
+ # For example, we can have :
11
+ #
12
+ # { except: { comments: { author: [:confirmation_token] } } }
13
+ #
14
+ # Because some comments have a an author that's a user, and the user `acts_as_cloneable`
15
+ # macro defines `{ except: [:confirmation_token] }`, but if one of the comments has
16
+ # an anonymous user in its author relation, this method would faild with a
17
+ # ActiveModel::MissingAttributeError.
18
+ #
19
+ def dup_default_attribute_value_to(kopy, attribute, origin)
20
+ return unless kopy.attributes.keys.include?(attribute.to_s)
21
+
22
+ kopy[attribute] = origin.class.column_defaults.dup[attribute.to_s]
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/para/ext.rb CHANGED
@@ -10,6 +10,7 @@ module Para
10
10
  end
11
11
 
12
12
  require 'para/ext/paperclip'
13
+ require 'para/ext/deep_cloneable'
13
14
  require 'para/ext/active_job_status'
14
15
  require 'para/ext/active_record_nested_attributes'
15
16
  require 'para/ext/request_iframe_xhr'
@@ -76,6 +76,14 @@ module Para
76
76
  nested? && options[:parent_builder].object
77
77
  end
78
78
 
79
+ # Returns the partial name to be looked up for rendering used inside the nested
80
+ # fields partials, for the nested fields container and the remote nested fields
81
+ # partial.
82
+ #
83
+ def nested_fields_partial_name
84
+ :fields
85
+ end
86
+
79
87
  private
80
88
 
81
89
  def default_resource_name
@@ -3,7 +3,7 @@ module Para
3
3
  class NestedManyInput < NestedBaseInput
4
4
  attr_reader :resource
5
5
 
6
- def input(wrapper_options = nil)
6
+ def input(_wrapper_options = nil)
7
7
  input_html_options[:class] << "nested-many"
8
8
 
9
9
  orderable = options.fetch(:orderable, model.orderable?)
@@ -40,6 +40,8 @@ module Para
40
40
  end
41
41
  end
42
42
 
43
+ private
44
+
43
45
  def parent_model
44
46
  @parent_model ||= @builder.object.class
45
47
  end
data/lib/para/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Para
2
- VERSION = '0.8.15'
4
+ VERSION = '0.9.3.2'
3
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: para
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.15
4
+ version: 0.9.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Valentin Ballestrino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-22 00:00:00.000000000 Z
11
+ date: 2021-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '4.0'
20
20
  - - "<="
21
21
  - !ruby/object:Gem::Version
22
- version: '6.0'
22
+ version: '7.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '4.0'
30
30
  - - "<="
31
31
  - !ruby/object:Gem::Version
32
- version: '6.0'
32
+ version: '7.0'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: rails-i18n
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -152,16 +152,22 @@ dependencies:
152
152
  name: paperclip
153
153
  requirement: !ruby/object:Gem::Requirement
154
154
  requirements:
155
- - - "~>"
155
+ - - ">="
156
156
  - !ruby/object:Gem::Version
157
157
  version: '4.2'
158
+ - - "<"
159
+ - !ruby/object:Gem::Version
160
+ version: '7.0'
158
161
  type: :runtime
159
162
  prerelease: false
160
163
  version_requirements: !ruby/object:Gem::Requirement
161
164
  requirements:
162
- - - "~>"
165
+ - - ">="
163
166
  - !ruby/object:Gem::Version
164
167
  version: '4.2'
168
+ - - "<"
169
+ - !ruby/object:Gem::Version
170
+ version: '7.0'
165
171
  - !ruby/object:Gem::Dependency
166
172
  name: cancancan
167
173
  requirement: !ruby/object:Gem::Requirement
@@ -334,16 +340,22 @@ dependencies:
334
340
  name: bootstrap-sass
335
341
  requirement: !ruby/object:Gem::Requirement
336
342
  requirements:
337
- - - "~>"
343
+ - - ">="
344
+ - !ruby/object:Gem::Version
345
+ version: '3.3'
346
+ - - "<"
338
347
  - !ruby/object:Gem::Version
339
- version: 3.3.0
348
+ version: '3.5'
340
349
  type: :runtime
341
350
  prerelease: false
342
351
  version_requirements: !ruby/object:Gem::Requirement
343
352
  requirements:
344
- - - "~>"
353
+ - - ">="
354
+ - !ruby/object:Gem::Version
355
+ version: '3.3'
356
+ - - "<"
345
357
  - !ruby/object:Gem::Version
346
- version: 3.3.0
358
+ version: '3.5'
347
359
  - !ruby/object:Gem::Dependency
348
360
  name: font-awesome-rails
349
361
  requirement: !ruby/object:Gem::Requirement
@@ -754,6 +766,7 @@ files:
754
766
  - lib/para/ext.rb
755
767
  - lib/para/ext/active_job_status.rb
756
768
  - lib/para/ext/active_record_nested_attributes.rb
769
+ - lib/para/ext/deep_cloneable.rb
757
770
  - lib/para/ext/paperclip.rb
758
771
  - lib/para/ext/request_iframe_xhr.rb
759
772
  - lib/para/form_builder.rb