activekit 0.3.2 → 0.4.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 +4 -4
- data/README.md +34 -0
- data/lib/active_kit/engine.rb +3 -1
- data/lib/active_kit/export/exportable.rb +65 -0
- data/lib/active_kit/export/exporter.rb +106 -0
- data/lib/active_kit/export/exporting.rb +104 -0
- data/lib/active_kit/export.rb +8 -0
- data/lib/active_kit/version.rb +1 -1
- data/lib/active_kit.rb +1 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2dffaf7d844bba5ea65c34b98f3872cf70cb44c76963fb0d7a96bb39df174acb
|
|
4
|
+
data.tar.gz: 5c64d866b07f8460145f0a8069ac316e96d9ee15985fcce13e9a598fd2f6dedc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f704f847169d738fa9f708246661b1dfff4594349fd3c8297b9070542046684106b5f5a831505bc5160c544fcc79d13b2c2920520e5184191832f34ff3e62979
|
|
7
|
+
data.tar.gz: af2af6217c992fb16c4205b88e0172947b043c2f52c93f389570989444a4726aa0fa62392e7d27b4dc48450120cb943c0e7e38c9f4606a3719d0a868badc8608
|
data/README.md
CHANGED
|
@@ -3,6 +3,40 @@ Add the essential kit for rails ActiveRecord models and be happy.
|
|
|
3
3
|
|
|
4
4
|
## Usage
|
|
5
5
|
|
|
6
|
+
### Export Attribute
|
|
7
|
+
|
|
8
|
+
Add exporting to your ActiveRecord models.
|
|
9
|
+
Export Attribute provides full exporting functionality for your model database records.
|
|
10
|
+
|
|
11
|
+
You can define any number of model attributes and association attributes in one model to export together.
|
|
12
|
+
|
|
13
|
+
Define the export attributes in accordance with the column name in your model like below.
|
|
14
|
+
```ruby
|
|
15
|
+
class Product < ApplicationRecord
|
|
16
|
+
export_attribute :name
|
|
17
|
+
export_attribute :sku, heading: "SKU No."
|
|
18
|
+
export_attribute :image_name, value: lambda { |record| record.image&.name }, includes: :image
|
|
19
|
+
export_attribute :variations, value: lambda { |record| record.variations }, includes: :variations, attributes: [:name, :price, discount_value: { heading: "Discount" }]
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
You can also define an export_describer to describe the details of the export instead of using the defaults.
|
|
24
|
+
```ruby
|
|
25
|
+
class Product < ApplicationRecord
|
|
26
|
+
# export_describer method_name, kind: :csv, database: -> { ActiveRecord::Base.connection_db_config.database.to_sym }
|
|
27
|
+
export_describer :to_csv, kind: :csv, database: -> { System::Current.tenant.database.to_sym }
|
|
28
|
+
export_attribute :name
|
|
29
|
+
export_attribute :sku, heading: "SKU No."
|
|
30
|
+
export_attribute :image_name, value: lambda { |record| record.image&.name }, includes: :image
|
|
31
|
+
export_attribute :variations, value: lambda { |record| record.variations }, includes: :variations, attributes: [:name, :price, discount_value: { heading: "Discount" }]
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The following class methods will be added to your model class to use in accordance with details provided for export_describer:
|
|
36
|
+
```ruby
|
|
37
|
+
Product.to_csv
|
|
38
|
+
```
|
|
39
|
+
|
|
6
40
|
### Position Attribute
|
|
7
41
|
|
|
8
42
|
Add positioning to your ActiveRecord models.
|
data/lib/active_kit/engine.rb
CHANGED
|
@@ -9,10 +9,12 @@ module ActiveKit
|
|
|
9
9
|
app.middleware.use ActiveKit::Position::Middleware
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
initializer "active_kit.
|
|
12
|
+
initializer "active_kit.activekitable" do
|
|
13
|
+
require "active_kit/export/exportable"
|
|
13
14
|
require "active_kit/position/positionable"
|
|
14
15
|
|
|
15
16
|
ActiveSupport.on_load(:active_record) do
|
|
17
|
+
include ActiveKit::Export::Exportable
|
|
16
18
|
include ActiveKit::Position::Positionable
|
|
17
19
|
end
|
|
18
20
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
module ActiveKit
|
|
4
|
+
module Export
|
|
5
|
+
module Exportable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class_methods do
|
|
12
|
+
def exporter
|
|
13
|
+
@exporter ||= ActiveKit::Export::Exporter.new(current_class: self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def export_describer(name, **options)
|
|
17
|
+
name = name.to_sym
|
|
18
|
+
options.deep_symbolize_keys!
|
|
19
|
+
|
|
20
|
+
unless exporter.find_describer_by(describer_name: name)
|
|
21
|
+
exporter.new_describer(name: name, options: options)
|
|
22
|
+
define_describer_method(kind: options[:kind], name: name)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def export_attribute(name, **options)
|
|
27
|
+
export_describer(:to_csv, kind: :csv, database: -> { ActiveRecord::Base.connection_db_config.database.to_sym }) unless exporter.describers?
|
|
28
|
+
|
|
29
|
+
options.deep_symbolize_keys!
|
|
30
|
+
exporter.new_attribute(name: name.to_sym, options: options)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def define_describer_method(kind:, name:)
|
|
34
|
+
case kind
|
|
35
|
+
when :csv
|
|
36
|
+
define_singleton_method name do
|
|
37
|
+
describer = exporter.find_describer_by(describer_name: name)
|
|
38
|
+
raise "could not find describer for the describer name '#{name}'" unless describer.present?
|
|
39
|
+
|
|
40
|
+
# The 'all' relation must be captured outside the Enumerator,
|
|
41
|
+
# else it will get reset to all the records of the class.
|
|
42
|
+
all_activerecord_relation = all.includes(describer.includes)
|
|
43
|
+
|
|
44
|
+
Enumerator.new do |yielder|
|
|
45
|
+
ActiveRecord::Base.connected_to(role: :writing, shard: describer.database.call) do
|
|
46
|
+
exporting = exporter.new_exporting(describer: describer)
|
|
47
|
+
|
|
48
|
+
# Add the headings.
|
|
49
|
+
yielder << CSV.generate_line(exporting.headings) if exporting.headings?
|
|
50
|
+
|
|
51
|
+
# Add the values.
|
|
52
|
+
# find_each will ignore any order if set earlier.
|
|
53
|
+
all_activerecord_relation.find_each do |record|
|
|
54
|
+
lines = exporting.lines_for(record: record)
|
|
55
|
+
lines.each { |line| yielder << CSV.generate_line(line) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module ActiveKit
|
|
2
|
+
module Export
|
|
3
|
+
class Exporter
|
|
4
|
+
attr_reader :describers
|
|
5
|
+
|
|
6
|
+
def initialize(current_class:)
|
|
7
|
+
@current_class = current_class
|
|
8
|
+
@describers = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def find_describer_by(describer_name:)
|
|
12
|
+
describer_options = @describers.dig(describer_name)
|
|
13
|
+
return nil unless describer_options.present?
|
|
14
|
+
|
|
15
|
+
describer_attributes = describer_options[:attributes]
|
|
16
|
+
includes = describer_attributes.values.map { |options| options.dig(:includes) }.compact.flatten(1).uniq
|
|
17
|
+
fields = build_describer_fields(describer_attributes)
|
|
18
|
+
hash = {
|
|
19
|
+
name: describer_name,
|
|
20
|
+
kind: describer_options[:kind],
|
|
21
|
+
database: describer_options[:database],
|
|
22
|
+
attributes: describer_attributes,
|
|
23
|
+
includes: includes,
|
|
24
|
+
fields: fields
|
|
25
|
+
}
|
|
26
|
+
OpenStruct.new(hash)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def new_describer(name:, options:)
|
|
30
|
+
options.store(:attributes, {})
|
|
31
|
+
@describers.store(name, options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def describers?
|
|
35
|
+
@describers.present?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def new_attribute(name:, options:)
|
|
39
|
+
describer_names = Array(options.delete(:describers))
|
|
40
|
+
describer_names = @describers.keys if describer_names.blank?
|
|
41
|
+
|
|
42
|
+
describer_names.each do |describer_name|
|
|
43
|
+
if describer_options = @describers.dig(describer_name)
|
|
44
|
+
describer_options[:attributes].store(name, options)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def new_exporting(describer:)
|
|
50
|
+
Exporting.new(describer: describer)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def build_describer_fields(describer_attributes)
|
|
56
|
+
describer_attributes.inject({}) do |fields_hash, (name, options)|
|
|
57
|
+
enclosed_attributes = Array(options.dig(:attributes))
|
|
58
|
+
|
|
59
|
+
if enclosed_attributes.blank?
|
|
60
|
+
field_key, field_value = (get_heading(options.dig(:heading))&.to_s || name.to_s.titleize), (options.dig(:value) || name)
|
|
61
|
+
else
|
|
62
|
+
field_key, field_value = get_nested_field(name, options, enclosed_attributes)
|
|
63
|
+
end
|
|
64
|
+
fields_hash.store(field_key, field_value)
|
|
65
|
+
|
|
66
|
+
fields_hash
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def get_nested_field(name, options, enclosed_attributes, ancestor_heading = nil)
|
|
71
|
+
parent_heading = ancestor_heading.present? ? ancestor_heading : ""
|
|
72
|
+
parent_heading += (get_heading(options.dig(:heading))&.to_s || name.to_s.singularize.titleize) + " "
|
|
73
|
+
parent_value = options.dig(:value) || name
|
|
74
|
+
|
|
75
|
+
enclosed_attributes.inject([[], [parent_value]]) do |nested_field, enclosed_attribute|
|
|
76
|
+
unless enclosed_attribute.is_a? Hash
|
|
77
|
+
nested_field_key = parent_heading + enclosed_attribute.to_s.titleize
|
|
78
|
+
nested_field_val = enclosed_attribute
|
|
79
|
+
|
|
80
|
+
nested_field[0].push(nested_field_key)
|
|
81
|
+
nested_field[1].push(nested_field_val)
|
|
82
|
+
else
|
|
83
|
+
enclosed_attribute.each do |enclosed_attribute_key, enclosed_attribute_value|
|
|
84
|
+
wrapped_attributes = Array(enclosed_attribute_value.dig(:attributes))
|
|
85
|
+
if wrapped_attributes.blank?
|
|
86
|
+
nested_field_key = parent_heading + (get_heading(enclosed_attribute_value.dig(:heading))&.to_s || enclosed_attribute_key.to_s.titleize)
|
|
87
|
+
nested_field_val = enclosed_attribute_value.dig(:value) || enclosed_attribute_key
|
|
88
|
+
else
|
|
89
|
+
nested_field_key, nested_field_val = get_nested_field(enclosed_attribute_key, enclosed_attribute_value, wrapped_attributes, parent_heading)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
nested_field[0].push(nested_field_key)
|
|
93
|
+
nested_field[1].push(nested_field_val)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
nested_field
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def get_heading(options_heading)
|
|
102
|
+
options_heading.is_a?(Proc) ? options_heading.call(@current_class) : options_heading
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module ActiveKit
|
|
2
|
+
module Export
|
|
3
|
+
class Exporting
|
|
4
|
+
|
|
5
|
+
def initialize(describer:)
|
|
6
|
+
@describer = describer
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def headings
|
|
10
|
+
@headings ||= @describer.fields.keys.flatten
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def headings?
|
|
14
|
+
headings.present?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def lines_for(record:)
|
|
18
|
+
row_counter, column_counter = 1, 0
|
|
19
|
+
|
|
20
|
+
@describer.fields.inject([[]]) do |rows, (heading, value)|
|
|
21
|
+
if value.is_a? Proc
|
|
22
|
+
rows[0].push(value.call(record))
|
|
23
|
+
column_counter += 1
|
|
24
|
+
elsif value.is_a?(Symbol) || value.is_a?(String)
|
|
25
|
+
rows[0].push(record.public_send(value))
|
|
26
|
+
column_counter += 1
|
|
27
|
+
elsif value.is_a? Array
|
|
28
|
+
deeprows = get_deeprows(record, heading, value, column_counter)
|
|
29
|
+
deeprows.each do |deeprow|
|
|
30
|
+
rows[row_counter] = deeprow
|
|
31
|
+
row_counter += 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
column_count = get_column_count_for(value)
|
|
35
|
+
column_count.times { |i| rows[0].push(nil) }
|
|
36
|
+
column_counter += column_count
|
|
37
|
+
else
|
|
38
|
+
raise "Could not identify '#{value}' for '#{heading}'."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
rows
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def get_deeprows(record, heading, value, column_counter)
|
|
48
|
+
value_clone = value.clone
|
|
49
|
+
assoc_value = value_clone.shift
|
|
50
|
+
|
|
51
|
+
if assoc_value.is_a? Proc
|
|
52
|
+
assoc_records = assoc_value.call(record)
|
|
53
|
+
elsif assoc_value.is_a?(Symbol) || assoc_value.is_a?(String)
|
|
54
|
+
assoc_records = record.public_send(assoc_value)
|
|
55
|
+
else
|
|
56
|
+
raise "Count not identity '#{assoc_value}' for '#{heading}'."
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
subrows = []
|
|
60
|
+
assoc_records.each do |assoc_record|
|
|
61
|
+
subrow, subrow_column_counter, deeprows = [], 0, []
|
|
62
|
+
column_counter.times { |i| subrow.push(nil) }
|
|
63
|
+
|
|
64
|
+
subrow = value_clone.inject(subrow) do |subrow, v|
|
|
65
|
+
if v.is_a? Proc
|
|
66
|
+
subrow.push(v.call(assoc_record))
|
|
67
|
+
subrow_column_counter += 1
|
|
68
|
+
elsif v.is_a?(Symbol) || v.is_a?(String)
|
|
69
|
+
subrow.push(assoc_record.public_send(v))
|
|
70
|
+
subrow_column_counter += 1
|
|
71
|
+
elsif v.is_a? Array
|
|
72
|
+
deeprows = get_deeprows(assoc_record, heading, v, (column_counter + subrow_column_counter))
|
|
73
|
+
|
|
74
|
+
column_count = get_column_count_for(v)
|
|
75
|
+
column_count.times { |i| subrow.push(nil) }
|
|
76
|
+
subrow_column_counter += column_count
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
subrow
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
subrows.push(subrow)
|
|
83
|
+
deeprows.each { |deeprow| subrows.push(deeprow) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
subrows
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def get_column_count_for(value)
|
|
90
|
+
count = 0
|
|
91
|
+
|
|
92
|
+
value.each do |v|
|
|
93
|
+
unless v.is_a? Array
|
|
94
|
+
count += 1
|
|
95
|
+
else
|
|
96
|
+
count += get_column_count_for(v)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
count - 1
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
data/lib/active_kit/version.rb
CHANGED
data/lib/active_kit.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activekit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- plainsource
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-
|
|
11
|
+
date: 2024-03-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -51,6 +51,10 @@ files:
|
|
|
51
51
|
- config/routes.rb
|
|
52
52
|
- lib/active_kit.rb
|
|
53
53
|
- lib/active_kit/engine.rb
|
|
54
|
+
- lib/active_kit/export.rb
|
|
55
|
+
- lib/active_kit/export/exportable.rb
|
|
56
|
+
- lib/active_kit/export/exporter.rb
|
|
57
|
+
- lib/active_kit/export/exporting.rb
|
|
54
58
|
- lib/active_kit/position.rb
|
|
55
59
|
- lib/active_kit/position/harmonize.rb
|
|
56
60
|
- lib/active_kit/position/middleware.rb
|