simple_drilldown 0.4.0 → 0.6.4

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: e6ff606b085525103af2a05ad54467a205226cde038b528474e751f2e2b080a0
4
- data.tar.gz: 73e8da613077c2ec5fb87ba80e2b2e8f9c6f72700b98de976f046002284b28f2
3
+ metadata.gz: c25058c9633cfe67926c2cbf3dca1105481fde8297077ba68414063ab239e05e
4
+ data.tar.gz: 5ff52f26970deef096886d62451ca80f545d3fbf88601ed20cc2ed3334c01397
5
5
  SHA512:
6
- metadata.gz: c2d6ebe228a5d6fa02bca708dbb83237e1ecd59ffdba56e75887275fb16656ed5c4aea4203ef6b265be5ee7b1c15b5b924796963c33f426ef50f6665d9c5a968
7
- data.tar.gz: 1d47c86fa8970a1258a7a34b4f546930f11b2c592ed6492c4c8d425283a7a5fb89cf2fe236bd50f04a7f2ee9030669ca876e33bee229dc55c06c536ecec8b850
6
+ metadata.gz: 478e1fa749626a6599055b82022553c5778f8ef4085fa513f28261c9340cb3a112235ff6968115f95de00d4c4af4ec6d9e9b721596edd03693249c5e299e0938
7
+ data.tar.gz: 2c4b94ae99c33ced23f149fbb0d0f5356867773a13e979619fe0c030ae83fbfac18444579b2b6b78427c48bca1e66a650455640a9d1fe9d95eb2c9629116a2ad
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
 
@@ -20,7 +20,7 @@
20
20
  case @search.display_type
21
21
  when SimpleDrilldown::Search::DisplayType::PIE
22
22
  %>
23
- <%= pie_chart data, height: '24rem' %>
23
+ <%= pie_chart data, height: '24rem', events: ['click'] %>
24
24
  <% when SimpleDrilldown::Search::DisplayType::BAR %>
25
25
  <%= column_chart data, height: '24rem' %>
26
26
  <% when SimpleDrilldown::Search::DisplayType::LINE %>
@@ -37,10 +37,10 @@
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
- { :onChange => 'form.submit()', :name => 'search[dimensions][]', :id => "search_dimensions_#{i}" } %>
43
+ { onChange: 'form.submit()', name: 'search[dimensions][]', id: "search_dimensions_#{i}" } %>
44
44
  <% end %>
45
45
 
46
46
  <br/>
@@ -60,3 +60,23 @@
60
60
  <%= form.check_box :list, { :onChange => 'form.submit()' } %>
61
61
  <%= form.label :list, t(:list) %>
62
62
  </div>
63
+
64
+ <% if @dimensions.size == 1 &&
65
+ [SimpleDrilldown::Search::DisplayType::BAR, SimpleDrilldown::Search::DisplayType::PIE].include?(@search.display_type) %>
66
+ <%= javascript_tag do %>
67
+ $('#chart-1').on('click', function(e){
68
+ chart = Chartkick.charts['chart-1'].getChartObject();
69
+ firstPoint = chart.getElementsAtEvent(e)[0]
70
+ if (firstPoint) {
71
+ label = chart.data.labels[firstPoint._index];
72
+ value = chart.data.datasets[firstPoint._datasetIndex].data[firstPoint._index];
73
+ console.log("Label: " + label + ", Value: " + value);
74
+ new_location = window.location.toString();
75
+ new_location = new_location.replace("&search[dimensions][]=<%= @dimensions[0][:url_param_name] %>", '');
76
+ new_location = new_location + '&search[filter][<%= @dimensions[0][:url_param_name] %>][]=' + label;
77
+ console.log(new_location);
78
+ window.location = new_location;
79
+ }
80
+ });
81
+ <% end %>
82
+ <% end %>
@@ -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>
@@ -23,7 +22,7 @@
23
22
  </tr>
24
23
  <% else %>
25
24
  <div class="form-group">
26
- <%= form.label "filter[#{dimension_name}]", t(dimension_name) %>
25
+ <%= form.label "filter[#{dimension_name}]", t(dimension_name, default: :"activerecord.models.#{dimension_name}") %>
27
26
  <%= select :search, :filter,
28
27
  choices,
29
28
  { :selected => @search.filter[dimension_name] },
@@ -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,20 +104,25 @@ 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,
89
124
  legal_values: legal_values,
90
- pretty_name: I18n.t(name),
125
+ pretty_name: I18n.t(name, default: :"activerecord.models.#{name}"),
91
126
  queries: queries,
92
127
  reverse: reverse,
93
128
  select_expression: queries.size > 1 ? "COALESCE(#{queries.map { |q| q[:select] }.join(',')})" : queries[0][:select],
@@ -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
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleDrilldown
4
+ # Routing helper methods
4
5
  module Routing
5
6
  def draw_drilldown(path, controller = path)
6
7
  get "#{path}(.:format)" => "#{controller}#index", as: path
7
8
  scope path do
8
- %i[excel_export html_export index].each do |action|
9
+ %i[choices excel_export html_export index].each do |action|
9
10
  get "#{action}(/:id)(.:format)", controller: controller, action: action
10
11
  end
11
12
  end
@@ -1,3 +1,3 @@
1
1
  module SimpleDrilldown
2
- VERSION = '0.4.0'
2
+ VERSION = '0.6.4'
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.4.0
4
+ version: 0.6.4
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-05-15 00:00:00.000000000 Z
11
+ date: 2020-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chartkick
@@ -123,10 +123,11 @@ 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
130
131
  - lib/simple_drilldown/routing.rb
131
132
  - lib/simple_drilldown/search.rb
132
133
  - lib/simple_drilldown/version.rb
@@ -136,7 +137,7 @@ licenses:
136
137
  - MIT
137
138
  metadata:
138
139
  allowed_push_host: https://rubygems.org/
139
- post_install_message:
140
+ post_install_message:
140
141
  rdoc_options: []
141
142
  require_paths:
142
143
  - lib
@@ -152,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
153
  version: '0'
153
154
  requirements: []
154
155
  rubygems_version: 3.1.2
155
- signing_key:
156
+ signing_key:
156
157
  specification_version: 4
157
158
  summary: Simple data warehouse and drilldown.
158
159
  test_files: []