spotlight_search 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/app/jobs/spotlight_search/export_job.rb +35 -4
- data/lib/spotlight_search.rb +9 -0
- data/lib/spotlight_search/exceptions.rb +1 -1
- data/lib/spotlight_search/exportable_columns.rb +24 -29
- data/lib/spotlight_search/exportable_columns_v2.rb +102 -0
- data/lib/spotlight_search/helpers.rb +23 -3
- data/lib/spotlight_search/utils.rb +69 -0
- data/lib/spotlight_search/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d8c71e34af7024e41f5d6767ce6b9cc6a4466aed6882997d97b4f45e57c2500
|
4
|
+
data.tar.gz: bd7d756c813e0a0572dd4973e8f795ce5195740811460bd91fa595aafa556080
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e83a3b01980d9d3fa0fea02ee9f9f226ec882dfbaaff6b13bf69748b9e37ef8e7cc6b8487cba6a27a4d2d1681864ae06d490628cf01d4746a6fce1c63cc9bf4
|
7
|
+
data.tar.gz: 1fca5cef1d8ced80eb3c9a040152277002758b592b34af18674ba205737fecba884de3f397ac4a4edf74b4104178dcdd1f75b4ae219f52b9b10e0a776adc8907
|
data/README.md
CHANGED
@@ -20,6 +20,8 @@ Or install it manually:
|
|
20
20
|
|
21
21
|
$ gem install spotlight_search
|
22
22
|
|
23
|
+
Include the spotlight_search javascript by adding the line `//= require spotlight_search` to your `app/assets/javascripts/application.js`
|
24
|
+
|
23
25
|
## Usage
|
24
26
|
|
25
27
|
1. [Filtering, Sorting and Pagination](#filtering-sorting-and-pagination)
|
@@ -169,7 +171,7 @@ Add `exportable email, model_object` in your view to display the export button.
|
|
169
171
|
</td>
|
170
172
|
</table>
|
171
173
|
|
172
|
-
<%= exportable(current_user.email,
|
174
|
+
<%= exportable(current_user.email, Person) %>
|
173
175
|
```
|
174
176
|
|
175
177
|
This will first show a popup where an option to select the export enabled columns will be listed. This will also apply any filters that has been selected along with a sorting if applied. It then pushes the export to a background job which will send an excel file of the contents to the specified email. You can edit the style of the button using the class `export-to-file-btn`.
|
@@ -5,7 +5,13 @@ module SpotlightSearch
|
|
5
5
|
def perform(email, klass, columns = [], filters = {})
|
6
6
|
klass = klass.constantize
|
7
7
|
records = get_records(klass, filters, columns)
|
8
|
-
file_path =
|
8
|
+
file_path =
|
9
|
+
case SpotlightSearch.exportable_columns_version
|
10
|
+
when :v1
|
11
|
+
create_excel(records, klass.name, columns)
|
12
|
+
when :v2
|
13
|
+
create_excel_v2(records, klass.name)
|
14
|
+
end
|
9
15
|
subject = "#{klass.name} export at #{Time.now}"
|
10
16
|
ExportMailer.send_excel_file(email, file_path, subject).deliver_now
|
11
17
|
File.delete(file_path)
|
@@ -13,7 +19,7 @@ module SpotlightSearch
|
|
13
19
|
|
14
20
|
def get_records(klass, filters, columns)
|
15
21
|
records = klass
|
16
|
-
if filters
|
22
|
+
if !filters.empty?
|
17
23
|
if filters['filters'].present?
|
18
24
|
filters['filters'].each do |scope, scope_args|
|
19
25
|
if scope_args.is_a?(Array)
|
@@ -26,9 +32,16 @@ module SpotlightSearch
|
|
26
32
|
if filters['sort'].present?
|
27
33
|
records = records.order("#{filters['sort']['sort_column']} #{filters['sort']['sort_direction']}")
|
28
34
|
end
|
35
|
+
else
|
36
|
+
records = records.all
|
37
|
+
end
|
38
|
+
case SpotlightSearch.exportable_columns_version
|
39
|
+
when :v1
|
40
|
+
columns = columns.map(&:to_sym)
|
41
|
+
records.select(*columns)
|
42
|
+
when :v2
|
43
|
+
records.as_json(SpotlightSearch::Utils.deserialize_csv_columns(columns, :as_json_params))
|
29
44
|
end
|
30
|
-
columns = columns.map(&:to_sym)
|
31
|
-
records.select(*columns)
|
32
45
|
end
|
33
46
|
|
34
47
|
# Creating excel with the passed records
|
@@ -48,5 +61,23 @@ module SpotlightSearch
|
|
48
61
|
xl.serialize(file_location)
|
49
62
|
file_location
|
50
63
|
end
|
64
|
+
|
65
|
+
def create_excel_v2(records, class_name)
|
66
|
+
flattened_records = records.map { |record| SpotlightSearch::Utils.flatten_hash(record) }
|
67
|
+
columns = flattened_records[0].keys
|
68
|
+
size_arr = []
|
69
|
+
columns.size.times { size_arr << 22 }
|
70
|
+
xl = Axlsx::Package.new
|
71
|
+
xl.workbook.add_worksheet do |sheet|
|
72
|
+
sheet.add_row columns, b: true
|
73
|
+
flattened_records.each do |record|
|
74
|
+
sheet.add_row(columns.map { |column| record[column] })
|
75
|
+
end
|
76
|
+
sheet.column_widths(*size_arr)
|
77
|
+
end
|
78
|
+
file_location = "#{Rails.root}/public/export_#{class_name}_#{Time.now.to_s}.xls"
|
79
|
+
xl.serialize(file_location)
|
80
|
+
file_location
|
81
|
+
end
|
51
82
|
end
|
52
83
|
end
|
data/lib/spotlight_search.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'spotlight_search/engine'
|
2
2
|
require 'spotlight_search/version'
|
3
3
|
require 'spotlight_search/exportable_columns'
|
4
|
+
require 'spotlight_search/exportable_columns_v2'
|
5
|
+
require 'spotlight_search/utils'
|
4
6
|
require 'spotlight_search/railtie' if defined?(Rails)
|
5
7
|
require 'active_support'
|
6
8
|
require 'active_support/rails'
|
@@ -10,6 +12,13 @@ module SpotlightSearch
|
|
10
12
|
|
11
13
|
autoload :Exceptions, 'spotlight_search/exceptions'
|
12
14
|
|
15
|
+
def self.setup
|
16
|
+
yield self
|
17
|
+
end
|
18
|
+
|
19
|
+
mattr_accessor :exportable_columns_version
|
20
|
+
@@exportable_columns_version = :v1
|
21
|
+
|
13
22
|
module ClassMethods
|
14
23
|
def filter_by(page, filter_params = {}, sort_params = {})
|
15
24
|
filtered_result = OpenStruct.new
|
@@ -28,35 +28,30 @@ module SpotlightSearch
|
|
28
28
|
# export_columns enabled: true, except: [:created_at, :updated_at]
|
29
29
|
# end
|
30
30
|
#
|
31
|
-
def export_columns(enabled: false, only: nil, except: nil)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
self.enabled_columns = self.enabled_columns - except
|
52
|
-
end
|
53
|
-
else
|
54
|
-
self.export_enabled = false
|
55
|
-
self.enabled_columns = nil
|
56
|
-
end
|
31
|
+
def export_columns(enabled: false, only: nil, except: nil, associated: nil)
|
32
|
+
ActiveRecord::Base.connection.migration_context.needs_migration? && return
|
33
|
+
return unless enabled
|
34
|
+
|
35
|
+
self.export_enabled = true
|
36
|
+
all_columns = self.column_names.map(&:to_sym)
|
37
|
+
if only.present?
|
38
|
+
unless (valid_columns = only & all_columns).size == only.size
|
39
|
+
invalid_columns = only - valid_columns
|
40
|
+
raise SpotlightSearch::Exceptions::InvalidColumns, invalid_columns
|
41
|
+
end
|
42
|
+
self.enabled_columns = only
|
43
|
+
else
|
44
|
+
self.enabled_columns = all_columns
|
45
|
+
end
|
46
|
+
if except.present?
|
47
|
+
unless (valid_columns = except & all_columns).size == except.size
|
48
|
+
invalid_columns = except - valid_columns
|
49
|
+
raise SpotlightSearch::Exceptions::InvalidColumns, invalid_columns
|
57
50
|
end
|
58
|
-
|
51
|
+
self.enabled_columns = self.enabled_columns - except
|
59
52
|
end
|
53
|
+
rescue ActiveRecord::NoDatabaseError
|
54
|
+
Rails.logger.info("No database error")
|
60
55
|
end
|
61
56
|
|
62
57
|
# Validates whether the selected columns are allowed for export
|
@@ -72,8 +67,8 @@ module SpotlightSearch
|
|
72
67
|
end
|
73
68
|
|
74
69
|
included do
|
75
|
-
class_attribute :enabled_columns, instance_accessor: false
|
76
|
-
class_attribute :export_enabled, instance_accessor: false
|
70
|
+
class_attribute :enabled_columns, instance_accessor: false, default: nil
|
71
|
+
class_attribute :export_enabled, instance_accessor: false, default: false
|
77
72
|
end
|
78
73
|
end
|
79
74
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module SpotlightSearch
|
2
|
+
module ExportableColumnsV2
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# Enables or disables export and specifies which all columns can be
|
7
|
+
# exported. For enabling export for all columns in all models
|
8
|
+
#
|
9
|
+
# class ApplicationRecord < ActiveRecord::Base
|
10
|
+
# export_columns enabled: true
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# For disabling export for only specific models
|
14
|
+
#
|
15
|
+
# class Person < ActiveRecord::Base
|
16
|
+
# export_columns enabled: false
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# For allowing export for only specific columns in a model
|
20
|
+
#
|
21
|
+
# class Person < ActiveRecord::Base
|
22
|
+
# export_columns enabled: true, only: [:created_at, :updated_at]
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# For excluding only specific columns and allowing all others
|
26
|
+
#
|
27
|
+
# class Person < ActiveRecord::Base
|
28
|
+
# export_columns enabled: true, except: [:created_at, :updated_at]
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
def export_columns(*record_fields, **associated_fields)
|
32
|
+
ActiveRecord::Base.connection.migration_context.needs_migration? && return
|
33
|
+
|
34
|
+
# Check that all record fields are valid accessible. Error if it doesn't.
|
35
|
+
# for each association, check that if its a valid association, and take the recursive step with that association
|
36
|
+
# End result is setting up in self, enabled columns and enabled associated columns
|
37
|
+
columns_hash = _model_exportable_columns(self, *record_fields, **associated_fields)
|
38
|
+
|
39
|
+
raise SpotlightSearch::Exceptions::InvalidColumns, columns_hash[:invalid_columns] if columns_hash[:invalid_columns].size.positive?
|
40
|
+
self.enabled_columns = [*record_fields, **associated_fields]
|
41
|
+
rescue ActiveRecord::NoDatabaseError
|
42
|
+
Rails.logger.info("No database error")
|
43
|
+
end
|
44
|
+
|
45
|
+
def _model_exportable_columns(klass, *record_fields, **associated_fields)
|
46
|
+
# Gets all the valid columns of a model
|
47
|
+
# If any column is invalid, it also returns it
|
48
|
+
raise SpotlightSearch::Exceptions::InvalidValue, "Expected ActiveRecord::Base, Received #{klass}" unless klass < ActiveRecord::Base
|
49
|
+
|
50
|
+
valid_columns = []
|
51
|
+
invalid_columns = []
|
52
|
+
|
53
|
+
# Base case: verify all the columns that belong to this record
|
54
|
+
record_fields.each do |field|
|
55
|
+
klass.new.respond_to?(field) ? valid_columns << field : invalid_columns << field
|
56
|
+
end
|
57
|
+
|
58
|
+
# Recursive case: check all associations and verify that they are all valid too
|
59
|
+
associated_fields.each do |association, association_record_fields|
|
60
|
+
reflection = klass.reflect_on_association(association)
|
61
|
+
invalid_columns << association && next unless reflection # Add whole association to invalid columns if it doesn't exist
|
62
|
+
|
63
|
+
case reflection
|
64
|
+
when ActiveRecord::Reflection::BelongsToReflection, ActiveRecord::Reflection::HasOneReflection
|
65
|
+
if reflection.polymorphic?
|
66
|
+
# We cannot process them further, so we'll assume it works and call it a day
|
67
|
+
valid_columns << { association => association_record_fields }
|
68
|
+
else
|
69
|
+
columns_hash = _model_exportable_columns(reflection.klass, *association_record_fields)
|
70
|
+
valid_columns << { association => columns_hash[:valid_columns] }
|
71
|
+
invalid_columns << { association => columns_hash[:invalid_columns] } if columns_hash[:invalid_columns].size.positive?
|
72
|
+
end
|
73
|
+
else
|
74
|
+
# one to many relationshops cannot be supported
|
75
|
+
invalid_columns << association
|
76
|
+
next
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# return all the valid and invalid columns in a hash
|
81
|
+
{
|
82
|
+
valid_columns: valid_columns,
|
83
|
+
invalid_columns: invalid_columns
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Validates whether the selected columns are allowed for export
|
88
|
+
def validate_exportable_columns(columns)
|
89
|
+
unless columns.is_a?(Array)
|
90
|
+
raise SpotlightSearch::Exceptions::InvalidValue, 'Expected Array. Invalid type received'
|
91
|
+
end
|
92
|
+
|
93
|
+
true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
included do
|
98
|
+
class_attribute :enabled_columns, instance_accessor: false, default: nil
|
99
|
+
class_attribute :enabled_associated_columns, instance_accessor: false, default: nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -48,9 +48,14 @@ module SpotlightSearch
|
|
48
48
|
tag.div class: "modal-body" do
|
49
49
|
form_tag '/spotlight_search/export_to_file', id: 'export-to-file-form', style: "width: 100%;" do
|
50
50
|
concat hidden_field_tag 'email', email, id: 'export-to-file-email'
|
51
|
-
concat hidden_field_tag 'filters', nil, id: 'export-to-file-filters'
|
51
|
+
concat hidden_field_tag 'filters', nil, id: 'export-to-file-filters' # Filters are not being sent
|
52
52
|
concat hidden_field_tag 'klass', klass.to_s, id: 'export-to-file-klass'
|
53
|
-
|
53
|
+
case SpotlightSearch.exportable_columns_version
|
54
|
+
when :v1
|
55
|
+
concat checkbox_row(klass)
|
56
|
+
when :v2
|
57
|
+
concat checkbox_row_v2(klass)
|
58
|
+
end
|
54
59
|
concat submit_tag 'Export as excel', class: 'btn btn-bordered export-to-file-btn'
|
55
60
|
end
|
56
61
|
end
|
@@ -67,7 +72,22 @@ module SpotlightSearch
|
|
67
72
|
def create_checkbox(column_name)
|
68
73
|
tag.div class: "col-md-4" do
|
69
74
|
concat check_box_tag "columns[]", column_name.to_s
|
70
|
-
concat column_name.to_s
|
75
|
+
concat column_name.to_s.humanize
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def checkbox_row_v2(klass)
|
80
|
+
tag.div class: "row" do
|
81
|
+
SpotlightSearch::Utils.serialize_csv_columns(*klass.enabled_columns).each do |column_path|
|
82
|
+
concat create_checkbox_v2(column_path)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_checkbox_v2(column_path)
|
88
|
+
tag.div class: "col-md-4" do
|
89
|
+
concat check_box_tag "columns[]", column_path
|
90
|
+
concat column_path.to_s.split('/').join('_').humanize
|
71
91
|
end
|
72
92
|
end
|
73
93
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module SpotlightSearch::Utils
|
4
|
+
class << self
|
5
|
+
def serialize_csv_columns(*columns, **hashes)
|
6
|
+
# Turns an arbitrary list of args and kwargs into a list of params to be used in a form
|
7
|
+
# For example, turns SpotlightSearch::Utils.serialize_csv_columns(:a, :b, c: [:d, e: :h], f: :g)
|
8
|
+
# into [:a, :b, "c/d", "c/e/h", "f/g"]
|
9
|
+
columns + hashes.map do |key, value|
|
10
|
+
serialize_csv_columns(*value).map do |column|
|
11
|
+
"#{key}/#{column}"
|
12
|
+
end
|
13
|
+
end.reduce([], :+)
|
14
|
+
end
|
15
|
+
|
16
|
+
def deserialize_csv_columns(list, method)
|
17
|
+
# Does the opposite operation of the above
|
18
|
+
list.reduce(recursive_hash) do |acc, item|
|
19
|
+
tokens = item.to_s.split('/')
|
20
|
+
send(method, acc, tokens.shift, tokens)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def base(hash, key, tokens) # recursive
|
25
|
+
hash.empty? && hash = { columns: [], associations: recursive_hash }
|
26
|
+
if tokens.empty?
|
27
|
+
# base case
|
28
|
+
hash[:columns] << key
|
29
|
+
else
|
30
|
+
# recursive case
|
31
|
+
# hash[:associations] ||= {}
|
32
|
+
hash[:associations][key] = base(hash[:associations][key], tokens.shift, tokens)
|
33
|
+
end
|
34
|
+
hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def as_json_params(hash, key, tokens)
|
38
|
+
hash.empty? && hash = { only: [], methods: [], include: recursive_hash }
|
39
|
+
if tokens.empty?
|
40
|
+
# base case
|
41
|
+
hash[:methods] << key
|
42
|
+
else
|
43
|
+
# recursive case
|
44
|
+
# hash[:associations] ||= {}
|
45
|
+
hash[:include][key] = as_json_params(hash[:include][key], tokens.shift, tokens)
|
46
|
+
end
|
47
|
+
hash
|
48
|
+
end
|
49
|
+
|
50
|
+
def recursive_hash
|
51
|
+
func = ->(h, k) { h[k] = Hash.new(&func) }
|
52
|
+
# This hash creates a new hash, infinitely deep, whenever a value is not found
|
53
|
+
# recursive_hash[:a][:b][:c][:d] will never fail
|
54
|
+
Hash.new(&func)
|
55
|
+
end
|
56
|
+
|
57
|
+
def flatten_hash(hash, prefix="", separator="_")
|
58
|
+
hash.reduce({}) do |acc, item|
|
59
|
+
case item[1]
|
60
|
+
when Hash
|
61
|
+
acc.merge(flatten_hash(item[1], "#{prefix}#{item[0]}#{separator}"))
|
62
|
+
else
|
63
|
+
acc.merge("#{prefix}#{item[0]}" => item[1])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spotlight_search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anbazhagan Palani
|
@@ -123,8 +123,10 @@ files:
|
|
123
123
|
- lib/spotlight_search/engine.rb
|
124
124
|
- lib/spotlight_search/exceptions.rb
|
125
125
|
- lib/spotlight_search/exportable_columns.rb
|
126
|
+
- lib/spotlight_search/exportable_columns_v2.rb
|
126
127
|
- lib/spotlight_search/helpers.rb
|
127
128
|
- lib/spotlight_search/railtie.rb
|
129
|
+
- lib/spotlight_search/utils.rb
|
128
130
|
- lib/spotlight_search/version.rb
|
129
131
|
- spotlight_search.gemspec
|
130
132
|
homepage: https://github.com/commutatus/spotlight-search
|