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