para 0.7.4 → 0.8.0

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
  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