spotlight_search 0.1.8 → 0.1.9

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
  SHA256:
3
- metadata.gz: 3e9cd4941331e8c05faf205f5e88b871d70281e28b537ff8884a873c4c039c01
4
- data.tar.gz: 65588a311e3dd0ca07e73064f65eb38dd92eaf21c525da71866a4621edf419bc
3
+ metadata.gz: 0d8c71e34af7024e41f5d6767ce6b9cc6a4466aed6882997d97b4f45e57c2500
4
+ data.tar.gz: bd7d756c813e0a0572dd4973e8f795ce5195740811460bd91fa595aafa556080
5
5
  SHA512:
6
- metadata.gz: e4e0877a70a4340f373fbcb5cad506bdb70311db3f04cb57a9ff8985f1b1321388e9e37f9109c2a262b798b450b233449b0401f1a8799ff76808733f8ec39499
7
- data.tar.gz: b7a088d2af85d00e71663cc50a37cacc149432f188e84907c33d138a2380ed705f0fb5fad8252c841d3f33d9736e0891c1b90f8e3d310ef05bb6c62a6e8d837b
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, current_user.class) %>
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 = create_excel(records, klass.name, columns)
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
@@ -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
@@ -1,7 +1,7 @@
1
1
  module SpotlightSearch
2
2
  module Exceptions
3
3
  class InvalidColumns < StandardError
4
- def initialize(columns: [])
4
+ def initialize(columns = [])
5
5
  message = 'Invalid columns found: ' + columns.map(&:to_s).join(', ')
6
6
  super(message)
7
7
  end
@@ -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
- begin
33
- unless ActiveRecord::Base.connection.migration_context.needs_migration?
34
- if enabled
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.new(nil, 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.new(nil, invalid_columns)
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
- rescue ActiveRecord::NoDatabaseError
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
- concat checkbox_row(klass)
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
@@ -1,3 +1,3 @@
1
1
  module SpotlightSearch
2
- VERSION = "0.1.8"
2
+ VERSION = "0.1.9"
3
3
  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.8
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