simple_drilldown 0.3.5 → 0.6.3

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: 388fda7e00b8742770fafabfe6742b0fa910bd6055ba7dc8dfe323c46a2bca74
4
- data.tar.gz: a765fbd6f39829ecf2ac58c40b44e48095f502530f30a26b75e3d6fed0513538
3
+ metadata.gz: 32063b0fd8dec6ee846e38eb37753d795acb2c6f3cca5d98a45d6c6a9c9bd868
4
+ data.tar.gz: 4c95e97c4780f82a6227a0f5e2048c7501d29e5534e7c74953746be915f42b1d
5
5
  SHA512:
6
- metadata.gz: 49e848195ec11d171b4968f5645d15805241acb3dfed17a9df221204324d320e238dd70c23234c506282f279afeabcd01277340417a35d5dc3495c1ecbf59305
7
- data.tar.gz: dfa6461733db75343fb7c8b4199c08b83bfa49fb22e9a8babadba6ec30a5aa8b9317c91701e817b6635736438aff4e83923d50bfc22a6f61f733a0bd4cb38b46
6
+ metadata.gz: b78736b952b5c7727ea627d83a4c47a72cf0665fded104fd6f65bb0f0adfc8b09a6899c4b832ce3a05991e05be2be8496ba6935ac24bc618af4492f7b997833b
7
+ data.tar.gz: e4f976920fcd12bd3820c390b22dd0d36af9a13f6d3f01517acb82574879d0196f4fc6fe7417a91a5a916a064d3187bc115119bfa1f16ea8954d53e9ee61b159
data/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
  <img align="right" src="https://travis-ci.org/DatekWireless/simple_drilldown.svg?branch=master" alt="Build Status">
5
5
  </a>
6
6
 
7
- simple_drilldown offers a simple way to define axis to filter and group records
7
+ `simple_drilldown` offers a simple way to define axis to filter and group records
8
8
  for analysis. The result is a record count for the selected filter and
9
- distribution and the option to list the actual records.
9
+ distribution and the option to list and export the actual records.
10
10
 
11
11
  ## Usage
12
12
 
@@ -16,6 +16,10 @@ For a given schema:
16
16
 
17
17
  ```ruby
18
18
  ActiveRecord::Schema.define(version: 20141204155251) do
19
+ create_table "users" do |t|
20
+ t.string "name", limit: 16, null: false
21
+ end
22
+
19
23
  create_table "posts" do |t|
20
24
  t.string "title", null: false
21
25
  t.text "body", null: false
@@ -25,12 +29,9 @@ ActiveRecord::Schema.define(version: 20141204155251) do
25
29
  t.datetime "updated_at", null: false
26
30
  end
27
31
 
28
- create_table "users" do |t|
29
- t.string "name", limit: 16, null: false
30
- end
31
-
32
32
  create_table "comments" do |t|
33
33
  t.integer "post_id", null: false
34
+ t.integer "user_id", null: false
34
35
  t.string "title", null: false
35
36
  t.text "body", null: false
36
37
  t.integer "rating", null: false
@@ -65,6 +66,8 @@ end
65
66
  Create a new controller to focus on posts. Each drilldown controller focuses on
66
67
  one main entity.
67
68
 
69
+ bin/rails g drilldown_controller User
70
+
68
71
  ```ruby
69
72
  class PostsDrilldownController < DrilldownController
70
73
 
@@ -37,7 +37,7 @@
37
37
  <% (0..2).each do |i|
38
38
  options = [['', '']]
39
39
  options << [@dimensions[i][:pretty_name], @dimensions[i][:url_param_name]] if @dimensions[i]
40
- options += @remaining_dimensions.keys.map { |name| [@dimension_defs[name][:pretty_name], name] } %>
40
+ options += @remaining_dimensions.keys.map { |name| [controller.c_dimension_defs[name][:pretty_name], name] } %>
41
41
  <%= t(i == 0 ? :group_by : :then_by) %>:
42
42
  <%= form.select 'dimensions', options, { :selected => @search.dimensions && @search.dimensions[i] },
43
43
  { :onChange => 'form.submit()', :name => 'search[dimensions][]', :id => "search_dimensions_#{i}" } %>
@@ -1,4 +1,3 @@
1
- <% dimension = @dimension_defs[dimension_name] %>
2
1
  <% if dimension_name == 'calendar_date' %>
3
2
  <% dates = [*@search.filter[dimension_name]] %>
4
3
  <tr>
@@ -6,7 +6,7 @@
6
6
  </style>
7
7
 
8
8
  <% @transaction_fields.each do | field | %>
9
- <%=form.label "fields[#{field}]", t(field), :class => "field_label" %>
9
+ <%=form.label "fields[#{field}]", t(field, default: :"attributes.#{field}"), :class => "field_label" %>
10
10
  <%=form.check_box :fields, :id => "search_fields[#{field}]", :name => "search[fields][#{field}]", :checked => @search.fields.include?(field) %>
11
11
  <br/>
12
12
  <% end %>
@@ -3,18 +3,8 @@
3
3
  <%= form.text_field :title, class: 'form-control' %>
4
4
  </div>
5
5
 
6
- <% choices = Concurrent::Hash.new %>
7
- <% threads = @dimension_defs.keys.map do |dimension_name| %>
8
- <% dimension = @dimension_defs[dimension_name] %>
9
- <% t = Thread.start do |thread| %>
10
- <% ActiveRecord::Base.connection_pool.with_connection do %>
11
- <% choices[dimension_name] = [[t(:all), nil]] + (dimension[:legal_values] && dimension[:legal_values].call(@search).map { |o| o.is_a?(Array) ? [o[0].to_s, o[1].to_s] : o.to_s } || []) %>
12
- <% end %>
13
- <% end %>
14
- <% next [t, dimension_name] %>
15
- <% end %>
16
- <% threads.each do |t, dimension_name| %>
17
- <% t.join %>
18
- <%= render partial: 'drilldown/field', locals: {choices: choices[dimension_name] || [],
19
- form: form, dimension_name: dimension_name} %>
6
+ <% controller.c_dimension_defs.each do |dimension_name, dimension| %>
7
+ <% choices = [[t(:all), nil]] + (dimension[:legal_values] && dimension[:legal_values].call(@search).map { |o| o.is_a?(Array) ? [o[0].to_s, o[1].to_s] : o.to_s } || []) %>
8
+ <%= render partial: 'drilldown/field', locals: { choices: choices || [],
9
+ form: form, dimension_name: dimension_name } %>
20
10
  <% end %>
@@ -1,10 +1,10 @@
1
1
  <% unless result[:transactions].empty? %>
2
2
  <tr>
3
- <td colspan="<%=@summary_fields.size + 1%>">
3
+ <td colspan="<%= controller.c_summary_fields.size + 1 %>">
4
4
  <table class="table table-condensed table-bordered" style="padding-bottom: 10px;">
5
- <%=render :partial => '/drilldown/row_header' %>
5
+ <%= render :partial => '/drilldown/row_header' %>
6
6
  <% result[:transactions].each do |t| %>
7
- <%=render :partial => '/drilldown/row', :locals => {:transaction => t, :previous_transaction => nil, :errors => [], :error_row => false, :meter1_errors => false} %>
7
+ <%= render :partial => '/drilldown/row', :locals => { :transaction => t, :previous_transaction => nil, :errors => [], :error_row => false, :meter1_errors => false } %>
8
8
  <% end %>
9
9
  </table>
10
10
  </td>
@@ -1,13 +1,13 @@
1
1
  <tr valign="top">
2
2
  <% @search.fields.each do |field| %>
3
- <td>
4
- <% if field == 'time' %>
5
- <%= (transaction.respond_to?(:completed_at) ? transaction.completed_at : transaction.created_at).localtime.strftime('%Y-%m-%d %H:%M') %>
6
- <% else %>
7
- <% field_def = @transaction_fields_map[field.to_sym] %>
8
- <%= field_def[:attr_method] ? field_def[:attr_method].call(transaction) : transaction.send(field) %>
9
- <% end %>
10
- </td>
3
+ <td>
4
+ <% if field == 'time' %>
5
+ <%= (transaction.respond_to?(:completed_at) ? transaction.completed_at : transaction.created_at).localtime.strftime('%Y-%m-%d %H:%M') %>
6
+ <% else %>
7
+ <% field_def = controller.c_fields[field.to_sym] %>
8
+ <%= field_def[:attr_method] ? field_def[:attr_method].call(transaction) : transaction.send(field) %>
9
+ <% end %>
10
+ </td>
11
11
  <% end %>
12
- <td><%= detour_to t(:show), transaction %></td>
12
+ <td><%= link_to t(:show), transaction %></td>
13
13
  </tr>
@@ -1,7 +1,7 @@
1
1
  <thead>
2
2
  <tr>
3
3
  <% @search.fields.each do |field| %>
4
- <th><%=t field %></th>
4
+ <th><%=t field, default: :"attributes.#{field}" %></th>
5
5
  <% end %>
6
6
  <th></th>
7
7
  </tr>
@@ -3,8 +3,8 @@
3
3
  <% @dimensions.each do |d| %>
4
4
  <th><%=h d[:pretty_name]%></th>
5
5
  <% end %>
6
- <th><%= t @target_class.table_name.capitalize %></th>
7
- <%= @summary_fields.map{|l| "<th>#{t(l)}</th>"}.join("\n").html_safe %>
6
+ <th><%= t controller.c_target_class.table_name.capitalize %></th>
7
+ <%= controller.c_summary_fields.map{|l| "<th>#{t(l)}</th>"}.join("\n").html_safe %>
8
8
  </tr>
9
9
 
10
10
  <%=summary_row(@result) %>
@@ -45,7 +45,7 @@ xml.Workbook(
45
45
  @transactions.each do |transaction|
46
46
  xml.Row do
47
47
  @transaction_fields.each do |field|
48
- field_map = @transaction_fields_map[field.to_sym]
48
+ field_map = controller.c_fields[field.to_sym]
49
49
  if field == 'time'
50
50
  xml.Cell 'ss:StyleID' => 'DateOnlyFormat' do
51
51
  xml.Data transaction.completed_at.gmtime.xmlschema, 'ss:Type' => 'DateTime'
@@ -6,3 +6,4 @@ Example:
6
6
 
7
7
  This will create:
8
8
  app/controllers/thing_drilldown_controller.rb
9
+ test/controllers/thing_drilldown_controller_test.rb
@@ -5,6 +5,7 @@ class DrilldownControllerGenerator < Rails::Generators::NamedBase
5
5
 
6
6
  def copy_drilldown_controller_file
7
7
  template 'drilldown_controller.rb.erb', "app/controllers/#{file_name}_drilldown_controller.rb"
8
- route "resources(:#{singular_name}_drilldown, only: :index){collection{get :excel_export;get :html_export}}"
8
+ template 'drilldown_controller_test.rb.erb', "test/controllers/#{file_name}_drilldown_controller_test.rb"
9
+ route "draw_drilldown :#{singular_name}_drilldown"
9
10
  end
10
11
  end
@@ -1,14 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class <%= class_name %>DrilldownController < DrilldownController
4
- # What fields should be displayed as default when listing actual <%= class_name %> records.
5
- default_fields %w[created_at updated_at] # TODO(uwe): Read fields from schema?
3
+ require 'simple_drilldown/controller'
6
4
 
5
+ class <%= class_name %>DrilldownController < SimpleDrilldown::Controller
7
6
  # The main focus of the drilldown
8
- target_class <%= class_name %>
7
+ # target_class <%= class_name %>
8
+
9
+ # `where` clause for the base line
10
+ # base_condition '1=1'
9
11
 
10
12
  # How should we count the reords?
11
- select 'count(*) as count'
13
+ # select 'count(*) as count'
14
+
15
+ # When selecting records, what relations should be included for optimization?
16
+ # Other relations can be included for specific dimensions and fields.
17
+ # base_includes :user, :comments # TODO(uwe): Read relations from schema?
18
+
19
+ # What fields should be displayed as default when listing actual <%= class_name %> records.
20
+ default_fields %w[created_at updated_at] # TODO(uwe): Read fields from schema?
12
21
 
13
22
  # When listing records, what relations should be included for optimization?
14
23
  # list_includes :user, :comments # TODO(uwe): Read relations from schema?
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class <%= class_name %>DrilldownControllerTest < ActionDispatch::IntegrationTest
6
+ test 'should get index' do
7
+ get <%= singular_name %>_drilldown_url
8
+ assert_response :success
9
+ end
10
+
11
+ test 'should get index with list' do
12
+ get <%= singular_name %>_drilldown_url search: { list: 1 }
13
+ assert_response :success
14
+ end
15
+ end
@@ -1,54 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'simple_drilldown/drilldown_helper'
3
+ require 'simple_drilldown/helper'
4
4
  require 'simple_drilldown/search'
5
5
 
6
6
  module SimpleDrilldown
7
- class DrilldownController < ::ApplicationController
8
- helper DrilldownHelper
7
+ class Controller < ::ApplicationController
8
+ helper Helper
9
9
 
10
10
  LIST_LIMIT = 10_000
11
11
 
12
+ class_attribute :c_base_condition, default: '1=1'
13
+ class_attribute :c_base_group, default: []
14
+ class_attribute :c_base_includes, default: []
15
+ class_attribute :c_default_fields, default: []
16
+ class_attribute :c_default_select_value, default: SimpleDrilldown::Search::SelectValue::COUNT
17
+ class_attribute :c_dimension_defs
18
+ class_attribute :c_fields
19
+ class_attribute :c_list_includes, default: []
20
+ class_attribute :c_list_order
21
+ class_attribute :c_select, default: 'count(*) as count'
22
+ class_attribute :c_summary_fields, default: []
23
+ class_attribute :c_target_class
24
+
12
25
  class << self
26
+ def inherited(base)
27
+ super
28
+ base.c_dimension_defs = Concurrent::Hash.new
29
+ base.c_fields = {}
30
+ begin
31
+ base.c_target_class = base.name.chomp('DrilldownController').constantize
32
+ rescue NameError
33
+ begin
34
+ base.c_target_class = base.name.chomp('Controller').constantize
35
+ rescue NameError
36
+ end
37
+ end
38
+ end
39
+
13
40
  def base_condition(base_condition)
14
- @@base_condition = base_condition
41
+ self.c_base_condition = base_condition
15
42
  end
16
43
 
17
44
  def base_includes(base_includes)
18
- @@base_includes = base_includes
45
+ self.c_base_includes = base_includes
19
46
  end
20
47
 
21
48
  def base_group(base_group)
22
- @@base_group = base_group
49
+ self.c_base_group = base_group
50
+ end
51
+
52
+ def default_fields(default_fields)
53
+ self.c_default_fields = default_fields.flatten
23
54
  end
24
55
 
25
- def default_fields(fields)
26
- @@default_fields = fields
56
+ def default_select_value(default_select_value)
57
+ self.c_default_select_value = default_select_value
27
58
  end
28
59
 
29
60
  def target_class(target_class)
30
- @@target_class = target_class
61
+ self.c_target_class = target_class
31
62
  end
32
63
 
33
64
  def select(select)
34
- @@select = select
65
+ self.c_select = select
35
66
  end
36
67
 
37
68
  def list_includes(list_includes)
38
- @@list_includes = list_includes
69
+ self.c_list_includes = list_includes.flatten
39
70
  end
40
71
 
41
72
  def list_order(list_order)
42
- @@list_order = list_order
73
+ self.c_list_order = list_order
43
74
  end
44
75
 
45
76
  def field(name, **options)
46
- @@fields ||= {}
47
- @@fields[name] = options
77
+ c_fields[name] = options
48
78
  end
49
79
 
50
80
  def summary_fields(*summary_fields)
51
- @@summary_fields = summary_fields
81
+ self.c_summary_fields = summary_fields.flatten
52
82
  end
53
83
 
54
84
  def dimension(name, select_expression = name, options = {})
@@ -74,15 +104,20 @@ module SimpleDrilldown
74
104
  raise "Unknown options: #{query_opts.keys.inspect}" unless (query_opts.keys - %i[select includes where]).empty?
75
105
  end
76
106
 
77
- @@dimension_defs ||= Concurrent::Hash.new
78
-
79
- @@dimension_defs[name.to_s] = {
80
- includes: queries.inject([]) do |a, e|
107
+ c_dimension_defs[name.to_s] = {
108
+ includes: queries.inject(nil) do |a, e|
81
109
  i = e[:includes]
82
110
  next a unless i
83
- next a if a.include?(i)
111
+ next a if a&.include?(i)
84
112
 
85
- a + [i]
113
+ case a
114
+ when nil
115
+ i
116
+ when Symbol
117
+ [a, *i]
118
+ else
119
+ a.concat(*i)
120
+ end
86
121
  end,
87
122
  interval: interval,
88
123
  label_method: label_method,
@@ -101,7 +136,7 @@ module SimpleDrilldown
101
136
  my_filter = search.filter.dup
102
137
  my_filter.delete(field.to_s) unless preserve_filter
103
138
  filter_conditions, _t, includes = make_conditions(my_filter)
104
- dimension_def = @@dimension_defs[field.to_s]
139
+ dimension_def = c_dimension_defs[field.to_s]
105
140
  result_sets = dimension_def[:queries].map do |query|
106
141
  if query[:includes]
107
142
  if query[:includes].is_a?(Array)
@@ -111,11 +146,11 @@ module SimpleDrilldown
111
146
  end
112
147
  includes.uniq!
113
148
  end
114
- rows = @@target_class.unscoped.where(@@base_condition)
149
+ rows = c_target_class.unscoped.where(c_base_condition)
115
150
  .select("#{query[:select]} AS value")
116
- .where(filter_conditions)
117
- .where(query[:where])
118
- .joins(make_join([], @@target_class.name.underscore.to_sym, includes))
151
+ .where(filter_conditions || '1=1')
152
+ .where(query[:where] || '1=1')
153
+ .joins(make_join([], c_target_class.name.underscore.to_sym, includes))
119
154
  .order('value')
120
155
  .group(:value)
121
156
  .to_a
@@ -137,14 +172,14 @@ module SimpleDrilldown
137
172
  end
138
173
 
139
174
  def make_conditions(search_filter)
140
- includes = @@base_includes.dup
175
+ includes = c_base_includes.dup
141
176
  if search_filter
142
177
  condition_strings = []
143
178
  condition_values = []
144
179
 
145
180
  filter_texts = []
146
181
  search_filter.each do |field, values|
147
- dimension_def = @@dimension_defs[field]
182
+ dimension_def = c_dimension_defs[field]
148
183
  raise "Unknown filter field: #{field.inspect}" if dimension_def.nil?
149
184
 
150
185
  values = [*values]
@@ -202,9 +237,9 @@ module SimpleDrilldown
202
237
  when Hash
203
238
  sql = +''
204
239
  include.each do |parent, child|
205
- sql << make_join(joins, model, parent) + ' '
240
+ sql << ' ' + make_join(joins, model, parent)
206
241
  ass = model.to_s.camelize.constantize.reflect_on_association parent
207
- sql << make_join(joins, parent, child, ass.class_name.constantize)
242
+ sql << ' ' + make_join(joins, parent, child, ass.class_name.constantize)
208
243
  end
209
244
  sql
210
245
  when Symbol
@@ -254,24 +289,7 @@ module SimpleDrilldown
254
289
 
255
290
  def initialize
256
291
  super()
257
- @fields = @@fields
258
- @default_fields = @@default_fields
259
- @default_select_value = SimpleDrilldown::Search::SelectValue::COUNT
260
- @target_class = @@target_class
261
- @select = @@select
262
- @@base_condition = '1 = 1' unless defined?(@@base_condition)
263
- @base_condition = @@base_condition
264
- @@base_includes = [] unless defined?(@@base_includes)
265
- @base_includes = @@base_includes
266
- @base_group = defined?(@@base_group) ? @@base_group : []
267
- @@list_includes = [] unless defined?(@@list_includes)
268
- @list_includes = @@list_includes
269
- @list_order = @@list_order
270
- @dimension_defs = @@dimension_defs
271
- @@summary_fields = [] unless defined?(@@summary_fields)
272
- @summary_fields = @@summary_fields
273
-
274
- @history_fields = @fields.select { |_k, v| v[:list_change_times] }.map { |k, _v| k.to_s }
292
+ @history_fields = c_fields.select { |_k, v| v[:list_change_times] }.map { |k, _v| k.to_s }
275
293
  end
276
294
 
277
295
  # ?dimension[0]=supplier&dimension[1]=transaction_type&
@@ -279,18 +297,17 @@ module SimpleDrilldown
279
297
  def index(do_render = true)
280
298
  @search = new_search_object
281
299
 
282
- @transaction_fields = (@search.fields + (@fields.keys.map(&:to_s) - @search.fields))
283
- @transaction_fields_map = @fields
300
+ @transaction_fields = (@search.fields + (c_fields.keys.map(&:to_s) - @search.fields))
284
301
 
285
- select = @select.dup
286
- includes = @base_includes.dup
302
+ select = c_select.dup
303
+ includes = c_base_includes.dup
287
304
 
288
305
  @dimensions = []
289
- select << ", 'All'::text as value0"
306
+ select << ", 'All'#{'::text' if c_target_class.connection.adapter_name == 'PostgreSQL'} as value0"
290
307
  @dimensions += @search.dimensions.map do |dn|
291
- raise "Unknown distribution field: #{dn.inspect}" if @dimension_defs[dn].nil?
308
+ raise "Unknown distribution field: #{dn.inspect}" if c_dimension_defs[dn].nil?
292
309
 
293
- @dimension_defs[dn]
310
+ c_dimension_defs[dn]
294
311
  end
295
312
  @dimensions.each_with_index do |d, i|
296
313
  select << ", #{d[:select_expression]} as value#{i + 1}"
@@ -315,18 +332,18 @@ module SimpleDrilldown
315
332
  order = (1..@dimensions.size).map { |i| "value#{i}" }.join(',')
316
333
  order = nil if order.empty?
317
334
  end
318
- group = (@base_group + (1..@dimensions.size).map { |i| "value#{i}" }).join(',')
335
+ group = (c_base_group + (1..@dimensions.size).map { |i| "value#{i}" }).join(',')
319
336
  group = nil if group.empty?
320
337
 
321
- joins = self.class.make_join([], @target_class.name.underscore.to_sym, includes)
322
- rows = @target_class.unscoped.where(@base_condition).select(select).where(conditions)
323
- .joins(joins)
324
- .group(group)
325
- .order(order).to_a
338
+ joins = self.class.make_join([], c_target_class.name.underscore.to_sym, includes)
339
+ rows = c_target_class.unscoped.where(c_base_condition).select(select).where(conditions)
340
+ .joins(joins)
341
+ .group(group)
342
+ .order(order).to_a
326
343
 
327
344
  if rows.empty?
328
345
  @result = { value: 'All', count: 0, row_count: 0, nodes: 0, rows: [] }
329
- @summary_fields.each { |f| @result[f] = 0 }
346
+ c_summary_fields.each { |f| @result[f] = 0 }
330
347
  else
331
348
  if do_render && @search.list && rows.inject(0) { |sum, r| sum + r[:count].to_i } > LIST_LIMIT
332
349
  @search.list = false
@@ -335,9 +352,9 @@ module SimpleDrilldown
335
352
  @result = result_from_rows(rows, 0, 0, ['All'])
336
353
  end
337
354
 
338
- remove_duplicates(@result) unless @base_group.empty?
355
+ remove_duplicates(@result) unless c_base_group.empty?
339
356
 
340
- @remaining_dimensions = @dimension_defs.dup
357
+ @remaining_dimensions = c_dimension_defs.dup
341
358
  @remaining_dimensions.each_key do |dim_name|
342
359
  if (@search.filter[dim_name] && @search.filter[dim_name].size == 1) ||
343
360
  (@dimensions.any? { |d| d[:url_param_name] == dim_name })
@@ -352,9 +369,9 @@ module SimpleDrilldown
352
369
  def choices
353
370
  @search = new_search_object
354
371
  dimension_name = params[:dimension_name]
355
- dimension = @dimension_defs[dimension_name]
372
+ dimension = c_dimension_defs[dimension_name]
356
373
  selected = @search.filter[dimension_name] || []
357
- raise "Unknown dimension #{dimension_name.inspect}: #{@dimension_defs.keys.inspect}" unless dimension
374
+ raise "Unknown dimension #{dimension_name.inspect}: #{c_dimension_defs.keys.inspect}" unless dimension
358
375
 
359
376
  choices = [[t(:all), nil]] +
360
377
  (dimension[:legal_values]&.call(@search)&.map { |o| o.is_a?(Array) ? o[0..1].map(&:to_s) : o.to_s } || [])
@@ -398,7 +415,7 @@ module SimpleDrilldown
398
415
  private
399
416
 
400
417
  def new_search_object
401
- SimpleDrilldown::Search.new(params[:search]&.to_unsafe_h, @default_fields, @default_select_value)
418
+ SimpleDrilldown::Search.new(params[:search]&.to_unsafe_h, c_default_fields, c_default_select_value)
402
419
  end
403
420
 
404
421
  def remove_duplicates(result)
@@ -411,7 +428,7 @@ module SimpleDrilldown
411
428
  if prev_row
412
429
  if prev_row[:value] == r[:value]
413
430
  prev_row[:count] += r[:count]
414
- @summary_fields.each do |f|
431
+ c_summary_fields.each do |f|
415
432
  prev_row[f] += r[f]
416
433
  end
417
434
  prev_row[:row_count] = [prev_row[:row_count], r[:row_count]].max
@@ -449,7 +466,7 @@ module SimpleDrilldown
449
466
  row_count: 0,
450
467
  nodes: 0
451
468
  }
452
- @summary_fields.each { |f| sub_result[f] = 0 }
469
+ c_summary_fields.each { |f| sub_result[f] = 0 }
453
470
  sub_result[:rows] = add_zero_results([], dimension + 1) if dimension < @dimensions.size - 1
454
471
  result_rows << sub_result
455
472
  end
@@ -472,7 +489,7 @@ module SimpleDrilldown
472
489
  row_count: 1,
473
490
  nodes: @search.list ? 2 : 1
474
491
  }
475
- @summary_fields.each { |f| result[f] = row[f].to_i }
492
+ c_summary_fields.each { |f| result[f] = row[f].to_i }
476
493
  return result
477
494
  end
478
495
 
@@ -495,37 +512,75 @@ module SimpleDrilldown
495
512
  nodes: result_rows.inject(0) { |t, r| t + r[:nodes] } + 1,
496
513
  rows: result_rows
497
514
  }
498
- @summary_fields.each { |f| result[f] = result_rows.inject(0) { |t, r| t + r[f] } }
515
+ c_summary_fields.each { |f| result[f] = result_rows.inject(0) { |t, r| t + r[f] } }
499
516
  result
500
517
  end
501
518
 
502
519
  def populate_list(conditions, includes, result, values)
503
520
  if result[:rows]
504
- result[:rows].each do |r|
505
- populate_list(conditions, includes, r, values + [r[:value]])
521
+ return result[:rows].each { |r| populate_list(conditions, includes, r, values + [r[:value]]) }
522
+ end
523
+ list_includes = merge_includes(includes, c_list_includes)
524
+ @search.fields.each do |field|
525
+ field_def = c_fields[field.to_sym]
526
+ raise "Field definition missing for: #{field.inspect}" unless field_def
527
+
528
+ field_includes = field_def[:include]
529
+ if field_includes
530
+ list_includes = merge_includes(list_includes , field_includes)
506
531
  end
507
- else
508
- list_includes = includes + @list_includes
509
- @search.fields.each do |field|
510
- field_def = @transaction_fields_map[field.to_sym]
511
- raise "Field definition missing for: #{field.inspect}" unless field_def
512
-
513
- field_includes = field_def[:include]
514
- if field_includes
515
- list_includes += field_includes.is_a?(Array) ? field_includes : [field_includes]
532
+ end
533
+ if @search.list_change_times
534
+ @history_fields.each do |f|
535
+ if @search.fields.include? f
536
+ list_includes = merge_includes(list_includes, assignment: { order: :"#{f}_changes" } )
516
537
  end
517
538
  end
518
- list_includes.uniq!
519
- if @search.list_change_times
520
- @history_fields.each do |f|
521
- list_includes << { assignment: { order: :"#{f}_changes" } } if @search.fields.include? f
522
- end
539
+ end
540
+ joins = self.class.make_join([], c_target_class.name.underscore.to_sym, list_includes)
541
+ list_conditions = list_conditions(conditions, values)
542
+ base_query = c_target_class.unscoped.where(c_base_condition).joins(joins).order(c_list_order)
543
+ base_query = base_query.where(list_conditions) if list_conditions
544
+ result[:transactions] = base_query.to_a
545
+ end
546
+
547
+ def merge_includes(*args)
548
+ hash = hash_includes(*args)
549
+ result = hash.dup.map { |k, v|
550
+ if v.blank?
551
+ hash.delete(k)
552
+ k
553
+ end
554
+ }.compact
555
+ result << hash unless hash.blank?
556
+ case result.size
557
+ when 0
558
+ nil
559
+ when 1
560
+ result[0]
561
+ else
562
+ result
563
+ end
564
+ end
565
+
566
+ def hash_includes(*args)
567
+ args.inject({}) do |h, inc|
568
+ case inc
569
+ when Array
570
+ inc.each { |v|
571
+ h = hash_includes(h, v)
572
+ }
573
+ when Hash
574
+ inc.each { |k, v|
575
+ h[k] = merge_includes(h[k], v)
576
+ }
577
+ when NilClass, FalseClass
578
+ when String, Symbol
579
+ h[inc] ||= []
580
+ else
581
+ raise "Unknown include type: #{inc.inspect}"
523
582
  end
524
- joins = self.class.make_join([], @target_class.name.underscore.to_sym, list_includes)
525
- list_conditions = list_conditions(conditions, values)
526
- base_query = @target_class.unscoped.where(@base_condition).joins(joins).order(@list_order)
527
- base_query = base_query.where(list_conditions) if list_conditions
528
- result[:transactions] = base_query.to_a
583
+ h
529
584
  end
530
585
  end
531
586
 
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'chartkick'
4
+ require 'simple_drilldown/routing'
5
+
3
6
  module SimpleDrilldown
4
7
  class Engine < ::Rails::Engine
5
8
  isolate_namespace SimpleDrilldown
9
+ config.autoload_paths << File.dirname(__dir__)
6
10
 
7
11
  initializer 'simple_drilldown.assets.precompile' do |app|
8
12
  app.config.assets.precompile += %w[chartkick.js]
9
13
  end
14
+
15
+ ActionDispatch::Routing::Mapper.include SimpleDrilldown::Routing
10
16
  end
11
17
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module SimpleDrilldown
4
4
  # View helper for SimpleDrilldown
5
- module DrilldownHelper
5
+ module Helper
6
6
  def value_label(dimension_index, value)
7
7
  dimension = @dimensions[dimension_index]
8
8
  return nil if dimension.nil?
@@ -11,7 +11,7 @@ module SimpleDrilldown
11
11
  end
12
12
 
13
13
  def caption
14
- result = @search.title || "#{@target_class} #{t(@search.select_value.downcase)}" +
14
+ result = @search.title || "#{controller.c_target_class} #{t(@search.select_value.downcase)}" +
15
15
  (@dimensions && @dimensions.any? ? ' by ' + @dimensions.map { |d| d[:pretty_name] }.join(' and ') : '')
16
16
  result.gsub('$date', [*@search.filter[:calendar_date]].uniq.join(' - '))
17
17
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleDrilldown
4
+ # Routing helper methods
5
+ module Routing
6
+ def draw_drilldown(path, controller = path)
7
+ get "#{path}(.:format)" => "#{controller}#index", as: path
8
+ scope path do
9
+ %i[choices excel_export html_export index].each do |action|
10
+ get "#{action}(/:id)(.:format)", controller: controller, action: action
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module SimpleDrilldown
2
- VERSION = '0.3.5'
2
+ VERSION = '0.6.3'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_drilldown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uwe Kubosch
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-19 00:00:00.000000000 Z
11
+ date: 2020-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chartkick
@@ -123,10 +123,12 @@ files:
123
123
  - lib/generators/drilldown_controller/USAGE
124
124
  - lib/generators/drilldown_controller/drilldown_controller_generator.rb
125
125
  - lib/generators/drilldown_controller/templates/drilldown_controller.rb.erb
126
+ - lib/generators/drilldown_controller/templates/drilldown_controller_test.rb.erb
126
127
  - lib/simple_drilldown.rb
127
- - lib/simple_drilldown/drilldown_controller.rb
128
- - lib/simple_drilldown/drilldown_helper.rb
128
+ - lib/simple_drilldown/controller.rb
129
129
  - lib/simple_drilldown/engine.rb
130
+ - lib/simple_drilldown/helper.rb
131
+ - lib/simple_drilldown/routing.rb
130
132
  - lib/simple_drilldown/search.rb
131
133
  - lib/simple_drilldown/version.rb
132
134
  - lib/tasks/simple_drilldown_tasks.rake
@@ -135,7 +137,7 @@ licenses:
135
137
  - MIT
136
138
  metadata:
137
139
  allowed_push_host: https://rubygems.org/
138
- post_install_message:
140
+ post_install_message:
139
141
  rdoc_options: []
140
142
  require_paths:
141
143
  - lib
@@ -151,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
153
  version: '0'
152
154
  requirements: []
153
155
  rubygems_version: 3.1.2
154
- signing_key:
156
+ signing_key:
155
157
  specification_version: 4
156
158
  summary: Simple data warehouse and drilldown.
157
159
  test_files: []