compendium 1.0.2 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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: