para 0.7.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58c45365ce3416da6671641777b577c9f4b406df
4
- data.tar.gz: 10e4cf5a2aebb52802790aee53dd1cc8f207af61
3
+ metadata.gz: 47cd4fad8f6308c2c3ae1948bea744640d0ce772
4
+ data.tar.gz: e6f7cf338d48a9ef60f696221413c263d34dcc10
5
5
  SHA512:
6
- metadata.gz: de0f4b0d6dfb69d07220eefc9701287dc3aadca482b0b0443b4e906d3192fad71a69c0ba7d4b6de5f024fc9ef6c82b10b0ece961b704b4f2e6e3794613923d48
7
- data.tar.gz: 75109b33e3a76136ca050d5d772bcfde934ec73c0d9478a99981f458a923f53b419ef46c0612129f52ce174779419f0d7ac563aa43f134da9068e2c04ad852d8
6
+ metadata.gz: e38d091236ea396e0836560be1128c551393495ea6e967ee6a40f73ccfc301e1ded6e5682b5b23f65e1ae7406c8fc1e820d57a8c9edb3d53fc109735620b1960
7
+ data.tar.gz: c31d521f7c6a38eadb0646ecdc48f23ca8e258c4b0b285c5b90301013175fde19792716654d7ceeca9180895853d7c4c237dc9c1a10e41d7d5202cad8fb33c41
@@ -4,8 +4,8 @@ module Para
4
4
  Para::Markup::Alert.new(self).container(message, options, &block)
5
5
  end
6
6
 
7
- def resources_table(options = {}, &block)
8
- Para::Markup::ResourcesTable.new(self).container(options, &block)
7
+ def resources_table(component:, **options, &block)
8
+ Para::Markup::ResourcesTable.new(component, self).container(options, &block)
9
9
  end
10
10
 
11
11
  def panel(options = {}, &block)
@@ -1,4 +1,4 @@
1
- = resources_table(model: model, component: component) do |table|
1
+ = resources_table(component: component, model: model) do |table|
2
2
  = table.header do
3
3
  - attributes.each do |field|
4
4
  = table.header_for(field.name.to_sym)
@@ -4,13 +4,13 @@
4
4
  %i.sort-dots
5
5
  %span.node-name
6
6
  = resource_name(root)
7
+
8
+ - buttons = Para::Markup::ResourcesButtons.new(@component, self)
9
+ - actions = Para::Markup::ResourcesTable.default_actions
7
10
 
8
11
  .pull-right
9
- = link_to @component.relation_path(root, action: :edit, return_to: request.fullpath), class: "btn btn-icon-primary btn-shadow btn-sm hint--left", :'aria-label' => ::I18n.t('para.shared.edit') do
10
- %i.fa.fa-pencil
11
-
12
- = link_to @component.relation_path(root), method: :delete, data: { confirm: ::I18n.t('para.list.delete_confirmation') }, class: 'btn btn-icon-danger btn-shadow btn-sm hint--left', :'aria-label' => ::I18n.t('para.shared.destroy') do
13
- %i.fa.fa-times
12
+ - actions.each do |action|
13
+ = buttons.send(:"#{action}_button", root)
14
14
 
15
15
  .clearfix
16
16
  %ul.tree
@@ -1,4 +1,4 @@
1
- = resources_table(model: model, component: component) do |table|
1
+ = resources_table(component: component, model: model) do |table|
2
2
  = table.header do
3
3
  <%- attributes.each do |field| -%>
4
4
  = table.header_for(:<%= field.name %>)
@@ -0,0 +1,23 @@
1
+ module Para
2
+ class ActiveStorageDownloader
3
+ if defined?(ActiveStorage)
4
+ include ActiveStorage::Downloading
5
+ end
6
+
7
+ attr_reader :attachment
8
+
9
+ delegate :blob, to: :attachment
10
+
11
+ def initialize(attachment)
12
+ @attachment = attachment
13
+ end
14
+
15
+ if defined?(ActiveStorage)
16
+ public :download_blob_to_tempfile
17
+ else
18
+ define_method(:download_blob_to_tempfile) do
19
+ raise NoMethodError, "ActiveStorage is not included in your application"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ module Para
2
+ module Cloneable
3
+ class AttachmentsCloner
4
+ attr_reader :original, :clone
5
+
6
+ def initialize(original, clone)
7
+ @original = original
8
+ @clone = clone
9
+ end
10
+
11
+ def clone!
12
+ 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
+ }
18
+
19
+ 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
+ })
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,67 @@
1
+ module Para
2
+ module Cloneable
3
+ class IncludeTreeBuilder
4
+ attr_reader :resource, :cloneable_options
5
+
6
+ def initialize(resource, cloneable_options)
7
+ @resource = resource
8
+ @cloneable_options = cloneable_options.deep_dup
9
+ end
10
+
11
+ def build
12
+ include_tree = build_cloneable_tree(resource, cloneable_options[:include])
13
+ cloneable_options[:include] = clean_include_tree(include_tree)
14
+ cloneable_options
15
+ end
16
+
17
+ private
18
+
19
+ def build_cloneable_tree(resource, include)
20
+ include.each_with_object({}) do |reflection_name, hash|
21
+ hash[reflection_name] = {}
22
+
23
+ if (reflection = resource.class.reflections[reflection_name.to_s])
24
+ reflection_options = hash[reflection_name]
25
+ association_target = resource.send(reflection_name)
26
+
27
+ if reflection.collection?
28
+ association_target.each do |nested_resource|
29
+ add_reflection_options(reflection_options, nested_resource)
30
+ end
31
+ else
32
+ add_reflection_options(reflection_options, association_target)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def add_reflection_options(reflection_options, nested_resource)
39
+ options = nested_resource.class.try(:cloneable_options)
40
+ return reflection_options unless options
41
+
42
+ include_options = options[:include]
43
+ target_options = build_cloneable_tree(nested_resource, include_options)
44
+ reflection_options.deep_merge!(target_options)
45
+ end
46
+
47
+ def clean_include_tree(tree)
48
+ shallow_relations = []
49
+ deep_relations = {}
50
+
51
+ tree.each do |key, value|
52
+ if !value || value.empty?
53
+ shallow_relations << key
54
+ else
55
+ deep_relations[key] = clean_include_tree(value)
56
+ end
57
+ end
58
+
59
+ if deep_relations.empty?
60
+ shallow_relations
61
+ else
62
+ shallow_relations + [deep_relations]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,20 +1,30 @@
1
1
  module Para
2
2
  module Cloneable
3
3
  extend ActiveSupport::Concern
4
+ extend ActiveSupport::Autoload
4
5
 
5
6
  included do
6
7
  class_attribute :cloneable_options
7
8
  end
8
9
 
10
+ eager_autoload do
11
+ autoload :IncludeTreeBuilder
12
+ autoload :AttachmentsCloner
13
+ end
14
+
9
15
  # Wraps the deep_cloneable gem #deep_clone method to allow using the
10
16
  # predefined associations and options from our Cloneable.acts_as_cloneable
11
17
  # macro.
12
18
  #
13
19
  def deep_clone!(options = {})
14
- options = options.reverse_merge(cloneable_options)
20
+ processed_options = Para::Cloneable::IncludeTreeBuilder.new(self, cloneable_options).build
21
+ options = options.reverse_merge(processed_options)
15
22
  callback = build_clone_callback(options.delete(:prepare))
16
-
17
- deep_clone(options, &callback)
23
+
24
+ deep_clone(options) do |original, clone|
25
+ Para::Cloneable::AttachmentsCloner.new(original, clone).clone!
26
+ callback&.call(original, clone)
27
+ end
18
28
  end
19
29
 
20
30
  private
@@ -49,6 +59,10 @@ module Para
49
59
 
50
60
  options = args.extract_options!
51
61
 
62
+ # Allow nested STI resources to define their own relations to clone even
63
+ # if other sibling models don't define those relations
64
+ options[:skip_missing_associations] = true
65
+
52
66
  self.cloneable_options = options.reverse_merge({
53
67
  include: args
54
68
  })
data/lib/para/job/base.rb CHANGED
@@ -9,7 +9,7 @@ module Para
9
9
 
10
10
  rescue_from Exception, with: :rescue_exception
11
11
 
12
- before_perform :store_job_type
12
+ before_enqueue :store_job_type
13
13
 
14
14
  protected
15
15
 
@@ -0,0 +1,61 @@
1
+ module Para
2
+ module Markup
3
+ class ResourcesButtons < Para::Markup::Component
4
+ attr_reader :component
5
+
6
+ def initialize(component, view)
7
+ @component = component
8
+ super(view)
9
+ end
10
+
11
+ def clone_button(resource)
12
+ return unless resource.class.cloneable?
13
+
14
+ path = component.relation_path(
15
+ resource, action: :clone, return_to: view.request.fullpath
16
+ )
17
+
18
+ options = {
19
+ method: :post,
20
+ class: 'btn btn-sm btn-icon-info btn-shadow hint--left',
21
+ aria: {
22
+ label: ::I18n.t('para.shared.copy')
23
+ }
24
+ }
25
+
26
+ view.link_to(path, options) do
27
+ content_tag(:i, '', class: 'fa fa-copy')
28
+ end
29
+ end
30
+
31
+ def edit_button(resource)
32
+ path = component.relation_path(
33
+ resource, action: :edit, return_to: view.request.fullpath
34
+ )
35
+
36
+ view.link_to(path, class: 'btn btn-sm btn-icon-primary btn-shadow hint--left', aria: { label: ::I18n.t('para.shared.edit') }) do
37
+ content_tag(:i, '', class: 'fa fa-pencil')
38
+ end
39
+ end
40
+
41
+ def delete_button(resource)
42
+ path = component.relation_path(resource, return_to: view.request.fullpath)
43
+
44
+ options = {
45
+ method: :delete,
46
+ data: {
47
+ confirm: ::I18n.t('para.list.delete_confirmation')
48
+ },
49
+ class: 'btn btn-sm btn-icon-danger btn-shadow hint--left',
50
+ aria: {
51
+ label: ::I18n.t('para.shared.destroy')
52
+ }
53
+ }
54
+
55
+ view.link_to(path, options) do
56
+ content_tag(:i, '', class: 'fa fa-times')
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -4,11 +4,15 @@ module Para
4
4
  class_attribute :default_actions
5
5
  self.default_actions = [:edit, :clone, :delete]
6
6
 
7
- attr_reader :model, :component, :orderable, :actions
7
+ attr_reader :component, :model, :orderable, :actions
8
+
9
+ def initialize(component, view)
10
+ @component = component
11
+ super(view)
12
+ end
8
13
 
9
14
  def container(options = {}, &block)
10
15
  @model = options.delete(:model)
11
- @component = options.delete(:component)
12
16
 
13
17
  if !options.key?(:orderable) || options.delete(:orderable)
14
18
  @orderable = model.orderable?
@@ -131,62 +135,15 @@ module Para
131
135
  end
132
136
 
133
137
  def actions_cell(resource)
138
+ buttons = ResourcesButtons.new(component, view)
139
+
134
140
  content_tag(:td, class: 'table-row-actions') do
135
141
  actions.map do |type|
136
- send(:"#{ type }_button", resource)
142
+ buttons.send(:"#{ type }_button", resource)
137
143
  end.compact.join.html_safe
138
144
  end
139
145
  end
140
146
 
141
- def clone_button(resource)
142
- return unless resource.class.cloneable?
143
-
144
- path = component.relation_path(
145
- resource, action: :clone, return_to: view.request.fullpath
146
- )
147
-
148
- options = {
149
- method: :post,
150
- class: 'btn btn-sm btn-icon-info btn-shadow hint--left',
151
- aria: {
152
- label: ::I18n.t('para.shared.copy')
153
- }
154
- }
155
-
156
- view.link_to(path, options) do
157
- content_tag(:i, '', class: 'fa fa-copy')
158
- end
159
- end
160
-
161
- def edit_button(resource)
162
- path = component.relation_path(
163
- resource, action: :edit, return_to: view.request.fullpath
164
- )
165
-
166
- view.link_to(path, class: 'btn btn-sm btn-icon-primary btn-shadow hint--left', aria: { label: ::I18n.t('para.shared.edit') }) do
167
- content_tag(:i, '', class: 'fa fa-pencil')
168
- end
169
- end
170
-
171
- def delete_button(resource)
172
- path = component.relation_path(resource, return_to: view.request.fullpath)
173
-
174
- options = {
175
- method: :delete,
176
- data: {
177
- confirm: ::I18n.t('para.list.delete_confirmation')
178
- },
179
- class: 'btn btn-sm btn-icon-danger btn-shadow hint--left',
180
- aria: {
181
- label: ::I18n.t('para.shared.destroy')
182
- }
183
- }
184
-
185
- view.link_to(path, options) do
186
- content_tag(:i, '', class: 'fa fa-times')
187
- end
188
- end
189
-
190
147
  private
191
148
 
192
149
  def search
data/lib/para/markup.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'para/markup/component'
2
2
  require 'para/markup/resources_table'
3
+ require 'para/markup/resources_buttons'
3
4
  require 'para/markup/modal'
4
5
  require 'para/markup/panel'
5
6
  require 'para/markup/alert'
data/lib/para/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Para
2
- VERSION = '0.7.4'
2
+ VERSION = '0.8.0'
3
3
  end
data/lib/para.rb CHANGED
@@ -52,6 +52,7 @@ module Para
52
52
  autoload :Cache
53
53
  autoload :Logging
54
54
  autoload :IframeTransport
55
+ autoload :ActiveStorageDownloader
55
56
  end
56
57
 
57
58
  def self.config(&block)
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.7.4
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Valentin Ballestrino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-04 00:00:00.000000000 Z
11
+ date: 2019-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -701,6 +701,7 @@ files:
701
701
  - lib/generators/para/table/templates/_table.html.haml
702
702
  - lib/generators/para/tree_item/tree_item_generator.rb
703
703
  - lib/para.rb
704
+ - lib/para/active_storage_downloader.rb
704
705
  - lib/para/attribute_field.rb
705
706
  - lib/para/attribute_field/base.rb
706
707
  - lib/para/attribute_field/belongs_to.rb
@@ -726,6 +727,8 @@ files:
726
727
  - lib/para/cache.rb
727
728
  - lib/para/cache/database_store.rb
728
729
  - lib/para/cloneable.rb
730
+ - lib/para/cloneable/attachments_cloner.rb
731
+ - lib/para/cloneable/include_tree_builder.rb
729
732
  - lib/para/component.rb
730
733
  - lib/para/component/exportable.rb
731
734
  - lib/para/component/history.rb
@@ -782,6 +785,7 @@ files:
782
785
  - lib/para/markup/component.rb
783
786
  - lib/para/markup/modal.rb
784
787
  - lib/para/markup/panel.rb
788
+ - lib/para/markup/resources_buttons.rb
785
789
  - lib/para/markup/resources_table.rb
786
790
  - lib/para/markup/resources_tree.rb
787
791
  - lib/para/model_field_parsers.rb