compendium 1.0.2 → 1.0.4

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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- Y2M5ZWFmZjNlN2Q0YWQ0MGQ4ZTQ0Njg3ZDIzN2VjMTI4NzZlOTJlYw==
4
+ ODBjOTg1ZGM3NjVhYTM3ZjcxZDM5YjMyYjI1M2I2MzJjYTkzZmFiNg==
5
5
  data.tar.gz: !binary |-
6
- ZjFkNjcwNmU2YTRlOTNhZTdiZjYyYmU0MzA1MDEzZGJhMjdmOGQ3MQ==
6
+ Yzg1ZDk0MmUxYTlkMDg2NTgzYTIxMzEyZjk3NjcxYjYwMzc2ZjhiZQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NjlmNzliZjA5MjU3N2E4Mzk2NzZkOWM0M2JmM2YzYzk1YWNjNDQ1MjgyZTc3
10
- MGFkNzM5ODc4YzI1NGUyMzM1ZDliMWE1ZDY5NmQ2YTQzMjFkNjgzOGNiMzQy
11
- ZGM3NWI5OTA5YjlhMGY0YzMwMjYwNzYzNjJiYjQ5ODE1MDY0NTM=
9
+ ZTA4YTdjYjUwOWE5ZTU3ODVhZmRhMzU2NjViZjZiMGFjZjk3ZDhkYjY0MjIz
10
+ NjY3ZmVjODkzNGYyNWNiNDY2ODlmZWNiNTI4NGI2MGFhNGIzNThjMzgwZjZl
11
+ ZTM5MzIzNjVlNDdmY2FiN2U2MTAxZTQwN2Q0NWUxZmFmN2Q2MjM=
12
12
  data.tar.gz: !binary |-
13
- ODAwYmEwOTJhMmViNTczMTQ4ZGNmYTY0ZGZmZjNjZTZkYjJjMTAwNzNiYjgx
14
- ZmEyOTA0MDRjNmFkMWNiMWM5NzQ3ZTYyYzkzMDlmYzY4ZTk0YTczOTgyZTgy
15
- MTc3ODZmYjU4NGY1MDA4NGJmMjVmNDgyNjIzOTE5YzcyY2Y4NzA=
13
+ N2QyNWI2OGU4ZGQ0OWJiODMxZDNhOTA4NDU1NTliOTViNzAxMzQ3MGRlZTAz
14
+ Y2UxODhjMjUyZTEzNjFhYmI5MzkwZGI3ZTYzNzEwMWM4NWVmNGVlZjJmYjdh
15
+ NTZkYWJkODVmYzk4OTUzZTVhNjkxNDVjODhjN2QyNmY2N2Q4ZTM=
@@ -1,11 +1,18 @@
1
+ require 'compendium/presenters/query'
2
+ require 'active_support/core_ext/array/extract_options'
3
+
1
4
  module Compendium::Presenters
2
5
  class Chart < Query
3
- attr_reader :data, :chart_provider
6
+ attr_reader :data, :container, :chart_provider
7
+
8
+ def initialize(template, object, *args, &setup)
9
+ options = args.extract_options!
10
+ type, container = args
4
11
 
5
- def initialize(template, object, type, container = nil, &setup)
6
12
  super(template, object)
7
13
 
8
- @data = results.records
14
+ @data = options[:index] ? results.records[options[:index]] : results
15
+ @data = @data.records if @data.is_a?(Compendium::ResultSet)
9
16
  @data = @data[0...-1] if query.options[:totals]
10
17
 
11
18
  @container = container || query.name
@@ -5,7 +5,7 @@ module Compendium::Presenters
5
5
  presents :option
6
6
 
7
7
  def name
8
- t(option.name)
8
+ t("options.#{option.name}", cascade: { offset: 2 })
9
9
  end
10
10
 
11
11
  def label(form)
@@ -21,10 +21,12 @@ module Compendium::Presenters
21
21
  out << content_tag(:span, label, class: 'option-label')
22
22
 
23
23
  if option.note?
24
- note = t(option.note == true ? :"#{option.name}_note" : option.note)
24
+ key = option.note == true ? :"#{option.name}_note" : option.note
25
+ note = t("options.#{key}", cascade: { offset: 2 })
26
+ title = t("options.#{option.name}_note_title", default: '', cascade: { offset: 2 })
25
27
 
26
28
  if defined?(AccessibleTooltip)
27
- return accessible_tooltip(:help, label: out, title: t("#{option.name}_note_title", default: '')) { note }
29
+ return accessible_tooltip(:help, label: out, title: title) { note }
28
30
  else
29
31
  out << content_tag(:div, note, class: 'option-note')
30
32
  end
@@ -1,3 +1,5 @@
1
+ require 'compendium/presenters/base'
2
+
1
3
  module Compendium::Presenters
2
4
  class Query < Base
3
5
  presents :query
@@ -4,6 +4,7 @@ module Compendium
4
4
  include Compendium::ReportsHelper
5
5
 
6
6
  before_filter :find_report
7
+ before_filter :validate_options, only: :run
7
8
  before_filter :run_report, only: :run
8
9
 
9
10
  def setup
@@ -24,6 +25,7 @@ module Compendium
24
25
  begin
25
26
  require(@report_name) unless Rails.env.development? or Module.const_defined?(@report_name.classify)
26
27
  @report_class = @report_name.camelize.constantize
28
+ @report = setup_report
27
29
  rescue LoadError
28
30
  flash[:error] = t(:invalid_report)
29
31
  redirect_to action: :index
@@ -31,16 +33,20 @@ module Compendium
31
33
  end
32
34
 
33
35
  def render_setup(opts = {})
34
- locals = { report: setup_report, prefix: @prefix }
35
- render_if_exists(opts.merge(locals: locals)) || render(locals: locals)
36
+ locals = { report: @report, prefix: @prefix }
37
+ opts.empty? ? render(action: :setup, locals: locals) : render_if_exists(opts.merge(locals: locals)) || render(action: :setup, locals: locals)
36
38
  end
37
39
 
38
40
  def setup_report
39
41
  @report_class.new(params[:report] || {})
40
42
  end
41
43
 
44
+ def validate_options
45
+ render_setup and return unless @report.valid?
46
+ end
47
+
42
48
  def run_report
43
- @report = @report_class.new(params[:report]).run(self)
49
+ @report.run(self)
44
50
  end
45
51
 
46
52
  def get_template_prefixes
@@ -5,7 +5,7 @@
5
5
 
6
6
  .options
7
7
  = form_for report, as: :report, url: compendium_reports_run_path do |f|
8
- - report.options.values.each do |option|
8
+ - report.options.each do |option|
9
9
  - expose option, Compendium::Presenters::Option do |opt|
10
10
  .option
11
11
  = opt.label(f)
data/compendium.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |gem|
21
21
  gem.add_dependency 'rails', '>= 3.0.0'
22
22
  gem.add_dependency 'sass-rails', '>= 3.0.0'
23
23
  gem.add_dependency 'compass-rails', '>= 1.0.0'
24
- gem.add_dependency 'collection_of', '1.0.4'
24
+ gem.add_dependency 'collection_of', '1.0.5'
25
25
  gem.add_dependency 'inheritable_attr', '>= 1.0.0'
26
26
  gem.add_development_dependency 'rspec', '~> 2.0'
27
27
  end
@@ -0,0 +1,44 @@
1
+ require 'compendium/query'
2
+
3
+ module Compendium
4
+ # A CollectionQuery is a Query which runs once for each in a given set of criteria
5
+ class CollectionQuery < Query
6
+ attr_accessor :collection
7
+
8
+ def initialize(*)
9
+ super
10
+ self.collection = prepare_collection(@options[:collection])
11
+ end
12
+
13
+ def run(params, context = self)
14
+ collection_values = get_collection_values(context, params)
15
+
16
+ results = collection_values.inject({}) do |r, (key, value)|
17
+ res = collect_results(context, params, key, value)
18
+ r[key] = res unless res.empty?
19
+ r
20
+ end
21
+
22
+ # A CollectionQuery's results will be a ResultSet of ResultSets
23
+ @results = ResultSet.new(results)
24
+ end
25
+
26
+ private
27
+
28
+ def get_collection_values(context, params)
29
+ self.collection = get_associated_query(collection) if collection.is_a?(Symbol)
30
+
31
+ if collection.is_a?(Query)
32
+ collection.run(params, context) unless collection.ran?
33
+ collection.results
34
+ else
35
+ collection
36
+ end
37
+ end
38
+
39
+ def prepare_collection(collection)
40
+ return collection if collection.is_a?(Query) or collection.is_a?(Symbol)
41
+ collection.is_a?(Hash) ? collection : Hash[collection.zip(collection)]
42
+ end
43
+ end
44
+ end
@@ -7,7 +7,7 @@ module Compendium
7
7
  module DSL
8
8
  def self.extended(klass)
9
9
  klass.inheritable_attr :queries, default: ::Collection[Query]
10
- klass.inheritable_attr :options, default: {}
10
+ klass.inheritable_attr :options, default: ::Collection[Option]
11
11
  end
12
12
 
13
13
  def query(name, opts = {}, &block)
@@ -20,16 +20,20 @@ module Compendium
20
20
  opts = args.extract_options!
21
21
  type = args.shift
22
22
 
23
+ add_params_validations(name, opts.delete(:validates))
24
+
23
25
  if options[name]
24
26
  options[name].type = type if type
25
27
  options[name].default = opts.delete(:default) if opts.key?(:default)
26
28
  options[name].merge!(opts)
27
29
  else
28
- options[name] = Compendium::Option.new(opts.merge(name: name, type: type))
30
+ options << Compendium::Option.new(opts.merge(name: name, type: type))
29
31
  end
30
32
  end
31
33
 
32
- def metric(name, proc, opts = {})
34
+ def metric(name, *args, &block)
35
+ proc = args.first.is_a?(Proc) ? args.first : block
36
+ opts = args.extract_options!
33
37
  raise ArgumentError, 'through option must be specified for metric' unless opts.key?(:through)
34
38
 
35
39
  [opts.delete(:through)].flatten.each do |query|
@@ -38,6 +42,15 @@ module Compendium
38
42
  end
39
43
  end
40
44
 
45
+ # Each Report will have its own descendant of Params in order to safely add validations
46
+ def params_class
47
+ @params_class ||= Class.new(Params)
48
+ end
49
+
50
+ def params_class=(klass)
51
+ @params_class = klass
52
+ end
53
+
41
54
  # Allow defined queries to be redefined by name, eg:
42
55
  # query :main_query
43
56
  # main_query { collect_records_here }
@@ -59,22 +72,30 @@ module Compendium
59
72
 
60
73
  private
61
74
 
62
- def define_query(name, opts, type = Query, &block)
63
- name = name.to_sym
64
- query = type.new(name, opts, block)
75
+ def define_query(name, opts, &block)
76
+ params = [name.to_sym, opts, block]
77
+ query_type = Query
65
78
 
66
- if opts.key?(:through)
79
+ if opts.key?(:collection)
80
+ query_type = CollectionQuery
81
+ elsif opts.key?(:through)
82
+ # Ensure each through query is defined
67
83
  through = [opts[:through]].flatten
84
+ through.each { |q| raise ArgumentError, "query #{q} is not defined" unless self.queries.include?(q.to_sym) }
68
85
 
69
- through.each do |q|
70
- raise ArgumentError, "query #{q} is not defined" unless self.queries.include?(q.to_sym)
71
- end
72
-
73
- query.through = through
86
+ query_type = ThroughQuery
87
+ params.insert(1, through)
74
88
  end
75
89
 
90
+ query = query_type.new(*params)
91
+
76
92
  metrics[name] = opts[:metric] if opts.key?(:metric)
77
93
  queries << query
78
94
  end
95
+
96
+ def add_params_validations(name, validations)
97
+ return if validations.blank?
98
+ self.params_class.validates name, validations
99
+ end
79
100
  end
80
101
  end
@@ -32,7 +32,7 @@ module Compendium
32
32
  raise IndexError if (!obj.nil? and index.nil?) or index.to_i.abs > @choices.length - 1
33
33
  end
34
34
 
35
- super(index || 0)
35
+ super(index)
36
36
  end
37
37
 
38
38
  def value
@@ -2,9 +2,12 @@ require 'compendium/open_hash'
2
2
  require 'compendium/param_types'
3
3
  require 'active_support/core_ext/string/inflections'
4
4
  require 'active_support/core_ext/object/blank'
5
+ require 'active_model'
5
6
 
6
7
  module Compendium
7
8
  class Params < OpenHash
9
+ include ActiveModel::Validations
10
+
8
11
  attr_reader :options
9
12
 
10
13
  def initialize(hash = {}, options = {})
@@ -12,15 +15,19 @@ module Compendium
12
15
  super(prepare_hash_from_options(hash))
13
16
  end
14
17
 
18
+ def self.model_name
19
+ ActiveModel::Name.new(Compendium::Params, Compendium, 'compendium.params')
20
+ end
21
+
15
22
  protected
16
23
 
17
24
  def prepare_hash_from_options(params)
18
25
  params = params.slice(*options.keys)
19
26
 
20
- options.each do |option_name, metadata|
27
+ options.each do |option|
21
28
  begin
22
- klass = "Compendium::#{"#{metadata.type}Param".classify}".constantize
23
- params[option_name] = klass.new(get_default_value(params[option_name], metadata.default), metadata.choices)
29
+ klass = "Compendium::#{"#{option.type}Param".classify}".constantize
30
+ params[option.name] = klass.new(get_default_value(params[option.name], option.default), option.choices)
24
31
  rescue IndexError
25
32
  raise IndexError, "invalid index for #{option_name}"
26
33
  end
@@ -1,12 +1,14 @@
1
1
  require 'compendium/result_set'
2
2
  require 'compendium/params'
3
+ require 'compendium/presenters/chart'
4
+ require 'compendium/presenters/table'
3
5
  require 'collection_of'
4
6
  require_relative '../../config/initializers/ruby/hash'
5
7
 
6
8
  module Compendium
7
9
  class Query
8
10
  attr_reader :name, :results, :metrics
9
- attr_accessor :options, :proc, :through, :report
11
+ attr_accessor :options, :proc, :report
10
12
 
11
13
  def initialize(*args)
12
14
  @report = args.shift if arg_is_report?(args.first)
@@ -23,7 +25,7 @@ module Compendium
23
25
  end
24
26
 
25
27
  def run(params, context = self)
26
- collect_results(params, context)
28
+ collect_results(context, params)
27
29
  collect_metrics(context)
28
30
 
29
31
  @results
@@ -34,11 +36,11 @@ module Compendium
34
36
  end
35
37
 
36
38
  def render_table(template, *options, &block)
37
- Compendium::Presenters::Table.new(template, self, *options, &block).render
39
+ Compendium::Presenters::Table.new(template, self, *options, &block).render unless empty?
38
40
  end
39
41
 
40
42
  def render_chart(template, *options, &block)
41
- Compendium::Presenters::Chart.new(template, self, *options, &block).render
43
+ Compendium::Presenters::Chart.new(template, self, *options, &block).render unless empty?
42
44
  end
43
45
 
44
46
  def ran?
@@ -46,24 +48,20 @@ module Compendium
46
48
  end
47
49
  alias_method :has_run?, :ran?
48
50
 
51
+ # A query is nil if it has no proc
49
52
  def nil?
50
53
  proc.nil?
51
54
  end
52
55
 
53
- private
54
-
55
- def collect_results(params, context)
56
- if through.nil?
57
- args = params
58
- else
59
- args = collect_through_query_results(through, params, context)
56
+ # A query is empty if it has no results
57
+ def empty?
58
+ results.empty?
59
+ end
60
60
 
61
- # If none of the through queries have any results, we shouldn't try to execute the query, because it
62
- # depends on the results of its parents.
63
- return @results = ResultSet.new([]) if args.compact.empty?
64
- end
61
+ private
65
62
 
66
- command = context.instance_exec(args, &proc) if proc
63
+ def collect_results(context, *params)
64
+ command = context.instance_exec(*params, &proc) if proc
67
65
  command = fetch_results(command)
68
66
  @results = ResultSet.new(command) if command
69
67
  end
@@ -73,11 +71,7 @@ module Compendium
73
71
  end
74
72
 
75
73
  def fetch_results(command)
76
- if options.key?(:through) or options.fetch(:collect, nil) == :active_record
77
- command
78
- else
79
- execute_command(command)
80
- end
74
+ (options.fetch(:collect, nil) == :active_record) ? command : execute_command(command)
81
75
  end
82
76
 
83
77
  def execute_command(command)
@@ -90,26 +84,12 @@ module Compendium
90
84
  ::ActiveRecord::Base.connection.select_all(command)
91
85
  end
92
86
 
93
- def collect_through_query_results(through, params, context)
94
- results = {}
95
-
96
- through = [through].flatten.map(&method(:get_through_query))
97
-
98
- through.each do |q|
99
- q.run(params, context) unless q.ran?
100
- results[q.name] = q.results.records
101
- end
102
-
103
- results = results[through.first.name] if through.size == 1
104
- results
105
- end
106
-
107
- def get_through_query(name)
108
- report.queries[name]
109
- end
110
-
111
87
  def arg_is_report?(arg)
112
88
  arg.is_a?(Report) or (arg.is_a?(Class) and arg < Report)
113
89
  end
90
+
91
+ def get_associated_query(query)
92
+ query.is_a?(Query) ? query : report.queries[query]
93
+ end
114
94
  end
115
95
  end
@@ -1,5 +1,7 @@
1
1
  require 'active_support/core_ext/class/attribute_accessors'
2
2
  require 'active_support/core_ext/hash/slice'
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'active_support/core_ext/string/inflections'
3
5
  require 'compendium/dsl'
4
6
 
5
7
  module Compendium
@@ -8,12 +10,45 @@ module Compendium
8
10
 
9
11
  extend Compendium::DSL
10
12
 
11
- def self.inherited(report)
12
- Compendium.reports << report
13
+ delegate :valid?, :errors, to: :params
14
+
15
+ class << self
16
+ def inherited(report)
17
+ Compendium.reports << report
18
+
19
+ # Each Report object has its own Params class so that validations can be added without affecting other
20
+ # reports. However, validations also need to be inherited, so when inheriting a report, subclass its
21
+ # params_class
22
+ report.params_class = Class.new(self.params_class)
23
+ report.params_class.class_eval %Q{
24
+ def self.model_name
25
+ ActiveModel::Name.new(Compendium::Params, Compendium, "compendium.params.#{report.name.underscore rescue 'report'}")
26
+ end
27
+ }
28
+ end
29
+
30
+ # Define predicate methods for getting the report type
31
+ # ie. r.spending? checks that r == SpendingReport
32
+ def method_missing(name, *args, &block)
33
+ prefix = name.to_s.gsub(/[?!]\z/, '')
34
+ report_class = "#{prefix}_report".classify.constantize rescue nil
35
+
36
+ return self == report_class if name.to_s.end_with?('?') and Compendium.reports.include?(report_class)
37
+
38
+ super
39
+ end
40
+
41
+ def respond_to_missing?(name, include_private = false)
42
+ prefix = name.to_s.gsub(/[?!]\z/, '')
43
+ report_class = "#{prefix}_report".classify.constantize rescue nil
44
+
45
+ return true if name.to_s.end_with?('?') and Compendium.reports.include?(report_class)
46
+ super
47
+ end
13
48
  end
14
49
 
15
50
  def initialize(params = {})
16
- @params = Params.new(params, options)
51
+ @params = self.class.params_class.new(params, options)
17
52
 
18
53
  # When creating a new report, map each query back to the report
19
54
  queries.each { |q| q.report = self }
@@ -1,8 +1,9 @@
1
1
  require 'active_support/core_ext/module/delegation'
2
+ require 'active_support/core_ext/hash/indifferent_access'
2
3
 
3
4
  module Compendium
4
5
  class ResultSet
5
- delegate :first, :last, :to_a, :empty?, :each, :map, :[], :count, :length, :size, :==, to: :records
6
+ delegate :first, :last, :to_a, :empty?, :each, :map, :inject, :select, :detect, :[], :count, :length, :size, :==, to: :records
6
7
 
7
8
  attr_reader :records
8
9
  alias :all :records
@@ -11,6 +12,8 @@ module Compendium
11
12
  @records = records.map do |r|
12
13
  r.respond_to?(:with_indifferent_access) ? r.with_indifferent_access : r
13
14
  end
15
+
16
+ @records = Hash[@records] if records.is_a?(Hash)
14
17
  end
15
18
 
16
19
  def keys
@@ -0,0 +1,43 @@
1
+ require 'compendium/query'
2
+
3
+ module Compendium
4
+ class ThroughQuery < Query
5
+ attr_accessor :through
6
+
7
+ def initialize(*args)
8
+ @report = args.shift if arg_is_report?(args.first)
9
+ @through = args.slice!(1)
10
+ super(*args)
11
+ end
12
+
13
+ private
14
+
15
+ def collect_results(context, params)
16
+ args = collect_through_query_results(params, context)
17
+
18
+ # If none of the through queries have any results, we shouldn't try to execute the query, because it
19
+ # depends on the results of its parents.
20
+ return @results = ResultSet.new([]) if args.compact.empty?
21
+
22
+ super(context, args)
23
+ end
24
+
25
+ def fetch_results(command)
26
+ command
27
+ end
28
+
29
+ def collect_through_query_results(params, context)
30
+ results = {}
31
+
32
+ queries = [through].flatten.map(&method(:get_associated_query))
33
+
34
+ queries.each do |q|
35
+ q.run(params, context) unless q.ran?
36
+ results[q.name] = q.results.records
37
+ end
38
+
39
+ results = results[queries.first.name] if queries.size == 1
40
+ results
41
+ end
42
+ end
43
+ end
@@ -1,3 +1,3 @@
1
1
  module Compendium
2
- VERSION = "1.0.2"
2
+ VERSION = "1.0.4"
3
3
  end
data/lib/compendium.rb CHANGED
@@ -5,6 +5,7 @@ require 'active_support/configurable'
5
5
  module Compendium
6
6
  autoload :AbstractChartProvider, 'compendium/abstract_chart_provider'
7
7
  autoload :ChartProvider, 'compendium/abstract_chart_provider'
8
+ autoload :CollectionQuery, 'compendium/collection_query'
8
9
  autoload :ContextWrapper, 'compendium/context_wrapper'
9
10
  autoload :DSL, 'compendium/dsl'
10
11
  autoload :Metric, 'compendium/metric'
@@ -13,6 +14,7 @@ module Compendium
13
14
  autoload :Query, 'compendium/query'
14
15
  autoload :ResultSet, 'compendium/result_set'
15
16
  autoload :Report, 'compendium/report'
17
+ autoload :ThroughQuery, 'compendium/through_query'
16
18
 
17
19
  autoload :Param, 'compendium/param_types'
18
20
  autoload :BooleanParam, 'compendium/param_types'
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'compendium/collection_query'
3
+
4
+ describe Compendium::CollectionQuery do
5
+ let(:collection) { { one: 1, two: 2, three: 3 } }
6
+ subject { described_class.new(:collection_query, { collection: collection }, -> _, key, item { [item * 2] }) }
7
+
8
+ before { Compendium::Query.any_instance.stub(:execute_query) { |cmd| cmd } }
9
+
10
+ describe "#run" do
11
+ context do
12
+ before { subject.run(nil) }
13
+
14
+ its(:results) { should be_a Compendium::ResultSet }
15
+ its(:results) { should == { one: [2], two: [4], three: [6] } }
16
+
17
+ context "when given an array instead of a hash" do
18
+ let(:collection) { [1, 2, 3] }
19
+
20
+ its(:results) { should be_a Compendium::ResultSet }
21
+ its(:results) { should == { 1 => [2], 2 => [4], 3 => [6] } }
22
+ end
23
+ end
24
+
25
+ it "should not collect empty results" do
26
+ subject.proc = -> _, key, item { [item] if item > 2 }
27
+ subject.run(nil)
28
+ subject.results.should == { three: [3] }
29
+ end
30
+
31
+ context "when given another query" do
32
+ let(:q) { Compendium::Query.new(:q, {}, -> * { { one: 1, two: 2, three: 3 } }) }
33
+ subject { described_class.new(:collection, { collection: q }, -> _, key, item { [ item * 2 ] }) }
34
+
35
+ before { subject.run(nil) if example.metadata.fetch(:run_query, true) }
36
+
37
+ its(:results) { should == { one: [2], two: [4], three: [6] } }
38
+
39
+ it "should not re-run the query if it has already ran", run_query: false do
40
+ q.run(nil)
41
+ q.should_not_receive(:run)
42
+ subject.run(nil)
43
+ end
44
+ end
45
+ end
46
+ end
data/spec/dsl_spec.rb CHANGED
@@ -25,15 +25,34 @@ describe Compendium::DSL do
25
25
  subject.option :starting_on, :date, default: proc
26
26
  subject.options[:starting_on].default.should == proc
27
27
  end
28
+
29
+ it "should add validations" do
30
+ subject.option :foo, validates: { presence: true }
31
+ subject.params_class.validators_on(:foo).should_not be_empty
32
+ end
33
+
34
+ it "should not add validations if no validates option is given" do
35
+ subject.params_class.should_not_receive :validates
36
+ subject.option :foo
37
+ end
38
+
39
+ it "should not bleed overridden options into the superclass" do
40
+ r = Class.new(subject)
41
+ r.option :starting_on, :boolean
42
+ r.option :new, :date
43
+ subject.options[:starting_on].should be_date
44
+ end
28
45
  end
29
46
 
30
47
  describe "#query" do
31
- subject do
48
+ let(:report_class) do
32
49
  Class.new(Compendium::Report) do
33
50
  query :test
34
51
  end
35
52
  end
36
53
 
54
+ subject { report_class }
55
+
37
56
  its(:queries) { should include :test }
38
57
 
39
58
  it "should relate the new query back to the report instance" do
@@ -44,6 +63,42 @@ describe Compendium::DSL do
44
63
  it "should not relate a query to the report class" do
45
64
  subject.test.report.should be_nil
46
65
  end
66
+
67
+ context "when given a through option" do
68
+ before { report_class.query :through, through: :test }
69
+ subject { report_class.queries[:through] }
70
+
71
+ it { should be_a Compendium::ThroughQuery }
72
+ its(:through) { should == [:test] }
73
+ end
74
+
75
+ context "when given a collection option" do
76
+ subject { report_class.queries[:collection] }
77
+
78
+ context "that is an enumerable" do
79
+ before { report_class.query :collection, collection: [] }
80
+
81
+ it { should be_a Compendium::CollectionQuery }
82
+ end
83
+
84
+ context "that is a symbol" do
85
+ let(:query) { double("Query") }
86
+
87
+ before do
88
+ Compendium::Query.any_instance.stub(:get_associated_query).with(:query).and_return(query)
89
+ report_class.query :collection, collection: :query
90
+ end
91
+
92
+ its(:collection) { should == :query }
93
+ end
94
+
95
+ context "that is a query" do
96
+ let(:query) { Compendium::Query.new(:query, {}, ->{}) }
97
+ before { report_class.query :collection, collection: query }
98
+
99
+ its(:collection) { should == query }
100
+ end
101
+ end
47
102
  end
48
103
 
49
104
  describe "#chart" do
@@ -81,6 +136,19 @@ describe Compendium::DSL do
81
136
  it "should raise an error if specified for an invalid query" do
82
137
  expect{ subject.metric :test_metric, metric_proc, through: :fake }.to raise_error ArgumentError, 'query fake is not defined'
83
138
  end
139
+
140
+ it "should allow metrics to be defined with a block" do
141
+ subject.metric :block_metric, through: :test do
142
+ 123
143
+ end
144
+
145
+ subject.queries[:test].metrics[:block_metric].run(self, nil).should == 123
146
+ end
147
+
148
+ it "should allow metrics to be defined with a lambda" do
149
+ subject.metric :block_metric, -> * { 123 }, through: :test
150
+ subject.queries[:test].metrics[:block_metric].run(self, nil).should == 123
151
+ end
84
152
  end
85
153
 
86
154
  it "should allow previously defined queries to be redefined by name" do
data/spec/params_spec.rb CHANGED
@@ -1,13 +1,15 @@
1
1
  require 'compendium/params'
2
2
 
3
3
  describe Compendium::Params do
4
- let(:options) { {
5
- starting_on: Compendium::Option.new(name: :starting_on, type: :date, default: ->{ Date.today }),
6
- ending_on: Compendium::Option.new(name: :ending_on, type: :date),
7
- report_type: Compendium::Option.new(name: :report_type, type: :radio, choices: [:big, :small]),
8
- boolean: Compendium::Option.new(name: :boolean, type: :boolean),
9
- another_boolean: Compendium::Option.new(name: :another_boolean, type: :boolean)
10
- } }
4
+ let(:options) {
5
+ opts = Collection[Compendium::Option]
6
+ opts << Compendium::Option.new(name: :starting_on, type: :date, default: ->{ Date.today })
7
+ opts << Compendium::Option.new(name: :ending_on, type: :date)
8
+ opts << Compendium::Option.new(name: :report_type, type: :radio, choices: [:big, :small])
9
+ opts << Compendium::Option.new(name: :boolean, type: :boolean)
10
+ opts << Compendium::Option.new(name: :another_boolean, type: :boolean)
11
+ opts
12
+ }
11
13
 
12
14
  subject{ described_class.new(@params, options) }
13
15
 
@@ -25,4 +27,17 @@ describe Compendium::Params do
25
27
  @params = {}
26
28
  subject.ending_on.should be_nil
27
29
  end
30
+
31
+ describe "#validations" do
32
+ let(:report_class) { Class.new(described_class) }
33
+ subject { report_class.new({}, options) }
34
+
35
+ before do
36
+ report_class.validates :ending_on, presence: true
37
+ subject.valid?
38
+ end
39
+
40
+ it { should_not be_valid }
41
+ its('errors.keys') { should include :ending_on }
42
+ end
28
43
  end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'compendium/presenters/chart'
3
+
4
+ describe Compendium::Presenters::Chart do
5
+ before do
6
+ described_class.any_instance.stub(:provider) { double('ChartProvider') }
7
+ described_class.any_instance.stub(:initialize_chart_provider)
8
+ end
9
+
10
+ describe '#initialize' do
11
+ let(:template) { double('Template') }
12
+ let(:query) { double('Query', name: 'test_query', results: results, options: {}) }
13
+ let(:results) { Compendium::ResultSet.new([]) }
14
+
15
+ context 'when all params are given' do
16
+ subject{ described_class.new(template, query, :pie, :container) }
17
+
18
+ its(:data) { should == results.records }
19
+ its(:container) { should == :container }
20
+ end
21
+
22
+ context 'when container is not given' do
23
+ subject{ described_class.new(template, query, :pie) }
24
+
25
+ its(:data) { should == results.records }
26
+ its(:container) { should == 'test_query' }
27
+ end
28
+
29
+ context "when options are given" do
30
+ before { results.stub(:records) { { one: [] } } }
31
+ subject{ described_class.new(template, query, :pie, index: :one) }
32
+
33
+ its(:data) { should == results.records[:one] }
34
+ its(:container) { should == 'test_query' }
35
+ end
36
+ end
37
+ end
@@ -15,7 +15,7 @@ describe Compendium::Presenters::Option do
15
15
 
16
16
  describe "#name" do
17
17
  it "should pass the name through I18n" do
18
- template.should_receive(:t).with(:test_option)
18
+ template.should_receive(:t).with('options.test_option', anything)
19
19
  subject.name
20
20
  end
21
21
  end
data/spec/query_spec.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'spec_helper'
1
2
  require 'compendium/query'
2
3
 
3
4
  describe Compendium::Query do
@@ -48,50 +49,47 @@ describe Compendium::Query do
48
49
  query = described_class.new(:blank, {}, nil)
49
50
  query.run(nil).should be_empty
50
51
  end
52
+ end
51
53
 
52
- context 'through queries' do
53
- let(:parent_query1) { described_class.new(:parent1, {}, -> * { }) }
54
- let(:parent_query2) { described_class.new(:parent2, {}, -> * { }) }
55
- let(:parent_query3) { described_class.new(:parent3, {}, -> * { [[1, 2, 3]] }) }
56
-
57
- subject { described_class.new(:sub, {}, -> records { records.first }) }
58
-
59
- before do
60
- subject.stub(:get_through_query).with(:parent1).and_return(parent_query1)
61
- subject.stub(:get_through_query).with(:parent2).and_return(parent_query2)
62
- subject.stub(:get_through_query).with(:parent3).and_return(parent_query3)
63
- described_class.any_instance.stub(:execute_query) { |cmd| cmd }
64
- end
65
-
66
- it "should not try to run a through query if the parent query has no results" do
67
- subject.through = :parent1
54
+ describe "#nil?" do
55
+ it "should return true if the query's proc is nil" do
56
+ Compendium::Query.new(:test, {}, nil).should be_nil
57
+ end
68
58
 
69
- expect { subject.run(nil) }.to_not raise_error
70
- subject.results.should be_empty
71
- end
59
+ it "should return false if the query's proc is not nil" do
60
+ Compendium::Query.new(:test, {}, ->{}).should_not be_nil
61
+ end
62
+ end
72
63
 
73
- it "should not try to run a through query with multiple parents all of which have no results" do
74
- subject.through = [:parent1, :parent2]
64
+ describe "#render_chart" do
65
+ let(:template) { double("Template") }
66
+ subject { described_class.new(:test, {}, -> * {}) }
75
67
 
76
- expect { subject.run(nil) }.to_not raise_error
77
- subject.results.should be_empty
78
- end
68
+ it "should return nil if the query has no results" do
69
+ subject.stub(empty?: true)
70
+ subject.render_chart(template).should be_nil
71
+ end
79
72
 
80
- it "should allow non blank queries" do
81
- subject.through = :parent3
82
- subject.run(nil)
83
- subject.results.should == [1, 2, 3]
84
- end
73
+ it "should initialize a new Chart presenter if the query has results" do
74
+ subject.stub(empty?: false)
75
+ Compendium::Presenters::Chart.should_receive(:new).with(template, subject).and_return(double("Presenter").as_null_object)
76
+ subject.render_chart(template)
85
77
  end
86
78
  end
87
79
 
88
- describe "#nil?" do
89
- it "should return true if the query's proc is nil" do
90
- Compendium::Query.new(:test, {}, nil).should be_nil
80
+ describe "#render_table" do
81
+ let(:template) { double("Template") }
82
+ subject { described_class.new(:test, {}, -> * {}) }
83
+
84
+ it "should return nil if the query has no results" do
85
+ subject.stub(empty?: true)
86
+ subject.render_table(template).should be_nil
91
87
  end
92
88
 
93
- it "should return false if the query's proc is not nil" do
94
- Compendium::Query.new(:test, {}, ->{}).should_not be_nil
89
+ it "should initialize a new Table presenter if the query has results" do
90
+ subject.stub(empty?: false)
91
+ Compendium::Presenters::Table.should_receive(:new).with(template, subject).and_return(double("Presenter").as_null_object)
92
+ subject.render_table(template)
95
93
  end
96
94
  end
97
95
  end
data/spec/report_spec.rb CHANGED
@@ -129,4 +129,60 @@ describe Compendium::Report do
129
129
  end
130
130
  end
131
131
  end
132
+
133
+ describe "predicate methods" do
134
+ before do
135
+ OneReport = Class.new(Compendium::Report)
136
+ TwoReport = Class.new(Compendium::Report)
137
+ ThreeReport = Class.new
138
+ end
139
+
140
+ after do
141
+ Object.send(:remove_const, :OneReport)
142
+ Object.send(:remove_const, :TwoReport)
143
+ Object.send(:remove_const, :ThreeReport)
144
+ end
145
+
146
+ it { should respond_to(:one?) }
147
+ it { should respond_to(:two?) }
148
+ it { should_not respond_to(:three?) }
149
+
150
+ it { should_not be_one }
151
+ it { should_not be_two }
152
+
153
+ specify { OneReport.should be_one }
154
+ specify { TwoReport.should be_two }
155
+ end
156
+
157
+ describe "parameters" do
158
+ let(:report_class) { Class.new(subject) }
159
+ let(:report_class2) { Class.new(report_class) }
160
+
161
+ it "should include ancestors params" do
162
+ report_class.params_class.ancestors.should include subject.params_class
163
+ end
164
+
165
+ it "should inherit validations" do
166
+ report_class.params_class.validates :foo, presence: true
167
+ report_class2.params_class.validators_on(:foo).should_not be_nil
168
+ end
169
+ end
170
+
171
+ describe "#valid?" do
172
+ let(:report_class) do
173
+ Class.new(described_class) do
174
+ option :id, :dropdown, choices: (0..10).to_a, validates: { presence: true }
175
+ end
176
+ end
177
+
178
+ it "should return true if there are no validation failures" do
179
+ r = report_class.new(id: 5)
180
+ r.should be_valid
181
+ end
182
+
183
+ it "should return false if there are validation failures" do
184
+ r = report_class.new(id: nil)
185
+ r.should_not be_valid
186
+ end
187
+ end
132
188
  end
@@ -0,0 +1,23 @@
1
+ require 'compendium/result_set'
2
+
3
+ describe Compendium::ResultSet do
4
+ describe "#initialize" do
5
+ subject{ described_class.new(results).records }
6
+
7
+ context "when given an array" do
8
+ let(:results) { [1, 2, 3] }
9
+ it { should == [1, 2, 3] }
10
+ end
11
+
12
+ context "when given an array of hashes" do
13
+ let(:results) { [{one: 1}, {two: 2}] }
14
+ it { should == [{"one" => 1}, {"two" => 2}] }
15
+ its(:first) { should be_a ActiveSupport::HashWithIndifferentAccess }
16
+ end
17
+
18
+ context "when given a hash" do
19
+ let(:results) { { one: 1, two: 2 } }
20
+ it { should == { one: 1, two: 2 } }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ require 'compendium/through_query'
2
+
3
+ describe Compendium::ThroughQuery do
4
+ describe "#initialize" do
5
+ let(:options) { double("Options") }
6
+ let(:proc) { double("Proc") }
7
+ let(:through) { double("Query") }
8
+
9
+ context "when supplying a report" do
10
+ let(:r) { Compendium::Report.new }
11
+ subject { described_class.new(r, :test, through, options, proc)}
12
+
13
+ its(:report) { should == r }
14
+ its(:name) { should == :test }
15
+ its(:through) { should == through }
16
+ its(:options) { should == options }
17
+ its(:proc) { should == proc }
18
+ end
19
+
20
+ context "when not supplying a report" do
21
+ subject { described_class.new(:test, through, options, proc)}
22
+
23
+ its(:report) { should be_nil }
24
+ its(:name) { should == :test }
25
+ its(:through) { should == through }
26
+ its(:options) { should == options }
27
+ its(:proc) { should == proc }
28
+ end
29
+ end
30
+
31
+ describe "#run" do
32
+ let(:parent1) { Compendium::Query.new(:parent1, {}, -> * { }) }
33
+ let(:parent2) { Compendium::Query.new(:parent2, {}, -> * { }) }
34
+ let(:parent3) { Compendium::Query.new(:parent3, {}, -> * { [[1, 2, 3]] }) }
35
+
36
+ before { parent3.stub(:execute_query) { |cmd| cmd } }
37
+
38
+ context "with a single parent" do
39
+ subject { described_class.new(:sub, parent1, {}, -> r { r.first }) }
40
+
41
+ it "should not try to run a through query if the parent query has no results" do
42
+ expect { subject.run(nil) }.to_not raise_error
43
+ subject.results.should be_empty
44
+ end
45
+ end
46
+
47
+ context "with multiple parents" do
48
+ subject { described_class.new(:sub, [parent1, parent2], {}, -> r { r.first }) }
49
+
50
+ it "should not try to run a through query with multiple parents all of which have no results" do
51
+ expect { subject.run(nil) }.to_not raise_error
52
+ subject.results.should be_empty
53
+ end
54
+
55
+ it "should allow non blank queries" do
56
+ subject.through = parent3
57
+ subject.run(nil)
58
+ subject.results.should == [1, 2, 3]
59
+ end
60
+ end
61
+
62
+ context "when the through option is an actual query" do
63
+ subject { described_class.new(:sub, parent3, {}, -> r { r.first }) }
64
+
65
+ before { subject.run(nil) }
66
+
67
+ its(:through) { should == parent3 }
68
+ its(:results) { should == [1, 2, 3] }
69
+ end
70
+ end
71
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: compendium
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Vandersluis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-11 00:00:00.000000000 Z
11
+ date: 2013-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  type: :runtime
@@ -60,12 +60,12 @@ dependencies:
60
60
  requirements:
61
61
  - - '='
62
62
  - !ruby/object:Gem::Version
63
- version: 1.0.4
63
+ version: 1.0.5
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: 1.0.4
68
+ version: 1.0.5
69
69
  - !ruby/object:Gem::Dependency
70
70
  type: :runtime
71
71
  prerelease: false
@@ -129,6 +129,7 @@ files:
129
129
  - lib/compendium.rb
130
130
  - lib/compendium/abstract_chart_provider.rb
131
131
  - lib/compendium/chart_provider/amcharts.rb
132
+ - lib/compendium/collection_query.rb
132
133
  - lib/compendium/context_wrapper.rb
133
134
  - lib/compendium/dsl.rb
134
135
  - lib/compendium/engine.rb
@@ -141,7 +142,9 @@ files:
141
142
  - lib/compendium/query.rb
142
143
  - lib/compendium/report.rb
143
144
  - lib/compendium/result_set.rb
145
+ - lib/compendium/through_query.rb
144
146
  - lib/compendium/version.rb
147
+ - spec/collection_query_spec.rb
145
148
  - spec/context_wrapper_spec.rb
146
149
  - spec/dsl_spec.rb
147
150
  - spec/metric_spec.rb
@@ -149,10 +152,13 @@ files:
149
152
  - spec/param_types_spec.rb
150
153
  - spec/params_spec.rb
151
154
  - spec/presenters/base_spec.rb
155
+ - spec/presenters/chart_spec.rb
152
156
  - spec/presenters/option_spec.rb
153
157
  - spec/query_spec.rb
154
158
  - spec/report_spec.rb
159
+ - spec/result_set_spec.rb
155
160
  - spec/spec_helper.rb
161
+ - spec/through_query_spec.rb
156
162
  homepage: https://github.com/dvandersluis/compendium
157
163
  licenses:
158
164
  - MIT
@@ -178,6 +184,7 @@ signing_key:
178
184
  specification_version: 4
179
185
  summary: Ruby on Rails reporting framework
180
186
  test_files:
187
+ - spec/collection_query_spec.rb
181
188
  - spec/context_wrapper_spec.rb
182
189
  - spec/dsl_spec.rb
183
190
  - spec/metric_spec.rb
@@ -185,8 +192,11 @@ test_files:
185
192
  - spec/param_types_spec.rb
186
193
  - spec/params_spec.rb
187
194
  - spec/presenters/base_spec.rb
195
+ - spec/presenters/chart_spec.rb
188
196
  - spec/presenters/option_spec.rb
189
197
  - spec/query_spec.rb
190
198
  - spec/report_spec.rb
199
+ - spec/result_set_spec.rb
191
200
  - spec/spec_helper.rb
201
+ - spec/through_query_spec.rb
192
202
  has_rdoc: