From bb7ea6e70bb6a8a28c9f0febaf9129245154d9d1 Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Tue, 23 Jan 2018 16:30:34 -0500 Subject: [PATCH 01/23] Extract param types into their own namespace --- lib/compendium.rb | 9 +- lib/compendium/param_types.rb | 120 ++----------- lib/compendium/param_types/boolean.rb | 24 +++ lib/compendium/param_types/date.rb | 19 ++ lib/compendium/param_types/dropdown.rb | 9 + lib/compendium/param_types/param.rb | 47 +++++ lib/compendium/param_types/radio.rb | 9 + lib/compendium/param_types/scalar.rb | 14 ++ lib/compendium/param_types/with_choices.rb | 23 +++ lib/compendium/params.rb | 4 +- .../{ => compendium}/collection_query_spec.rb | 0 spec/{ => compendium}/context_wrapper_spec.rb | 0 spec/{ => compendium}/count_query_spec.rb | 0 spec/{ => compendium}/dsl_spec.rb | 0 spec/{ => compendium}/metric_spec.rb | 0 spec/{ => compendium}/option_spec.rb | 0 spec/compendium/param_types/boolean_spec.rb | 56 ++++++ spec/compendium/param_types/date_spec.rb | 23 +++ spec/compendium/param_types/dropdown_spec.rb | 12 ++ spec/compendium/param_types/param_spec.rb | 19 ++ spec/compendium/param_types/radio_spec.rb | 12 ++ spec/compendium/param_types/scalar_spec.rb | 16 ++ .../param_types/with_choices_spec.rb | 41 +++++ spec/{ => compendium}/params_spec.rb | 0 spec/{ => compendium}/query_spec.rb | 0 spec/{ => compendium}/report_spec.rb | 0 spec/{ => compendium}/result_set_spec.rb | 0 spec/{ => compendium}/sum_query_spec.rb | 0 spec/{ => compendium}/through_query_spec.rb | 0 spec/param_types_spec.rb | 166 ------------------ 30 files changed, 337 insertions(+), 286 deletions(-) create mode 100644 lib/compendium/param_types/boolean.rb create mode 100644 lib/compendium/param_types/date.rb create mode 100644 lib/compendium/param_types/dropdown.rb create mode 100644 lib/compendium/param_types/param.rb create mode 100644 lib/compendium/param_types/radio.rb create mode 100644 lib/compendium/param_types/scalar.rb create mode 100644 lib/compendium/param_types/with_choices.rb rename spec/{ => compendium}/collection_query_spec.rb (100%) rename spec/{ => compendium}/context_wrapper_spec.rb (100%) rename spec/{ => compendium}/count_query_spec.rb (100%) rename spec/{ => compendium}/dsl_spec.rb (100%) rename spec/{ => compendium}/metric_spec.rb (100%) rename spec/{ => compendium}/option_spec.rb (100%) create mode 100644 spec/compendium/param_types/boolean_spec.rb create mode 100644 spec/compendium/param_types/date_spec.rb create mode 100644 spec/compendium/param_types/dropdown_spec.rb create mode 100644 spec/compendium/param_types/param_spec.rb create mode 100644 spec/compendium/param_types/radio_spec.rb create mode 100644 spec/compendium/param_types/scalar_spec.rb create mode 100644 spec/compendium/param_types/with_choices_spec.rb rename spec/{ => compendium}/params_spec.rb (100%) rename spec/{ => compendium}/query_spec.rb (100%) rename spec/{ => compendium}/report_spec.rb (100%) rename spec/{ => compendium}/result_set_spec.rb (100%) rename spec/{ => compendium}/sum_query_spec.rb (100%) rename spec/{ => compendium}/through_query_spec.rb (100%) delete mode 100644 spec/param_types_spec.rb diff --git a/lib/compendium.rb b/lib/compendium.rb index b13f5e2..e788e62 100644 --- a/lib/compendium.rb +++ b/lib/compendium.rb @@ -13,20 +13,13 @@ module Compendium autoload :Metric, 'compendium/metric' autoload :Option, 'compendium/option' autoload :Params, 'compendium/params' + autoload :ParamTypes, 'compendium/param_types' autoload :Query, 'compendium/query' autoload :ResultSet, 'compendium/result_set' autoload :Report, 'compendium/report' autoload :SumQuery, 'compendium/sum_query' autoload :ThroughQuery, 'compendium/through_query' - autoload :Param, 'compendium/param_types' - autoload :BooleanParam, 'compendium/param_types' - autoload :DateParam, 'compendium/param_types' - autoload :DropdownParam, 'compendium/param_types' - autoload :ParamWithChoices, 'compendium/param_types' - autoload :RadioParam, 'compendium/param_types' - autoload :ScalarParam, 'compendium/param_types' - def self.reports @reports ||= [] end diff --git a/lib/compendium/param_types.rb b/lib/compendium/param_types.rb index 6a4a419..0c988dd 100644 --- a/lib/compendium/param_types.rb +++ b/lib/compendium/param_types.rb @@ -1,111 +1,11 @@ -require_relative '../../config/initializers/ruby/numeric' -require 'delegate' - module Compendium - class Param < ::SimpleDelegator - def scalar?; false; end - def boolean?; false; end - def date?; false; end - def dropdown?; false; end - def radio?; false; end - - def ==(other) - return true if (value == other rescue false) - super - end - - # Need to explicitly delegate nil? to the object, otherwise it's always false - # This is because SimpleDelegator is a non-nil object, and it only forwards non-defined methods! - def nil? - __getobj__.nil? - end - - def to_f - Kernel.Float(__getobj__) - end - - def to_i - Kernel.Integer(__getobj__) - end - end - - class ParamWithChoices < Param - def initialize(obj, choices) - @choices = choices - - if @choices.respond_to?(:call) - # If given a proc, defer determining values until later. - index = obj - else - index = obj.numeric? ? obj.to_i : @choices.index(obj) - raise IndexError if (!obj.nil? && index.nil?) || index.to_i.abs > @choices.length - 1 - end - - super(index) - end - - def value - @choices[self] - end - end - - class ScalarParam < Param - def initialize(obj, *) - super obj - end - - # A scalar param just keeps track of a value with no modifications - def scalar? - true - end - end - - class BooleanParam < Param - def initialize(obj, *) - # If given 0, 1, or a version thereof (ie. "0"), pass it along - return super obj.to_i if obj.numeric? && (0..1).cover?(obj.to_i) - super !!obj ? 0 : 1 - end - - def boolean? - true - end - - def value - [true, false][self] - end - - # When negating a BooleanParam, use the value instead - def ! - !value - end - end - - class DateParam < Param - def initialize(obj, *) - if obj.respond_to?(:to_date) - obj = obj.to_date - else - obj = Date.parse(obj) rescue nil - end - - super obj - end - - def date? - true - end - end - - class RadioParam < ParamWithChoices - def radio? - true - end - end - - class DropdownParam < ParamWithChoices - def dropdown? - true - end - end -end \ No newline at end of file + module ParamTypes + autoload :Param, 'compendium/param_types/param' + autoload :Boolean, 'compendium/param_types/boolean' + autoload :Date, 'compendium/param_types/date' + autoload :Dropdown, 'compendium/param_types/dropdown' + autoload :WithChoices, 'compendium/param_types/with_choices' + autoload :Radio, 'compendium/param_types/radio' + autoload :Scalar, 'compendium/param_types/scalar' + end +end diff --git a/lib/compendium/param_types/boolean.rb b/lib/compendium/param_types/boolean.rb new file mode 100644 index 0000000..2127c83 --- /dev/null +++ b/lib/compendium/param_types/boolean.rb @@ -0,0 +1,24 @@ +module Compendium + module ParamTypes + class Boolean < Param + def initialize(obj, *) + # If given 0, 1, or a version thereof (ie. "0"), pass it along + return super obj.to_i if obj.numeric? && (0..1).cover?(obj.to_i) + super !!obj ? 0 : 1 + end + + def boolean? + true + end + + def value + [true, false][self] + end + + # When negating a BooleanParam, use the value instead + def ! + !value + end + end + end +end diff --git a/lib/compendium/param_types/date.rb b/lib/compendium/param_types/date.rb new file mode 100644 index 0000000..a267607 --- /dev/null +++ b/lib/compendium/param_types/date.rb @@ -0,0 +1,19 @@ +module Compendium + module ParamTypes + class Date < Param + def initialize(obj, *) + if obj.respond_to?(:to_date) + obj = obj.to_date + else + obj = ::Date.parse(obj) rescue nil + end + + super obj + end + + def date? + true + end + end + end +end diff --git a/lib/compendium/param_types/dropdown.rb b/lib/compendium/param_types/dropdown.rb new file mode 100644 index 0000000..e57a075 --- /dev/null +++ b/lib/compendium/param_types/dropdown.rb @@ -0,0 +1,9 @@ +module Compendium + module ParamTypes + class Dropdown < WithChoices + def dropdown? + true + end + end + end +end diff --git a/lib/compendium/param_types/param.rb b/lib/compendium/param_types/param.rb new file mode 100644 index 0000000..8dcd00b --- /dev/null +++ b/lib/compendium/param_types/param.rb @@ -0,0 +1,47 @@ +require_relative '../../../config/initializers/ruby/numeric' +require 'delegate' + +module Compendium + module ParamTypes + class Param < ::SimpleDelegator + def scalar? + false + end + + def boolean? + false + end + + def date? + false + end + + def dropdown? + false + end + + def radio? + false + end + + def ==(other) + return true if (value == other rescue false) + super + end + + # Need to explicitly delegate nil? to the object, otherwise it's always false + # This is because SimpleDelegator is a non-nil object, and it only forwards non-defined methods! + def nil? + __getobj__.nil? + end + + def to_f + Kernel.Float(__getobj__) + end + + def to_i + Kernel.Integer(__getobj__) + end + end + end +end diff --git a/lib/compendium/param_types/radio.rb b/lib/compendium/param_types/radio.rb new file mode 100644 index 0000000..d6353ba --- /dev/null +++ b/lib/compendium/param_types/radio.rb @@ -0,0 +1,9 @@ +module Compendium + module ParamTypes + class Radio < WithChoices + def radio? + true + end + end + end +end diff --git a/lib/compendium/param_types/scalar.rb b/lib/compendium/param_types/scalar.rb new file mode 100644 index 0000000..d0ad77c --- /dev/null +++ b/lib/compendium/param_types/scalar.rb @@ -0,0 +1,14 @@ +module Compendium + module ParamTypes + class Scalar < Param + def initialize(obj, *) + super obj + end + + # A scalar param just keeps track of a value with no modifications + def scalar? + true + end + end + end +end diff --git a/lib/compendium/param_types/with_choices.rb b/lib/compendium/param_types/with_choices.rb new file mode 100644 index 0000000..6e13437 --- /dev/null +++ b/lib/compendium/param_types/with_choices.rb @@ -0,0 +1,23 @@ +module Compendium + module ParamTypes + class WithChoices < Param + def initialize(obj, choices) + @choices = choices + + if @choices.respond_to?(:call) + # If given a proc, defer determining values until later. + index = obj + else + index = obj.numeric? ? obj.to_i : @choices.index(obj) + raise IndexError if (!obj.nil? && index.nil?) || index.to_i.abs > @choices.length - 1 + end + + super(index) + end + + def value + @choices[self] + end + end + end +end diff --git a/lib/compendium/params.rb b/lib/compendium/params.rb index e23f138..dd04d4e 100644 --- a/lib/compendium/params.rb +++ b/lib/compendium/params.rb @@ -26,7 +26,7 @@ def prepare_hash_from_options(params) options.each do |option| begin - klass = "Compendium::#{"#{option.type}Param".classify}".constantize + klass = Compendium::ParamTypes.const_get(option.type.classify) params[option.name] = klass.new(get_default_value(params[option.name], option.default), option.choices) rescue IndexError raise IndexError, "invalid index for #{option_name}" @@ -44,4 +44,4 @@ def get_default_value(current, default) end end end -end \ No newline at end of file +end diff --git a/spec/collection_query_spec.rb b/spec/compendium/collection_query_spec.rb similarity index 100% rename from spec/collection_query_spec.rb rename to spec/compendium/collection_query_spec.rb diff --git a/spec/context_wrapper_spec.rb b/spec/compendium/context_wrapper_spec.rb similarity index 100% rename from spec/context_wrapper_spec.rb rename to spec/compendium/context_wrapper_spec.rb diff --git a/spec/count_query_spec.rb b/spec/compendium/count_query_spec.rb similarity index 100% rename from spec/count_query_spec.rb rename to spec/compendium/count_query_spec.rb diff --git a/spec/dsl_spec.rb b/spec/compendium/dsl_spec.rb similarity index 100% rename from spec/dsl_spec.rb rename to spec/compendium/dsl_spec.rb diff --git a/spec/metric_spec.rb b/spec/compendium/metric_spec.rb similarity index 100% rename from spec/metric_spec.rb rename to spec/compendium/metric_spec.rb diff --git a/spec/option_spec.rb b/spec/compendium/option_spec.rb similarity index 100% rename from spec/option_spec.rb rename to spec/compendium/option_spec.rb diff --git a/spec/compendium/param_types/boolean_spec.rb b/spec/compendium/param_types/boolean_spec.rb new file mode 100644 index 0000000..14e4f2c --- /dev/null +++ b/spec/compendium/param_types/boolean_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' +require 'compendium/param_types/boolean' + +describe Compendium::ParamTypes::Boolean do + subject{ described_class.new(true) } + + it { is_expected.not_to be_scalar } + it { is_expected.to be_boolean } + it { is_expected.not_to be_date } + it { is_expected.not_to be_dropdown } + it { is_expected.not_to be_radio } + + it "should pass along 0 and 1" do + expect(described_class.new(0)).to eq(0) + expect(described_class.new(1)).to eq(1) + end + + it "should convert a numeric string to a number" do + expect(described_class.new('0')).to eq(0) + expect(described_class.new('1')).to eq(1) + end + + it "should return 0 for a truthy value" do + expect(described_class.new(true)).to eq(0) + expect(described_class.new(:abc)).to eq(0) + end + + it "should return 1 for a falsey value" do + expect(described_class.new(false)).to eq(1) + expect(described_class.new(nil)).to eq(1) + end + + describe "#value" do + it "should return true for a truthy value" do + expect(described_class.new(true).value).to eq(true) + expect(described_class.new(:abc).value).to eq(true) + expect(described_class.new(0).value).to eq(true) + end + + it "should return false for a falsey value" do + expect(described_class.new(false).value).to eq(false) + expect(described_class.new(nil).value).to eq(false) + expect(described_class.new(1).value).to eq(false) + end + end + + describe "#!" do + it "should return false if the boolean is true" do + expect(!described_class.new(true)).to eq(false) + end + + it "should return true if the boolean is false" do + expect(!described_class.new(false)).to eq(true) + end + end +end diff --git a/spec/compendium/param_types/date_spec.rb b/spec/compendium/param_types/date_spec.rb new file mode 100644 index 0000000..4893eab --- /dev/null +++ b/spec/compendium/param_types/date_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' +require 'compendium/param_types/date' + +describe Compendium::ParamTypes::Date do + subject{ described_class.new(Date.today) } + + it { is_expected.not_to be_scalar } + it { is_expected.not_to be_boolean } + it { is_expected.to be_date } + it { is_expected.not_to be_dropdown } + it { is_expected.not_to be_radio } + + it "should convert date strings to date objects" do + p = described_class.new("2010-05-20") + expect(p).to eq(Date.new(2010, 5, 20)) + end + + it "should convert other date/time formats to date objects" do + described_class.new(DateTime.new(2010, 5, 20, 10, 30, 59)) == Date.new(2010, 5, 20) + described_class.new(Time.new(2010, 5, 20, 10, 30, 59)) == Date.new(2010, 5, 20) + described_class.new(Date.new(2010, 5, 20)) == Date.new(2010, 5, 20) + end +end diff --git a/spec/compendium/param_types/dropdown_spec.rb b/spec/compendium/param_types/dropdown_spec.rb new file mode 100644 index 0000000..b3ab593 --- /dev/null +++ b/spec/compendium/param_types/dropdown_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' +require 'compendium/param_types/dropdown' + +describe Compendium::ParamTypes::Dropdown do + subject{ described_class.new(0, %w(a b c)) } + + it { is_expected.not_to be_scalar } + it { is_expected.not_to be_boolean } + it { is_expected.not_to be_date } + it { is_expected.to be_dropdown } + it { is_expected.not_to be_radio } +end diff --git a/spec/compendium/param_types/param_spec.rb b/spec/compendium/param_types/param_spec.rb new file mode 100644 index 0000000..c8a6e30 --- /dev/null +++ b/spec/compendium/param_types/param_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' +require 'compendium/param_types/param' + +describe Compendium::ParamTypes::Param do + subject{ described_class.new(:test) } + + it { is_expected.not_to be_scalar } + it { is_expected.not_to be_boolean } + it { is_expected.not_to be_date } + it { is_expected.not_to be_dropdown } + it { is_expected.not_to be_radio } + + describe "#==" do + it "should compare to the param's value" do + allow(subject).to receive_messages(value: :test_value) + expect(subject).to eq(:test_value) + end + end +end diff --git a/spec/compendium/param_types/radio_spec.rb b/spec/compendium/param_types/radio_spec.rb new file mode 100644 index 0000000..0e65a9e --- /dev/null +++ b/spec/compendium/param_types/radio_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' +require 'compendium/param_types/radio' + +describe Compendium::ParamTypes::Radio do + subject{ described_class.new(0, %w(a b c)) } + + it { is_expected.not_to be_scalar } + it { is_expected.not_to be_boolean } + it { is_expected.not_to be_date } + it { is_expected.not_to be_dropdown } + it { is_expected.to be_radio } +end diff --git a/spec/compendium/param_types/scalar_spec.rb b/spec/compendium/param_types/scalar_spec.rb new file mode 100644 index 0000000..2f00c80 --- /dev/null +++ b/spec/compendium/param_types/scalar_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' +require 'compendium/param_types/scalar' + +describe Compendium::ParamTypes::Scalar do + subject{ described_class.new(123) } + + it { is_expected.to be_scalar } + it { is_expected.not_to be_boolean } + it { is_expected.not_to be_date } + it { is_expected.not_to be_dropdown } + it { is_expected.not_to be_radio } + + it "should not change values" do + expect(subject).to eq(123) + end +end diff --git a/spec/compendium/param_types/with_choices_spec.rb b/spec/compendium/param_types/with_choices_spec.rb new file mode 100644 index 0000000..af88b9a --- /dev/null +++ b/spec/compendium/param_types/with_choices_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' +require 'compendium/param_types/with_choices' + +describe Compendium::ParamTypes::WithChoices do + subject{ described_class.new(0, %w(a b c)) } + + it { is_expected.not_to be_boolean } + it { is_expected.not_to be_date } + it { is_expected.not_to be_dropdown } + it { is_expected.not_to be_radio } + + it "should return the index when given an index" do + p = described_class.new(1, [:foo, :bar, :baz]) + expect(p).to eq(1) + end + + it "should return the index when given a value" do + p = described_class.new(:foo, [:foo, :bar, :baz]) + expect(p).to eq(0) + end + + it "should return the index when given a string value" do + p = described_class.new("2", [:foo, :bar, :baz]) + expect(p).to eq(2) + end + + it "should raise an error if given an invalid index" do + expect { described_class.new(3, [:foo, :bar, :baz]) }.to raise_error IndexError + end + + it "should raise an error if given a value that is not included in the choices" do + expect { described_class.new(:quux, [:foo, :bar, :baz]) }.to raise_error IndexError + end + + describe "#value" do + it "should return the value of the given choice" do + p = described_class.new(2, [:foo, :bar, :baz]) + expect(p.value).to eq(:baz) + end + end +end diff --git a/spec/params_spec.rb b/spec/compendium/params_spec.rb similarity index 100% rename from spec/params_spec.rb rename to spec/compendium/params_spec.rb diff --git a/spec/query_spec.rb b/spec/compendium/query_spec.rb similarity index 100% rename from spec/query_spec.rb rename to spec/compendium/query_spec.rb diff --git a/spec/report_spec.rb b/spec/compendium/report_spec.rb similarity index 100% rename from spec/report_spec.rb rename to spec/compendium/report_spec.rb diff --git a/spec/result_set_spec.rb b/spec/compendium/result_set_spec.rb similarity index 100% rename from spec/result_set_spec.rb rename to spec/compendium/result_set_spec.rb diff --git a/spec/sum_query_spec.rb b/spec/compendium/sum_query_spec.rb similarity index 100% rename from spec/sum_query_spec.rb rename to spec/compendium/sum_query_spec.rb diff --git a/spec/through_query_spec.rb b/spec/compendium/through_query_spec.rb similarity index 100% rename from spec/through_query_spec.rb rename to spec/compendium/through_query_spec.rb diff --git a/spec/param_types_spec.rb b/spec/param_types_spec.rb deleted file mode 100644 index 3c2dd3b..0000000 --- a/spec/param_types_spec.rb +++ /dev/null @@ -1,166 +0,0 @@ -require 'compendium/param_types' - -describe Compendium::Param do - subject{ described_class.new(:test) } - - it { is_expected.not_to be_scalar } - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } - - describe "#==" do - it "should compare to the param's value" do - allow(subject).to receive_messages(value: :test_value) - expect(subject).to eq(:test_value) - end - end -end - -describe Compendium::ScalarParam do - subject{ described_class.new(123) } - - it { is_expected.to be_scalar } - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } - - it "should not change values" do - expect(subject).to eq(123) - end -end - -describe Compendium::ParamWithChoices do - subject{ described_class.new(0, %w(a b c)) } - - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } - - it "should return the index when given an index" do - p = described_class.new(1, [:foo, :bar, :baz]) - expect(p).to eq(1) - end - - it "should return the index when given a value" do - p = described_class.new(:foo, [:foo, :bar, :baz]) - expect(p).to eq(0) - end - - it "should return the index when given a string value" do - p = described_class.new("2", [:foo, :bar, :baz]) - expect(p).to eq(2) - end - - it "should raise an error if given an invalid index" do - expect { described_class.new(3, [:foo, :bar, :baz]) }.to raise_error IndexError - end - - it "should raise an error if given a value that is not included in the choices" do - expect { described_class.new(:quux, [:foo, :bar, :baz]) }.to raise_error IndexError - end - - describe "#value" do - it "should return the value of the given choice" do - p = described_class.new(2, [:foo, :bar, :baz]) - expect(p.value).to eq(:baz) - end - end -end - -describe Compendium::BooleanParam do - subject{ described_class.new(true) } - - it { is_expected.not_to be_scalar } - it { is_expected.to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } - - it "should pass along 0 and 1" do - expect(described_class.new(0)).to eq(0) - expect(described_class.new(1)).to eq(1) - end - - it "should convert a numeric string to a number" do - expect(described_class.new('0')).to eq(0) - expect(described_class.new('1')).to eq(1) - end - - it "should return 0 for a truthy value" do - expect(described_class.new(true)).to eq(0) - expect(described_class.new(:abc)).to eq(0) - end - - it "should return 1 for a falsey value" do - expect(described_class.new(false)).to eq(1) - expect(described_class.new(nil)).to eq(1) - end - - describe "#value" do - it "should return true for a truthy value" do - expect(described_class.new(true).value).to eq(true) - expect(described_class.new(:abc).value).to eq(true) - expect(described_class.new(0).value).to eq(true) - end - - it "should return false for a falsey value" do - expect(described_class.new(false).value).to eq(false) - expect(described_class.new(nil).value).to eq(false) - expect(described_class.new(1).value).to eq(false) - end - end - - describe "#!" do - it "should return false if the boolean is true" do - expect(!described_class.new(true)).to eq(false) - end - - it "should return true if the boolean is false" do - expect(!described_class.new(false)).to eq(true) - end - end -end - -describe Compendium::DateParam do - subject{ described_class.new(Date.today) } - - it { is_expected.not_to be_scalar } - it { is_expected.not_to be_boolean } - it { is_expected.to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } - - it "should convert date strings to date objects" do - p = described_class.new("2010-05-20") - expect(p).to eq(Date.new(2010, 5, 20)) - end - - it "should convert other date/time formats to date objects" do - described_class.new(DateTime.new(2010, 5, 20, 10, 30, 59)) == Date.new(2010, 5, 20) - described_class.new(Time.new(2010, 5, 20, 10, 30, 59)) == Date.new(2010, 5, 20) - described_class.new(Date.new(2010, 5, 20)) == Date.new(2010, 5, 20) - end -end - -describe Compendium::DropdownParam do - subject{ described_class.new(0, %w(a b c)) } - - it { is_expected.not_to be_scalar } - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.to be_dropdown } - it { is_expected.not_to be_radio } -end - -describe Compendium::RadioParam do - subject{ described_class.new(0, %w(a b c)) } - - it { is_expected.not_to be_scalar } - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.to be_radio } -end From e8d7e6862411723505828f22e59b38def03680f6 Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Wed, 24 Jan 2018 11:26:31 -0500 Subject: [PATCH 02/23] Extract queries into a namespace --- lib/compendium.rb | 6 +- lib/compendium/collection_query.rb | 46 ----- lib/compendium/count_query.rb | 23 --- lib/compendium/dsl.rb | 14 +- lib/compendium/errors.rb | 8 +- lib/compendium/queries.rb | 9 + lib/compendium/queries/collection.rb | 48 ++++++ lib/compendium/queries/count.rb | 24 +++ lib/compendium/queries/query.rb | 158 ++++++++++++++++++ lib/compendium/queries/sum.rb | 29 ++++ lib/compendium/queries/through.rb | 54 ++++++ lib/compendium/query.rb | 156 ----------------- lib/compendium/sum_query.rb | 28 ---- lib/compendium/through_query.rb | 51 ------ spec/compendium/dsl_spec.rb | 26 +-- .../collection_spec.rb} | 8 +- .../count_spec.rb} | 8 +- spec/compendium/{ => queries}/query_spec.rb | 8 +- .../sum_spec.rb} | 8 +- .../through_spec.rb} | 10 +- spec/compendium/report_spec.rb | 2 +- 21 files changed, 370 insertions(+), 354 deletions(-) delete mode 100644 lib/compendium/collection_query.rb delete mode 100644 lib/compendium/count_query.rb create mode 100644 lib/compendium/queries.rb create mode 100644 lib/compendium/queries/collection.rb create mode 100644 lib/compendium/queries/count.rb create mode 100644 lib/compendium/queries/query.rb create mode 100644 lib/compendium/queries/sum.rb create mode 100644 lib/compendium/queries/through.rb delete mode 100644 lib/compendium/query.rb delete mode 100644 lib/compendium/sum_query.rb delete mode 100644 lib/compendium/through_query.rb rename spec/compendium/{collection_query_spec.rb => queries/collection_spec.rb} (86%) rename spec/compendium/{count_query_spec.rb => queries/count_spec.rb} (94%) rename spec/compendium/{ => queries}/query_spec.rb (96%) rename spec/compendium/{sum_query_spec.rb => queries/sum_spec.rb} (94%) rename spec/compendium/{through_query_spec.rb => queries/through_spec.rb} (90%) diff --git a/lib/compendium.rb b/lib/compendium.rb index e788e62..6fb7b9c 100644 --- a/lib/compendium.rb +++ b/lib/compendium.rb @@ -6,19 +6,15 @@ module Compendium autoload :AbstractChartProvider, 'compendium/abstract_chart_provider' autoload :ChartProvider, 'compendium/abstract_chart_provider' - autoload :CollectionQuery, 'compendium/collection_query' autoload :ContextWrapper, 'compendium/context_wrapper' - autoload :CountQuery, 'compendium/count_query' autoload :DSL, 'compendium/dsl' autoload :Metric, 'compendium/metric' autoload :Option, 'compendium/option' autoload :Params, 'compendium/params' autoload :ParamTypes, 'compendium/param_types' - autoload :Query, 'compendium/query' + autoload :Queries, 'compendium/queries' autoload :ResultSet, 'compendium/result_set' autoload :Report, 'compendium/report' - autoload :SumQuery, 'compendium/sum_query' - autoload :ThroughQuery, 'compendium/through_query' def self.reports @reports ||= [] diff --git a/lib/compendium/collection_query.rb b/lib/compendium/collection_query.rb deleted file mode 100644 index 4dde2ed..0000000 --- a/lib/compendium/collection_query.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'compendium/query' - -module Compendium - # A CollectionQuery is a Query which runs once for each in a given set of criteria - class CollectionQuery < Query - attr_accessor :collection - - def initialize(*) - super - self.collection = prepare_collection(@options[:collection]) - end - - def run(params, context = self) - collection_values = get_collection_values(context, params) - - results = collection_values.inject({}) do |r, (key, value)| - res = collect_results(context, params, key, value) - r[key] = res unless res.empty? - r - end - - # A CollectionQuery's results will be a ResultSet of ResultSets - @results = ResultSet.new(results) - end - - private - - def get_collection_values(context, params) - self.collection = get_associated_query(collection) if collection.is_a?(Symbol) - - if collection.is_a?(Query) - collection.run(params, context) unless collection.ran? - collection.results - elsif collection.is_a?(Proc) - prepare_collection(collection.call(params)) - else - collection - end - end - - def prepare_collection(collection) - return collection if collection.is_a?(Query) || collection.is_a?(Symbol) || collection.is_a?(Proc) - collection.is_a?(Hash) ? collection : Hash[collection.zip(collection)] - end - end -end diff --git a/lib/compendium/count_query.rb b/lib/compendium/count_query.rb deleted file mode 100644 index 3a604c0..0000000 --- a/lib/compendium/count_query.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'compendium/errors' -require 'compendium/query' - -module Compendium - # A CountQuery is a Query which runs an SQL count statement - # Often useful in conjunction with a grouped query - class CountQuery < Query - - def initialize(*args) - super - - @options.reverse_merge!(order: 'COUNT(*)', reverse: true) - end - - private - - def execute_command(command) - return [] if command.nil? - raise InvalidCommand unless command.respond_to?(:count) - command.count - end - end -end diff --git a/lib/compendium/dsl.rb b/lib/compendium/dsl.rb index f70780e..8417e3f 100644 --- a/lib/compendium/dsl.rb +++ b/lib/compendium/dsl.rb @@ -6,7 +6,7 @@ module Compendium module DSL def self.extended(klass) - klass.inheritable_attr :queries, default: ::Collection[Query] + klass.inheritable_attr :queries, default: ::Collection[Queries::Query] klass.inheritable_attr :options, default: ::Collection[Option] klass.inheritable_attr :exporters, default: {} end @@ -122,21 +122,21 @@ def each_query(query_names, &block) def define_query(name, opts, &block) params = [name.to_sym, opts, block] - query_type = Query + query_type = Queries::Query if opts.key?(:collection) - query_type = CollectionQuery + query_type = Queries::Collection elsif opts.key?(:through) # Ensure each through query is defined through = [opts[:through]].flatten through.each { |q| raise ArgumentError, "query #{q} is not defined" unless self.queries.include?(q.to_sym) } - query_type = ThroughQuery + query_type = Queries::Through params.insert(1, through) elsif opts.fetch(:count, false) - query_type = CountQuery + query_type = Queries::Count elsif opts.fetch(:sum, false) - query_type = SumQuery + query_type = Queries::Sum params.insert(1, opts[:sum]) end @@ -146,7 +146,7 @@ def define_query(name, opts, &block) metrics[name] = opts[:metric] if opts.key?(:metric) if queries[name] - raise CannotRedefineQueryType unless queries[name].instance_of?(query_type) + raise Queries::CannotRedefineType unless queries[name].instance_of?(query_type) queries.delete(name) end diff --git a/lib/compendium/errors.rb b/lib/compendium/errors.rb index e5fd797..9d66de3 100644 --- a/lib/compendium/errors.rb +++ b/lib/compendium/errors.rb @@ -1,6 +1,8 @@ module Compendium CompendiumError = Class.new(StandardError) - InvalidCommand = Class.new(CompendiumError) - CannotRedefineQueryType = Class.new(CompendiumError) -end \ No newline at end of file + module Queries + InvalidCommand = Class.new(CompendiumError) + CannotRedefineType = Class.new(CompendiumError) + end +end diff --git a/lib/compendium/queries.rb b/lib/compendium/queries.rb new file mode 100644 index 0000000..c6bb231 --- /dev/null +++ b/lib/compendium/queries.rb @@ -0,0 +1,9 @@ +module Compendium + module Queries + autoload :Query, 'compendium/queries/query' + autoload :Collection, 'compendium/queries/collection' + autoload :Count, 'compendium/queries/count' + autoload :Sum, 'compendium/queries/sum' + autoload :Through, 'compendium/queries/through' + end +end diff --git a/lib/compendium/queries/collection.rb b/lib/compendium/queries/collection.rb new file mode 100644 index 0000000..dc9263e --- /dev/null +++ b/lib/compendium/queries/collection.rb @@ -0,0 +1,48 @@ +require 'compendium/queries/query' + +module Compendium + module Queries + # A Collection is a Query which runs once for each in a given set of criteria + class Collection < Query + attr_accessor :collection + + def initialize(*) + super + self.collection = prepare_collection(@options[:collection]) + end + + def run(params, context = self) + collection_values = get_collection_values(context, params) + + results = collection_values.inject({}) do |r, (key, value)| + res = collect_results(context, params, key, value) + r[key] = res unless res.empty? + r + end + + # A CollectionQuery's results will be a ResultSet of ResultSets + @results = Compendium::ResultSet.new(results) + end + + private + + def get_collection_values(context, params) + self.collection = get_associated_query(collection) if collection.is_a?(Symbol) + + if collection.is_a?(Query) + collection.run(params, context) unless collection.ran? + collection.results + elsif collection.is_a?(Proc) + prepare_collection(collection.call(params)) + else + collection + end + end + + def prepare_collection(collection) + return collection if collection.is_a?(Query) || collection.is_a?(Symbol) || collection.is_a?(Proc) + collection.is_a?(Hash) ? collection : Hash[collection.zip(collection)] + end + end + end +end diff --git a/lib/compendium/queries/count.rb b/lib/compendium/queries/count.rb new file mode 100644 index 0000000..85a05c9 --- /dev/null +++ b/lib/compendium/queries/count.rb @@ -0,0 +1,24 @@ +require 'compendium/errors' +require 'compendium/queries/query' + +module Compendium + module Queries + # A Count is a Query which runs an SQL count statement + # Often useful in conjunction with a grouped query + class Count < Query + def initialize(*args) + super + + @options.reverse_merge!(order: 'COUNT(*)', reverse: true) + end + + private + + def execute_command(command) + return [] if command.nil? + raise InvalidCommand unless command.respond_to?(:count) + command.count + end + end + end +end diff --git a/lib/compendium/queries/query.rb b/lib/compendium/queries/query.rb new file mode 100644 index 0000000..1dfd7af --- /dev/null +++ b/lib/compendium/queries/query.rb @@ -0,0 +1,158 @@ +require 'compendium/result_set' +require 'compendium/params' +require 'compendium/metric' +require 'compendium/presenters/chart' +require 'compendium/presenters/table' +require 'collection_of' + +module Compendium + module Queries + class Query + attr_reader :name, :results, :metrics, :filters + attr_accessor :options, :proc, :report, :table_settings + + def initialize(*args) + @report = args.shift if arg_is_report?(args.first) + + raise ArgumentError, "wrong number of arguments (#{args.size + (@report ? 1 : 0)} for 3..4)" unless args.size == 3 + + @name, @options, @proc = args + @metrics = ::Collection[Compendium::Metric] + @filters = ::Collection[Proc] + end + + def initialize_clone(*) + super + @metrics = @metrics.clone + @filters = @filters.clone + end + + def run(params, context = self) + if report.is_a?(Class) + # If running a query directly from a class rather than an instance, the class's query should + # not be affected/modified, so run the query without a reference back to the report. + # Otherwise, if the class is subsequently instantiated, the instance will already have results. + dup.tap{ |q| q.report = nil }.run(params, context) + else + collect_results(context, params) + collect_metrics(context) + + @results + end + end + + # Get a URL for this query (format: :json set by default) + def url(params = {}) + report.url(params.merge(query: self.name)) + end + + def add_metric(name, proc, options = {}) + Compendium::Metric.new(name, self.name, proc, options).tap { |m| @metrics << m } + end + + def add_filter(filter) + @filters << filter + end + + def render_table(template, *options, &block) + Compendium::Presenters::Table.new(template, self, *options, &block).render unless empty? + end + + def render_csv(&block) + Compendium::Presenters::CSV.new(self, &block).render unless empty? + end + + # Allow access to the chart object without having to explicitly render it + def chart(template, *options, &block) + # Access the actual chart object + Compendium::Presenters::Chart.new(template, self, *options, &block) + end + + def render_chart(template, *options, &block) + # A query can be rendered regardless of if it has data or not + # Rendering a chart with no result set builds a chart scaffold which can be updated through AJAX + chart(template, *options, &block).render + end + + def ran? + !@results.nil? + end + alias_method :has_run?, :ran? + + # A query is nil if it has no proc + def nil? + proc.nil? + end + + # A query is empty if it has no results + def empty? + results.blank? + end + + private + + def collect_results(context, *params) + command = context.instance_exec(*params, &proc) if proc + command = order_command(command) if options[:order] + + results = fetch_results(command) + results = filter_results(results, *params) if filters.any? + @results = Compendium::ResultSet.new(results) if results + end + + def collect_metrics(context) + metrics.each{ |m| m.run(context, results) } unless results.empty? + end + + def fetch_results(command) + (options.fetch(:collect, nil) == :active_record) ? command : execute_command(command) + end + + def filter_results(results, params) + return unless results + + if results.respond_to? :with_indifferent_access + results = results.with_indifferent_access + else + results.map! &:with_indifferent_access + end + + filters.each do |f| + if f.arity == 2 + results = f.call(results, params) + else + results = f.call(results) + end + end + + results + end + + def order_command(command) + return command unless command.respond_to?(:order) + + command = command.order(options[:order]) + command = command.reverse_order if options.fetch(:reverse, false) + command + end + + def execute_command(command) + return [] if command.nil? + command = command.to_sql if command.respond_to?(:to_sql) + execute_query(command) + end + + def execute_query(command) + ::ActiveRecord::Base.connection.select_all(command) + end + + def arg_is_report?(arg) + arg.is_a?(Compendium::Report) || (arg.is_a?(Class) && arg < Compendium::Report) + end + + def get_associated_query(query) + query.is_a?(Query) ? query : report.queries[query] + end + end + end +end diff --git a/lib/compendium/queries/sum.rb b/lib/compendium/queries/sum.rb new file mode 100644 index 0000000..622cdec --- /dev/null +++ b/lib/compendium/queries/sum.rb @@ -0,0 +1,29 @@ +require 'compendium/errors' +require 'compendium/queries/query' + +module Compendium + module Queries + # A Sum is a Query which runs an SQL sum statement (with a given column) + # Often useful in conjunction with a grouped query and counter cache + # (alternately, see Count) + class Sum < Query + attr_accessor :column + + def initialize(*args) + @report = args.shift if arg_is_report?(args.first) + @column = args.slice!(1) + super(*args) + + @options.reverse_merge!(order: "SUM(#{@column})", reverse: true) + end + + private + + def execute_command(command) + return [] if command.nil? + raise InvalidCommand unless command.respond_to?(:sum) + command.sum(column) + end + end + end +end diff --git a/lib/compendium/queries/through.rb b/lib/compendium/queries/through.rb new file mode 100644 index 0000000..b0f53be --- /dev/null +++ b/lib/compendium/queries/through.rb @@ -0,0 +1,54 @@ +require 'compendium/queries/query' + +module Compendium + module Queries + # A Through is a Query which distills data from previously run queries (one or multiple) + class Through < Query + attr_accessor :through + + def initialize(*args) + @report = args.shift if arg_is_report?(args.first) + @through = args.slice!(1) + super(*args) + end + + private + + def collect_results(context, params) + results = collect_through_query_results(params, context) + + # If none of the through queries have any results, we shouldn't try to execute the query, because it + # depends on the results of its parents. + return @results = Compendium::ResultSet.new([]) if any_results?(results) + + # If the proc collects two arguments, pass results and params, otherwise just results + args = !proc || proc.arity == 1 ? [results] : [results, params] + + super(context, *args) + end + + def fetch_results(command) + command + end + + def collect_through_query_results(params, context) + results = {} + + queries = Array.wrap(through).map(&method(:get_associated_query)) + + queries.each do |q| + q.run(params, context) unless q.ran? + results[q.name] = q.results.records.dup + end + + results = results[queries.first.name] if queries.size == 1 + results + end + + def any_results?(results) + results = results.values if results.is_a? Hash + results.all?(&:blank?) + end + end + end +end diff --git a/lib/compendium/query.rb b/lib/compendium/query.rb deleted file mode 100644 index 1194017..0000000 --- a/lib/compendium/query.rb +++ /dev/null @@ -1,156 +0,0 @@ -require 'compendium/result_set' -require 'compendium/params' -require 'compendium/metric' -require 'compendium/presenters/chart' -require 'compendium/presenters/table' -require 'collection_of' - -module Compendium - class Query - attr_reader :name, :results, :metrics, :filters - attr_accessor :options, :proc, :report, :table_settings - - def initialize(*args) - @report = args.shift if arg_is_report?(args.first) - - raise ArgumentError, "wrong number of arguments (#{args.size + (@report ? 1 : 0)} for 3..4)" unless args.size == 3 - - @name, @options, @proc = args - @metrics = ::Collection[Metric] - @filters = ::Collection[Proc] - end - - def initialize_clone(*) - super - @metrics = @metrics.clone - @filters = @filters.clone - end - - def run(params, context = self) - if report.is_a?(Class) - # If running a query directly from a class rather than an instance, the class's query should - # not be affected/modified, so run the query without a reference back to the report. - # Otherwise, if the class is subsequently instantiated, the instance will already have results. - dup.tap{ |q| q.report = nil }.run(params, context) - else - collect_results(context, params) - collect_metrics(context) - - @results - end - end - - # Get a URL for this query (format: :json set by default) - def url(params = {}) - report.url(params.merge(query: self.name)) - end - - def add_metric(name, proc, options = {}) - Compendium::Metric.new(name, self.name, proc, options).tap { |m| @metrics << m } - end - - def add_filter(filter) - @filters << filter - end - - def render_table(template, *options, &block) - Compendium::Presenters::Table.new(template, self, *options, &block).render unless empty? - end - - def render_csv(&block) - Compendium::Presenters::CSV.new(self, &block).render unless empty? - end - - # Allow access to the chart object without having to explicitly render it - def chart(template, *options, &block) - # Access the actual chart object - Compendium::Presenters::Chart.new(template, self, *options, &block) - end - - def render_chart(template, *options, &block) - # A query can be rendered regardless of if it has data or not - # Rendering a chart with no result set builds a chart scaffold which can be updated through AJAX - chart(template, *options, &block).render - end - - def ran? - !@results.nil? - end - alias_method :has_run?, :ran? - - # A query is nil if it has no proc - def nil? - proc.nil? - end - - # A query is empty if it has no results - def empty? - results.blank? - end - - private - - def collect_results(context, *params) - command = context.instance_exec(*params, &proc) if proc - command = order_command(command) if options[:order] - - results = fetch_results(command) - results = filter_results(results, *params) if filters.any? - @results = ResultSet.new(results) if results - end - - def collect_metrics(context) - metrics.each{ |m| m.run(context, results) } unless results.empty? - end - - def fetch_results(command) - (options.fetch(:collect, nil) == :active_record) ? command : execute_command(command) - end - - def filter_results(results, params) - return unless results - - if results.respond_to? :with_indifferent_access - results = results.with_indifferent_access - else - results.map! &:with_indifferent_access - end - - filters.each do |f| - if f.arity == 2 - results = f.call(results, params) - else - results = f.call(results) - end - end - - results - end - - def order_command(command) - return command unless command.respond_to?(:order) - - command = command.order(options[:order]) - command = command.reverse_order if options.fetch(:reverse, false) - command - end - - def execute_command(command) - return [] if command.nil? - command = command.to_sql if command.respond_to?(:to_sql) - execute_query(command) - end - - def execute_query(command) - ::ActiveRecord::Base.connection.select_all(command) - end - - def arg_is_report?(arg) - arg.is_a?(Report) || (arg.is_a?(Class) && arg < Report) - end - - def get_associated_query(query) - query.is_a?(Query) ? query : report.queries[query] - end - end -end diff --git a/lib/compendium/sum_query.rb b/lib/compendium/sum_query.rb deleted file mode 100644 index 9963ca2..0000000 --- a/lib/compendium/sum_query.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'compendium/errors' -require 'compendium/query' - -module Compendium - # A SumQuery is a Query which runs an SQL sum statement (with a given column) - # Often useful in conjunction with a grouped query and counter cache - # (alternately, see CountQuery) - class SumQuery < Query - - attr_accessor :column - - def initialize(*args) - @report = args.shift if arg_is_report?(args.first) - @column = args.slice!(1) - super(*args) - - @options.reverse_merge!(order: "SUM(#{@column})", reverse: true) - end - - private - - def execute_command(command) - return [] if command.nil? - raise InvalidCommand unless command.respond_to?(:sum) - command.sum(column) - end - end -end diff --git a/lib/compendium/through_query.rb b/lib/compendium/through_query.rb deleted file mode 100644 index 164801d..0000000 --- a/lib/compendium/through_query.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'compendium/query' - -module Compendium - class ThroughQuery < Query - attr_accessor :through - - def initialize(*args) - @report = args.shift if arg_is_report?(args.first) - @through = args.slice!(1) - super(*args) - end - - private - - def collect_results(context, params) - results = collect_through_query_results(params, context) - - # If none of the through queries have any results, we shouldn't try to execute the query, because it - # depends on the results of its parents. - return @results = ResultSet.new([]) if any_results?(results) - - # If the proc collects two arguments, pass results and params, otherwise just results - args = !proc || proc.arity == 1 ? [results] : [results, params] - - super(context, *args) - end - - def fetch_results(command) - command - end - - def collect_through_query_results(params, context) - results = {} - - queries = Array.wrap(through).map(&method(:get_associated_query)) - - queries.each do |q| - q.run(params, context) unless q.ran? - results[q.name] = q.results.records.dup - end - - results = results[queries.first.name] if queries.size == 1 - results - end - - def any_results?(results) - results = results.values if results.is_a? Hash - results.all?(&:blank?) - end - end -end diff --git a/spec/compendium/dsl_spec.rb b/spec/compendium/dsl_spec.rb index 6538411..74bd75e 100644 --- a/spec/compendium/dsl_spec.rb +++ b/spec/compendium/dsl_spec.rb @@ -89,14 +89,14 @@ end it 'should not allow replacing a query with a different type' do - expect { subject.query :test, count: true }.to raise_error { Compendium::CannotRedefineQueryType } - expect(subject.test).to be_instance_of Compendium::Query + expect { subject.query :test, count: true }.to raise_error { Compendium::Queries::CannotRedefineType } + expect(subject.test).to be_instance_of Compendium::Queries::Query end it 'should allow replacing a query with the same type' do subject.query :another_test, count: true, &proc2 expect(subject.another_test.proc).to eq(proc2) - expect(subject.another_test).to be_instance_of Compendium::CountQuery + expect(subject.another_test).to be_instance_of Compendium::Queries::Count end end @@ -104,7 +104,7 @@ before { report_class.query :through, through: :test } subject { report_class.queries[:through] } - specify { is_expected.to be_a Compendium::ThroughQuery } + specify { is_expected.to be_a Compendium::Queries::Through } specify { expect(subject.through).to eq([:test]) } end @@ -114,14 +114,14 @@ context "that is an enumerable" do before { report_class.query :collection, collection: [] } - it { is_expected.to be_a Compendium::CollectionQuery } + it { is_expected.to be_a Compendium::Queries::Collection } end context "that is a symbol" do let(:query) { double("Query") } before do - allow_any_instance_of(Compendium::Query).to receive(:get_associated_query).with(:query).and_return(query) + allow_any_instance_of(Compendium::Queries::Query).to receive(:get_associated_query).with(:query).and_return(query) report_class.query :collection, collection: :query end @@ -129,7 +129,7 @@ end context "that is a query" do - let(:query) { Compendium::Query.new(:query, {}, ->{}) } + let(:query) { Compendium::Queries::Query.new(:query, {}, ->{}) } before { report_class.query :collection, collection: query } specify { expect(subject.collection).to eq(query) } @@ -141,13 +141,13 @@ context "set to true" do before { report_class.query :counted, count: true } - it { is_expected.to be_a Compendium::CountQuery } + it { is_expected.to be_a Compendium::Queries::Count } end context "set to false" do before { report_class.query :counted, count: false } - it { is_expected.to be_a Compendium::Query } - it { is_expected.not_to be_a Compendium::CountQuery } + it { is_expected.to be_a Compendium::Queries::Query } + it { is_expected.not_to be_a Compendium::Queries::Count } end end @@ -157,14 +157,14 @@ context 'set to a truthy value' do before { report_class.query :summed, sum: :assoc_count } - it { is_expected.to be_a Compendium::SumQuery } + it { is_expected.to be_a Compendium::Queries::Sum } specify { expect(subject.column).to eq(:assoc_count) } end context 'set to false' do before { report_class.query :summed, sum: false } - it { is_expected.to be_a Compendium::Query } - it { is_expected.not_to be_a Compendium::SumQuery } + it { is_expected.to be_a Compendium::Queries::Query } + it { is_expected.not_to be_a Compendium::Queries::Sum } end end end diff --git a/spec/compendium/collection_query_spec.rb b/spec/compendium/queries/collection_spec.rb similarity index 86% rename from spec/compendium/collection_query_spec.rb rename to spec/compendium/queries/collection_spec.rb index db99a9a..52b6931 100644 --- a/spec/compendium/collection_query_spec.rb +++ b/spec/compendium/queries/collection_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' -require 'compendium/collection_query' +require 'compendium/queries/collection' -describe Compendium::CollectionQuery do +describe Compendium::Queries::Collection do let(:collection) { { one: 1, two: 2, three: 3 } } subject { described_class.new(:collection_query, { collection: collection }, -> _, key, item { [item * 2] }) } - before { allow_any_instance_of(Compendium::Query).to receive(:execute_query) { |instance, cmd| cmd } } + before { allow_any_instance_of(Compendium::Queries::Query).to receive(:execute_query) { |instance, cmd| cmd } } describe "#run" do context do @@ -29,7 +29,7 @@ end context "when given another query" do - let(:q) { Compendium::Query.new(:q, {}, -> * { { one: 1, two: 2, three: 3 } }) } + let(:q) { Compendium::Queries::Query.new(:q, {}, -> * { { one: 1, two: 2, three: 3 } }) } subject { described_class.new(:collection, { collection: q }, -> _, key, item { [ item * 2 ] }) } before { subject.run(nil) if RSpec.current_example.metadata.fetch(:run_query, true) } diff --git a/spec/compendium/count_query_spec.rb b/spec/compendium/queries/count_spec.rb similarity index 94% rename from spec/compendium/count_query_spec.rb rename to spec/compendium/queries/count_spec.rb index 46706eb..cbc4ada 100644 --- a/spec/compendium/count_query_spec.rb +++ b/spec/compendium/queries/count_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' require 'compendium' -require 'compendium/count_query' +require 'compendium/queries/count' class SingleCounter def count @@ -32,7 +32,7 @@ def count end end -describe Compendium::CountQuery do +describe Compendium::Queries::Count do subject { described_class.new(:counted_query, { count: true }, -> * { @counter }) } it 'should have a default order' do @@ -71,7 +71,7 @@ def count it "should raise an error if the proc does not respond to count" do @counter = Class.new - expect { subject.run(nil, self) }.to raise_error Compendium::InvalidCommand + expect { subject.run(nil, self) }.to raise_error Compendium::Queries::InvalidCommand end end -end \ No newline at end of file +end diff --git a/spec/compendium/query_spec.rb b/spec/compendium/queries/query_spec.rb similarity index 96% rename from spec/compendium/query_spec.rb rename to spec/compendium/queries/query_spec.rb index f72e99e..d6b1447 100644 --- a/spec/compendium/query_spec.rb +++ b/spec/compendium/queries/query_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -require 'compendium/query' +require 'compendium/queries/query' -describe Compendium::Query do +describe Compendium::Queries::Query do describe "#initialize" do let(:options) { double("Options") } let(:proc) { double("Proc") } @@ -137,11 +137,11 @@ describe "#nil?" do it "should return true if the query's proc is nil" do - expect(Compendium::Query.new(:test, {}, nil)).to be_nil + expect(Compendium::Queries::Query.new(:test, {}, nil)).to be_nil end it "should return false if the query's proc is not nil" do - expect(Compendium::Query.new(:test, {}, ->{})).not_to be_nil + expect(Compendium::Queries::Query.new(:test, {}, ->{})).not_to be_nil end end diff --git a/spec/compendium/sum_query_spec.rb b/spec/compendium/queries/sum_spec.rb similarity index 94% rename from spec/compendium/sum_query_spec.rb rename to spec/compendium/queries/sum_spec.rb index d108f49..a48fe7f 100644 --- a/spec/compendium/sum_query_spec.rb +++ b/spec/compendium/queries/sum_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -require 'compendium/sum_query' +require 'compendium/queries/sum' require 'compendium/report' class SingleSummer @@ -32,7 +32,7 @@ def sum(col) end end -describe Compendium::SumQuery do +describe Compendium::Queries::Sum do subject { described_class.new(:counted_query, :col, { sum: :col }, -> * { @counter }) } it 'should have a default order' do @@ -71,7 +71,7 @@ def sum(col) it "should raise an error if the proc does not respond to sum" do @counter = Class.new - expect { subject.run(nil, self) }.to raise_error Compendium::InvalidCommand + expect { subject.run(nil, self) }.to raise_error Compendium::Queries::InvalidCommand end end -end \ No newline at end of file +end diff --git a/spec/compendium/through_query_spec.rb b/spec/compendium/queries/through_spec.rb similarity index 90% rename from spec/compendium/through_query_spec.rb rename to spec/compendium/queries/through_spec.rb index e00faeb..38485a1 100644 --- a/spec/compendium/through_query_spec.rb +++ b/spec/compendium/queries/through_spec.rb @@ -1,6 +1,6 @@ -require 'compendium/through_query' +require 'compendium/queries/through' -describe Compendium::ThroughQuery do +describe Compendium::Queries::Through do describe "#initialize" do let(:options) { double("Options") } let(:proc) { double("Proc") } @@ -29,9 +29,9 @@ end describe "#run" do - let(:parent1) { Compendium::Query.new(:parent1, {}, -> * { }) } - let(:parent2) { Compendium::Query.new(:parent2, {}, -> * { }) } - let(:parent3) { Compendium::Query.new(:parent3, {}, -> * { [[1, 2, 3]] }) } + let(:parent1) { Compendium::Queries::Query.new(:parent1, {}, -> * { }) } + let(:parent2) { Compendium::Queries::Query.new(:parent2, {}, -> * { }) } + let(:parent3) { Compendium::Queries::Query.new(:parent3, {}, -> * { [[1, 2, 3]] }) } before { allow(parent3).to receive(:execute_query) { |cmd| cmd } } diff --git a/spec/compendium/report_spec.rb b/spec/compendium/report_spec.rb index c8fd310..40813ea 100644 --- a/spec/compendium/report_spec.rb +++ b/spec/compendium/report_spec.rb @@ -54,7 +54,7 @@ let!(:report2) { report_class.new } before do - allow_any_instance_of(Compendium::Query).to receive(:fetch_results) { |instance, c| c } + allow_any_instance_of(Compendium::Queries::Query).to receive(:fetch_results) { |instance, c| c } subject.run end From 02ac3a50df46b691cae1df9a6fefbf3c3876c75c Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Wed, 24 Jan 2018 13:29:02 -0500 Subject: [PATCH 03/23] Move presenters to lib --- app/classes/compendium/presenters/csv.rb | 32 ----- app/classes/compendium/presenters/metric.rb | 32 ----- app/classes/compendium/presenters/option.rb | 112 ----------------- app/classes/compendium/presenters/query.rb | 27 ----- .../compendium/presenters/settings/query.rb | 30 ----- .../compendium/presenters/settings/table.rb | 51 -------- app/classes/compendium/presenters/table.rb | 96 --------------- lib/compendium.rb | 3 +- lib/compendium/presenters.rb | 12 ++ .../compendium/presenters/base.rb | 14 +-- .../compendium/presenters/chart.rb | 0 lib/compendium/presenters/csv.rb | 34 ++++++ lib/compendium/presenters/metric.rb | 34 ++++++ lib/compendium/presenters/option.rb | 114 ++++++++++++++++++ lib/compendium/presenters/query.rb | 29 +++++ lib/compendium/presenters/settings.rb | 8 ++ lib/compendium/presenters/settings/query.rb | 37 ++++++ lib/compendium/presenters/settings/table.rb | 55 +++++++++ lib/compendium/presenters/table.rb | 104 ++++++++++++++++ spec/{ => compendium}/presenters/base_spec.rb | 2 +- .../{ => compendium}/presenters/chart_spec.rb | 8 +- spec/{ => compendium}/presenters/csv_spec.rb | 0 .../presenters/option_spec.rb | 1 - .../presenters/settings/query_spec.rb | 0 .../presenters/settings/table_spec.rb | 0 .../{ => compendium}/presenters/table_spec.rb | 0 26 files changed, 441 insertions(+), 394 deletions(-) delete mode 100644 app/classes/compendium/presenters/csv.rb delete mode 100644 app/classes/compendium/presenters/metric.rb delete mode 100644 app/classes/compendium/presenters/option.rb delete mode 100644 app/classes/compendium/presenters/query.rb delete mode 100644 app/classes/compendium/presenters/settings/query.rb delete mode 100644 app/classes/compendium/presenters/settings/table.rb delete mode 100644 app/classes/compendium/presenters/table.rb create mode 100644 lib/compendium/presenters.rb rename {app/classes => lib}/compendium/presenters/base.rb (99%) rename {app/classes => lib}/compendium/presenters/chart.rb (100%) create mode 100644 lib/compendium/presenters/csv.rb create mode 100644 lib/compendium/presenters/metric.rb create mode 100644 lib/compendium/presenters/option.rb create mode 100644 lib/compendium/presenters/query.rb create mode 100644 lib/compendium/presenters/settings.rb create mode 100644 lib/compendium/presenters/settings/query.rb create mode 100644 lib/compendium/presenters/settings/table.rb create mode 100644 lib/compendium/presenters/table.rb rename spec/{ => compendium}/presenters/base_spec.rb (99%) rename spec/{ => compendium}/presenters/chart_spec.rb (89%) rename spec/{ => compendium}/presenters/csv_spec.rb (100%) rename spec/{ => compendium}/presenters/option_spec.rb (99%) rename spec/{ => compendium}/presenters/settings/query_spec.rb (100%) rename spec/{ => compendium}/presenters/settings/table_spec.rb (100%) rename spec/{ => compendium}/presenters/table_spec.rb (100%) diff --git a/app/classes/compendium/presenters/csv.rb b/app/classes/compendium/presenters/csv.rb deleted file mode 100644 index f344755..0000000 --- a/app/classes/compendium/presenters/csv.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'csv' - -module Compendium::Presenters - class CSV < Table - def initialize(object, &block) - super(nil, object, &block) - end - - def render - ::CSV.generate do |csv| - csv << headings.map{ |_, val| formatted_heading(val) } - - records.each do |row| - csv << row.map{ |key, val| formatted_value(key, val) } - end - - if has_totals_row? - totals[totals.keys.first] = translate(:total) - csv << totals.map do |key, val| - formatted_value(key, val) unless settings.skipped_total_cols.include?(key.to_sym) - end - end - end - end - - private - - def settings_class - Compendium::Presenters::Settings::Table - end - end -end diff --git a/app/classes/compendium/presenters/metric.rb b/app/classes/compendium/presenters/metric.rb deleted file mode 100644 index 815c335..0000000 --- a/app/classes/compendium/presenters/metric.rb +++ /dev/null @@ -1,32 +0,0 @@ -module Compendium::Presenters - class Metric < Base - presents :metric - - delegate :name, :query, :description, :ran?, to: :metric - - def initialize(template, object, options = {}) - super(template, object) - @options = options - end - - def label - @options[:label] || t("#{query}.#{name}") - end - - def description - @options[:description] - end - - def result(number_format = '%0.1f', display_nil_as = :na) - if metric.result - sprintf(number_format, metric.result) - else - t(display_nil_as) - end - end - - def render - @template.render 'compendium/reports/metric', metric: self - end - end -end \ No newline at end of file diff --git a/app/classes/compendium/presenters/option.rb b/app/classes/compendium/presenters/option.rb deleted file mode 100644 index df93255..0000000 --- a/app/classes/compendium/presenters/option.rb +++ /dev/null @@ -1,112 +0,0 @@ -module Compendium::Presenters - class Option < Base - MISSING_CHOICES_ERROR = "choices must be specified" - - presents :option - delegate :hidden?, to: :option - - def name - t("options.#{option.name}", cascade: { offset: 2 }) - end - - def label(form) - if option.note? - key = option.note == true ? :"#{option.name}_note" : option.note - note = t("options.#{key}", cascade: { offset: 2 }) - end - - if option.note? && defined?(AccessibleTooltip) - title = t("options.#{option.name}_note_title", default: '', cascade: { offset: 2 }) - tooltip = accessible_tooltip(:help, label: name, title: title) { note } - return form.label option.name, tooltip - else - label = case option.type.to_sym - when :boolean, :radio - name - - else - form.label option.name, name - end - - out = ActiveSupport::SafeBuffer.new - out << content_tag(:span, label, class: 'option-label') - out << content_tag(:div, note, class: 'option-note') if option.note? - out - end - end - - def note - if option.note? - key = option.note === true ? :"#{option.name}_note" : option.note - content_tag(:div, t(key), class: 'option-note') - end - end - - def input(ctx, form) - out = ActiveSupport::SafeBuffer.new - - case option.type.to_sym - when :scalar - out << scalar_field(form) - - when :date - out << date_field(form) - - when :dropdown - raise ArgumentError, MISSING_CHOICES_ERROR unless option.choices - - choices = option.choices - choices = ctx.instance_exec(&choices) if choices.respond_to?(:call) - out << dropdown(form, choices, option.options) - - when :boolean, :radio - choices = if option.radio? - raise ArgumentError, MISSING_CHOICES_ERROR unless option.choices - option.choices - else - %w(true false) - end - - choices.each.with_index { |choice, index| out << radio_button(form, choice, index) } - end - - out - end - - def hidden_field(form) - form.hidden_field option.name - end - - private - - def date_field(form, include_time = false) - content_tag('div', class: 'option-date') do - if defined?(CalendarDateSelect) - form.calendar_date_select option.name, time: include_time, popup: 'force' - else - form.text_field option.name - end - end - end - - def scalar_field(form) - content_tag('div', class: 'option-scalar') do - form.text_field option.name - end - end - - def dropdown(form, choices = {}, options = {}) - content_tag('div', class: 'option-dropdown') do - form.select option.name, choices, options.symbolize_keys - end - end - - def radio_button(form, label, value) - content_tag('div', class: 'option-radio') do - div_content = ActiveSupport::SafeBuffer.new - div_content << form.radio_button(option.name, value) - div_content << form.label(option.name, t(label), value: value) - end - end - end -end diff --git a/app/classes/compendium/presenters/query.rb b/app/classes/compendium/presenters/query.rb deleted file mode 100644 index 9adfc0d..0000000 --- a/app/classes/compendium/presenters/query.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'compendium/presenters/base' -require 'compendium/presenters/settings/query' -require 'compendium/presenters/settings/table' - -module Compendium::Presenters - class Query < Base - presents :query - - def initialize(template, object) - super(template, object) - end - - def render - raise NotImplementedError - end - - private - - def results - query.results - end - - def settings_class - Settings.const_get(self.class.name.demodulize, false) rescue Settings::Query - end - end -end diff --git a/app/classes/compendium/presenters/settings/query.rb b/app/classes/compendium/presenters/settings/query.rb deleted file mode 100644 index 115943f..0000000 --- a/app/classes/compendium/presenters/settings/query.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Compendium::Presenters::Settings - class Query - attr_reader :query - - delegate :[], :fetch, to: :@settings - delegate :report, to: :query, allow_nil: true - - def initialize(query = nil) - @settings = {}.with_indifferent_access - @query = query - end - - def update(&block) - instance_exec(self, &block) - end - - def method_missing(name, *args, &block) - if block_given? - @settings[name] = block.call(*args) - elsif !args.empty? - @settings[name] = args.length == 1 ? args.first : args - elsif name.to_s.end_with?('?') - prefix = name.to_s.gsub(/\?\z/, '') - @settings.key?(prefix) - else - @settings[name] - end - end - end -end diff --git a/app/classes/compendium/presenters/settings/table.rb b/app/classes/compendium/presenters/settings/table.rb deleted file mode 100644 index 3ec5a61..0000000 --- a/app/classes/compendium/presenters/settings/table.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'compendium/presenters/settings/query' - -module Compendium::Presenters::Settings - class Table < Query - attr_reader :headings - - def initialize(*) - super - - @headings = {} - - # Set default values for settings - number_format '%0.2f' - table_class 'results' - header_class 'headings' - row_class 'data' - totals_class 'totals' - skipped_total_cols [] - end - - def set_headings(headings) - headings.map!(&:to_sym) - @headings = Hash[headings.zip(headings)].with_indifferent_access - end - - def override_heading(*args, &block) - if block_given? - @headings.each do |key, val| - res = yield val.to_s - @headings[key] = res if res - end - else - col, label = args - @headings[col] = label - end - end - - def format(column, &block) - @settings[:formatters] ||= {} - @settings[:formatters][column] = block - end - - def formatters - (@settings[:formatters] || {}) - end - - def skip_total_for(*cols) - @settings[:skipped_total_cols].concat(cols.map(&:to_sym)) - end - end -end diff --git a/app/classes/compendium/presenters/table.rb b/app/classes/compendium/presenters/table.rb deleted file mode 100644 index 907f081..0000000 --- a/app/classes/compendium/presenters/table.rb +++ /dev/null @@ -1,96 +0,0 @@ -module Compendium::Presenters - class Table < Query - attr_reader :records, :totals, :settings - - def initialize(*) - super - - @records = results.records - - @settings = settings_class.new(query) - @settings.set_headings(results.keys) - @settings.update(&query.table_settings) if query.table_settings - yield @settings if block_given? - - if has_totals_row? - @totals = @records.pop - totals[totals.keys.first] = translate(:total) - end - end - - def render - content_tag(:table, class: @settings.table_class) do - table = ActiveSupport::SafeBuffer.new - table << content_tag(:thead, build_row(headings, settings.header_class, :th, &heading_proc)) - table << content_tag(:tbody) do - tbody = ActiveSupport::SafeBuffer.new - records.each { |row| tbody << build_row(row, settings.row_class, &data_proc) } - tbody - end - table << content_tag(:tfoot, build_row(totals, @settings.totals_class, :th, &totals_proc)) if has_totals_row? - table - end - end - - private - - def headings - @settings.headings - end - - def has_totals_row? - query.options.fetch(:totals, false) - end - - def data_proc - proc { |key, val| formatted_value(key, val) } - end - - def heading_proc - proc { |_, val| formatted_heading(val) } - end - - def totals_proc - proc { |key, val| formatted_value(key, val) unless settings.skipped_total_cols.include?(key.to_sym) } - end - - def build_row(row, row_class, cell_type = :td) - content_tag(:tr, class: row_class) do - out = ActiveSupport::SafeBuffer.new - - row.each.with_index do |(key, val), i| - val = yield key, val, i if block_given? - out << content_tag(cell_type, val) - end - - out - end - end - - def formatted_heading(v) - v.is_a?(Symbol) ? translate(v) : v - end - - def formatted_value(k, v) - if @settings.formatters[k] - @settings.formatters[k].call(v) - else - if v.numeric? - if v.zero? && @settings.display_zero_as? - @settings.display_zero_as - else - sprintf(@settings.number_format, v) - end - elsif v.nil? - @settings.display_nil_as - end - end || v - end - - def translate(v, opts = {}) - opts.reverse_merge!(scope: settings.i18n_scope) if settings.i18n_scope? - opts[:default] = -> * { I18n.t(v, scope: 'compendium') } - I18n.t(v, opts) - end - end -end diff --git a/lib/compendium.rb b/lib/compendium.rb index 6fb7b9c..da717f1 100644 --- a/lib/compendium.rb +++ b/lib/compendium.rb @@ -12,6 +12,7 @@ module Compendium autoload :Option, 'compendium/option' autoload :Params, 'compendium/params' autoload :ParamTypes, 'compendium/param_types' + autoload :Presenters, 'compendium/presenters' autoload :Queries, 'compendium/queries' autoload :ResultSet, 'compendium/result_set' autoload :Report, 'compendium/report' @@ -24,7 +25,7 @@ def self.reports # Compendium.configure do |config| # config.chart_provider = :AmCharts # end - def self.configure(&block) + def self.configure yield @config ||= Compendium::Configuration.new end diff --git a/lib/compendium/presenters.rb b/lib/compendium/presenters.rb new file mode 100644 index 0000000..8e39411 --- /dev/null +++ b/lib/compendium/presenters.rb @@ -0,0 +1,12 @@ +module Compendium + module Presenters + autoload :Base, 'compendium/presenters/base' + autoload :Chart, 'compendium/presenters/chart' + autoload :CSV, 'compendium/presenters/csv' + autoload :Metric, 'compendium/presenters/metric' + autoload :Option, 'compendium/presenters/option' + autoload :Query, 'compendium/presenters/query' + autoload :Table, 'compendium/presenters/table' + autoload :Settings, 'compendium/presenters/settings' + end +end diff --git a/app/classes/compendium/presenters/base.rb b/lib/compendium/presenters/base.rb similarity index 99% rename from app/classes/compendium/presenters/base.rb rename to lib/compendium/presenters/base.rb index 526cd22..bcc3deb 100644 --- a/app/classes/compendium/presenters/base.rb +++ b/lib/compendium/presenters/base.rb @@ -1,5 +1,11 @@ module Compendium::Presenters class Base + def self.presents(name) + define_method(name) do + @object + end + end + def initialize(template, object) @object = object @template = template @@ -11,12 +17,6 @@ def to_s private - def self.presents(name) - define_method(name) do - @object - end - end - def method_missing(*args, &block) return @template.send(*args, &block) if @template.respond_to?(args.first) super @@ -27,4 +27,4 @@ def respond_to_missing?(*args) super end end -end \ No newline at end of file +end diff --git a/app/classes/compendium/presenters/chart.rb b/lib/compendium/presenters/chart.rb similarity index 100% rename from app/classes/compendium/presenters/chart.rb rename to lib/compendium/presenters/chart.rb diff --git a/lib/compendium/presenters/csv.rb b/lib/compendium/presenters/csv.rb new file mode 100644 index 0000000..dc7682c --- /dev/null +++ b/lib/compendium/presenters/csv.rb @@ -0,0 +1,34 @@ +require 'csv' + +module Compendium + module Presenters + class CSV < Table + def initialize(object, &block) + super(nil, object, &block) + end + + def render + ::CSV.generate do |csv| + csv << headings.map { |_, val| formatted_heading(val) } + + records.each do |row| + csv << row.map { |key, val| formatted_value(key, val) } + end + + if has_totals_row? + totals[totals.keys.first] = translate(:total) + csv << totals.map do |key, val| + formatted_value(key, val) unless settings.skipped_total_cols.include?(key.to_sym) + end + end + end + end + + private + + def settings_class + Compendium::Presenters::Settings::Table + end + end + end +end diff --git a/lib/compendium/presenters/metric.rb b/lib/compendium/presenters/metric.rb new file mode 100644 index 0000000..ae384ca --- /dev/null +++ b/lib/compendium/presenters/metric.rb @@ -0,0 +1,34 @@ +module Compendium + module Presenters + class Metric < Base + presents :metric + + delegate :name, :query, :description, :ran?, to: :metric + + def initialize(template, object, options = {}) + super(template, object) + @options = options + end + + def label + @options[:label] || t("#{query}.#{name}") + end + + def description + @options[:description] + end + + def result(number_format = '%0.1f', display_nil_as = :na) + if metric.result + sprintf(number_format, metric.result) + else + t(display_nil_as) + end + end + + def render + @template.render 'compendium/reports/metric', metric: self + end + end + end +end diff --git a/lib/compendium/presenters/option.rb b/lib/compendium/presenters/option.rb new file mode 100644 index 0000000..49be584 --- /dev/null +++ b/lib/compendium/presenters/option.rb @@ -0,0 +1,114 @@ +module Compendium + module Presenters + class Option < Base + MISSING_CHOICES_ERROR = "choices must be specified" + + presents :option + delegate :hidden?, to: :option + + def name + t("options.#{option.name}", cascade: { offset: 2 }) + end + + def label(form) + if option.note? + key = option.note == true ? :"#{option.name}_note" : option.note + note = t("options.#{key}", cascade: { offset: 2 }) + end + + if option.note? && defined?(AccessibleTooltip) + title = t("options.#{option.name}_note_title", default: '', cascade: { offset: 2 }) + tooltip = accessible_tooltip(:help, label: name, title: title) { note } + return form.label option.name, tooltip + else + label = case option.type.to_sym + when :boolean, :radio + name + + else + form.label option.name, name + end + + out = ActiveSupport::SafeBuffer.new + out << content_tag(:span, label, class: 'option-label') + out << content_tag(:div, note, class: 'option-note') if option.note? + out + end + end + + def note + if option.note? + key = option.note === true ? :"#{option.name}_note" : option.note + content_tag(:div, t(key), class: 'option-note') + end + end + + def input(ctx, form) + out = ActiveSupport::SafeBuffer.new + + case option.type.to_sym + when :scalar + out << scalar_field(form) + + when :date + out << date_field(form) + + when :dropdown + raise ArgumentError, MISSING_CHOICES_ERROR unless option.choices + + choices = option.choices + choices = ctx.instance_exec(&choices) if choices.respond_to?(:call) + out << dropdown(form, choices, option.options) + + when :boolean, :radio + choices = if option.radio? + raise ArgumentError, MISSING_CHOICES_ERROR unless option.choices + option.choices + else + %w(true false) + end + + choices.each.with_index { |choice, index| out << radio_button(form, choice, index) } + end + + out + end + + def hidden_field(form) + form.hidden_field option.name + end + + private + + def date_field(form, include_time = false) + content_tag('div', class: 'option-date') do + if defined?(CalendarDateSelect) + form.calendar_date_select option.name, time: include_time, popup: 'force' + else + form.text_field option.name + end + end + end + + def scalar_field(form) + content_tag('div', class: 'option-scalar') do + form.text_field option.name + end + end + + def dropdown(form, choices = {}, options = {}) + content_tag('div', class: 'option-dropdown') do + form.select option.name, choices, options.symbolize_keys + end + end + + def radio_button(form, label, value) + content_tag('div', class: 'option-radio') do + div_content = ActiveSupport::SafeBuffer.new + div_content << form.radio_button(option.name, value) + div_content << form.label(option.name, t(label), value: value) + end + end + end + end +end diff --git a/lib/compendium/presenters/query.rb b/lib/compendium/presenters/query.rb new file mode 100644 index 0000000..fc4c574 --- /dev/null +++ b/lib/compendium/presenters/query.rb @@ -0,0 +1,29 @@ +require 'compendium/presenters/base' +require 'compendium/presenters/settings/query' +require 'compendium/presenters/settings/table' + +module Compendium + module Presenters + class Query < Base + presents :query + + def initialize(template, object) + super(template, object) + end + + def render + raise NotImplementedError + end + + private + + def results + query.results + end + + def settings_class + Settings::Query + end + end + end +end diff --git a/lib/compendium/presenters/settings.rb b/lib/compendium/presenters/settings.rb new file mode 100644 index 0000000..06c72f3 --- /dev/null +++ b/lib/compendium/presenters/settings.rb @@ -0,0 +1,8 @@ +module Compendium + module Presenters + module Settings + autoload :Query, 'compendium/presenters/settings/query' + autoload :Table, 'compendium/presenters/settings/table' + end + end +end diff --git a/lib/compendium/presenters/settings/query.rb b/lib/compendium/presenters/settings/query.rb new file mode 100644 index 0000000..005ce0f --- /dev/null +++ b/lib/compendium/presenters/settings/query.rb @@ -0,0 +1,37 @@ +require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/hash/indifferent_access' + +module Compendium + module Presenters + module Settings + class Query + attr_reader :query + + delegate :[], :fetch, to: :@settings + delegate :report, to: :query, allow_nil: true + + def initialize(query = nil) + @settings = {}.with_indifferent_access + @query = query + end + + def update(&block) + instance_exec(self, &block) + end + + def method_missing(name, *args, &block) + if block_given? + @settings[name] = block.call(*args) + elsif !args.empty? + @settings[name] = args.length == 1 ? args.first : args + elsif name.to_s.end_with?('?') + prefix = name.to_s.gsub(/\?\z/, '') + @settings.key?(prefix) + else + @settings[name] + end + end + end + end + end +end diff --git a/lib/compendium/presenters/settings/table.rb b/lib/compendium/presenters/settings/table.rb new file mode 100644 index 0000000..47d0f63 --- /dev/null +++ b/lib/compendium/presenters/settings/table.rb @@ -0,0 +1,55 @@ +require 'compendium/presenters/settings/query' + +module Compendium + module Presenters + module Settings + class Table < Query + attr_reader :headings + + def initialize(*) + super + + @headings = {} + + # Set default values for settings + number_format '%0.2f' + table_class 'results' + header_class 'headings' + row_class 'data' + totals_class 'totals' + skipped_total_cols [] + end + + def set_headings(headings) + headings.map!(&:to_sym) + @headings = Hash[headings.zip(headings)].with_indifferent_access + end + + def override_heading(*args) + if block_given? + @headings.each do |key, val| + res = yield val.to_s + @headings[key] = res if res + end + else + col, label = args + @headings[col] = label + end + end + + def format(column, &block) + @settings[:formatters] ||= {} + @settings[:formatters][column] = block + end + + def formatters + (@settings[:formatters] || {}) + end + + def skip_total_for(*cols) + @settings[:skipped_total_cols].concat(cols.map(&:to_sym)) + end + end + end + end +end diff --git a/lib/compendium/presenters/table.rb b/lib/compendium/presenters/table.rb new file mode 100644 index 0000000..dfc5ad0 --- /dev/null +++ b/lib/compendium/presenters/table.rb @@ -0,0 +1,104 @@ +require 'compendium/presenters/query' + +module Compendium + module Presenters + class Table < Query + attr_reader :records, :totals, :settings + + def initialize(*) + super + + @records = results.records + + @settings = settings_class.new(query) + @settings.set_headings(results.keys) + @settings.update(&query.table_settings) if query.table_settings + yield @settings if block_given? + + if has_totals_row? + @totals = @records.pop + totals[totals.keys.first] = translate(:total) + end + end + + def render + content_tag(:table, class: @settings.table_class) do + table = ActiveSupport::SafeBuffer.new + table << content_tag(:thead, build_row(headings, settings.header_class, :th, &heading_proc)) + table << content_tag(:tbody) do + tbody = ActiveSupport::SafeBuffer.new + records.each { |row| tbody << build_row(row, settings.row_class, &data_proc) } + tbody + end + table << content_tag(:tfoot, build_row(totals, @settings.totals_class, :th, &totals_proc)) if has_totals_row? + table + end + end + + private + + def headings + @settings.headings + end + + def has_totals_row? + query.options.fetch(:totals, false) + end + + def data_proc + proc { |key, val| formatted_value(key, val) } + end + + def heading_proc + proc { |_, val| formatted_heading(val) } + end + + def totals_proc + proc { |key, val| formatted_value(key, val) unless settings.skipped_total_cols.include?(key.to_sym) } + end + + def build_row(row, row_class, cell_type = :td) + content_tag(:tr, class: row_class) do + out = ActiveSupport::SafeBuffer.new + + row.each.with_index do |(key, val), i| + val = yield key, val, i if block_given? + out << content_tag(cell_type, val) + end + + out + end + end + + def formatted_heading(v) + v.is_a?(Symbol) ? translate(v) : v + end + + def formatted_value(k, v) + if @settings.formatters[k] + @settings.formatters[k].call(v) + else + if v.numeric? + if v.zero? && @settings.display_zero_as? + @settings.display_zero_as + else + sprintf(@settings.number_format, v) + end + elsif v.nil? + @settings.display_nil_as + end + end || v + end + + def translate(v, opts = {}) + opts.reverse_merge!(scope: settings.i18n_scope) if settings.i18n_scope? + opts[:default] = -> * { I18n.t(v, scope: 'compendium') } + I18n.t(v, opts) + end + + def settings_class + Settings::Table + end + end + end +end diff --git a/spec/presenters/base_spec.rb b/spec/compendium/presenters/base_spec.rb similarity index 99% rename from spec/presenters/base_spec.rb rename to spec/compendium/presenters/base_spec.rb index 6d83ed5..a8349ca 100644 --- a/spec/presenters/base_spec.rb +++ b/spec/compendium/presenters/base_spec.rb @@ -17,4 +17,4 @@ expect(template).to receive(:delegated?) expect(subject).to be_delegated end -end \ No newline at end of file +end diff --git a/spec/presenters/chart_spec.rb b/spec/compendium/presenters/chart_spec.rb similarity index 89% rename from spec/presenters/chart_spec.rb rename to spec/compendium/presenters/chart_spec.rb index b79d45a..ee0bc06 100644 --- a/spec/presenters/chart_spec.rb +++ b/spec/compendium/presenters/chart_spec.rb @@ -13,14 +13,14 @@ describe '#initialize' do context 'when all params are given' do - subject{ described_class.new(template, query, :pie, :container) } + subject { described_class.new(template, query, :pie, :container) } specify { expect(subject.data).to eq(results.records) } specify { expect(subject.container).to eq(:container) } end context 'when container is not given' do - subject{ described_class.new(template, query, :pie) } + subject { described_class.new(template, query, :pie) } specify { expect(subject.data).to eq(results.records) } specify { expect(subject.container).to eq('test_query') } @@ -28,7 +28,7 @@ context "when options are given" do before { allow(results).to receive(:records) { { one: [] } } } - subject{ described_class.new(template, query, :pie, index: :one) } + subject { described_class.new(template, query, :pie, index: :one) } specify { expect(subject.data).to eq(results.records[:one]) } specify { expect(subject.container).to eq('test_query') } @@ -37,7 +37,7 @@ context "when the query has not been run" do before { allow(query).to receive_messages(ran?: false, url: '/path/to/query.json') } - subject{ described_class.new(template, query, :pie, params: { foo: 'bar' }) } + subject { described_class.new(template, query, :pie, params: { foo: 'bar' }) } specify { expect(subject.data).to eq('/path/to/query.json') } specify { expect(subject.params).to eq({ report: { foo: 'bar' } }) } diff --git a/spec/presenters/csv_spec.rb b/spec/compendium/presenters/csv_spec.rb similarity index 100% rename from spec/presenters/csv_spec.rb rename to spec/compendium/presenters/csv_spec.rb diff --git a/spec/presenters/option_spec.rb b/spec/compendium/presenters/option_spec.rb similarity index 99% rename from spec/presenters/option_spec.rb rename to spec/compendium/presenters/option_spec.rb index a6ab611..5a316c4 100644 --- a/spec/presenters/option_spec.rb +++ b/spec/compendium/presenters/option_spec.rb @@ -46,4 +46,3 @@ end end end - diff --git a/spec/presenters/settings/query_spec.rb b/spec/compendium/presenters/settings/query_spec.rb similarity index 100% rename from spec/presenters/settings/query_spec.rb rename to spec/compendium/presenters/settings/query_spec.rb diff --git a/spec/presenters/settings/table_spec.rb b/spec/compendium/presenters/settings/table_spec.rb similarity index 100% rename from spec/presenters/settings/table_spec.rb rename to spec/compendium/presenters/settings/table_spec.rb diff --git a/spec/presenters/table_spec.rb b/spec/compendium/presenters/table_spec.rb similarity index 100% rename from spec/presenters/table_spec.rb rename to spec/compendium/presenters/table_spec.rb From 5ef1178bbc9e4a4706e47c7b46a2794e39f61b38 Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Wed, 24 Jan 2018 13:39:21 -0500 Subject: [PATCH 04/23] Add tests for override_heading --- spec/compendium/presenters/settings/table_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/compendium/presenters/settings/table_spec.rb b/spec/compendium/presenters/settings/table_spec.rb index 0a4a05a..046d8b1 100644 --- a/spec/compendium/presenters/settings/table_spec.rb +++ b/spec/compendium/presenters/settings/table_spec.rb @@ -65,4 +65,18 @@ expect(subject.skipped_total_cols).to eq([:foo]) end end + + describe '#override_heading' do + it 'should override a given heading' do + subject.override_heading :one, 'First Column' + expect(subject.headings).to eq('one' => 'First Column', 'two' => :two) + end + + it 'should override multiple headings with a block' do + subject.override_heading do |col| + col.to_s * 2 + end + expect(subject.headings).to eq('one' => 'oneone', 'two' => 'twotwo') + end + end end From acddc913a4aa56053a89b9ea2be412cbff197924 Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Fri, 26 Jan 2018 13:11:17 -0500 Subject: [PATCH 05/23] Add rubocop, fix offenses --- .rubocop.yml | 104 ++++++++++++ .rubocop_todo.yml | 32 ++++ Rakefile | 4 +- .../compendium/reports_controller.rb | 29 ++-- app/helpers/compendium/reports_helper.rb | 7 +- compendium.gemspec | 28 ++-- .../connection_adapters/quoting.rb | 20 ++- config/initializers/ruby/numeric.rb | 2 +- lib/compendium/abstract_chart_provider.rb | 2 +- lib/compendium/context_wrapper.rb | 2 +- lib/compendium/dsl.rb | 38 +++-- lib/compendium/engine.rb | 2 +- lib/compendium/metric.rb | 2 +- lib/compendium/open_hash.rb | 20 +-- lib/compendium/option.rb | 9 +- lib/compendium/param_types/boolean.rb | 13 +- lib/compendium/param_types/date.rb | 6 +- lib/compendium/presenters/base.rb | 44 +++--- lib/compendium/presenters/chart.rb | 130 +++++++-------- lib/compendium/presenters/csv.rb | 2 +- lib/compendium/presenters/option.rb | 106 +++++++------ lib/compendium/presenters/settings/query.rb | 8 +- lib/compendium/presenters/settings/table.rb | 2 +- lib/compendium/presenters/table.rb | 32 ++-- lib/compendium/queries/collection.rb | 3 +- lib/compendium/queries/query.rb | 40 ++--- lib/compendium/queries/query/render.rb | 27 ++++ lib/compendium/report.rb | 26 +-- lib/compendium/result_set.rb | 7 +- lib/compendium/version.rb | 2 +- spec/compendium/context_wrapper_spec.rb | 12 +- spec/compendium/dsl_spec.rb | 96 ++++++------ spec/compendium/metric_spec.rb | 52 +++--- spec/compendium/option_spec.rb | 8 +- spec/compendium/param_types/boolean_spec.rb | 22 +-- spec/compendium/param_types/date_spec.rb | 14 +- spec/compendium/param_types/dropdown_spec.rb | 2 +- spec/compendium/param_types/param_spec.rb | 4 +- spec/compendium/param_types/radio_spec.rb | 2 +- spec/compendium/param_types/scalar_spec.rb | 4 +- .../param_types/with_choices_spec.rb | 28 ++-- spec/compendium/params_spec.rb | 16 +- spec/compendium/presenters/base_spec.rb | 6 +- spec/compendium/presenters/chart_spec.rb | 23 ++- spec/compendium/presenters/csv_spec.rb | 4 +- spec/compendium/presenters/option_spec.rb | 146 +++++++++++++++-- .../presenters/settings/table_spec.rb | 2 +- spec/compendium/queries/collection_spec.rb | 36 ++--- spec/compendium/queries/count_spec.rb | 16 +- spec/compendium/queries/query_spec.rb | 88 +++++------ spec/compendium/queries/sum_spec.rb | 20 +-- spec/compendium/queries/through_spec.rb | 56 +++---- spec/compendium/report_spec.rb | 148 +++++++++++++----- spec/compendium/result_set_spec.rb | 18 +-- spec/spec_helper.rb | 4 +- 55 files changed, 984 insertions(+), 592 deletions(-) create mode 100644 .rubocop.yml create mode 100644 .rubocop_todo.yml create mode 100644 lib/compendium/queries/query/render.rb diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..baa0ec5 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,104 @@ +inherit_from: .rubocop_todo.yml + +Layout/AccessModifierIndentation: + EnforcedStyle: outdent + +Layout/CaseIndentation: + EnforcedStyle: end + IndentOneStep: true + +Layout/ElseAlignment: + Enabled: false + +Layout/SpaceBeforeBlockBraces: + EnforcedStyle: space + EnforcedStyleForEmptyBraces: space + +Layout/SpaceInLambdaLiteral: + EnforcedStyle: require_space + +Layout/SpaceInsideArrayLiteralBrackets: + EnforcedStyle: no_space + +Layout/SpaceInsideBlockBraces: + EnforcedStyle: space + EnforcedStyleForEmptyBraces: no_space + SpaceBeforeBlockParameters: true + +Lint/EndAlignment: + EnforcedStyleAlignWith: variable + +Lint/UnusedMethodArgument: + Exclude: + - 'lib/compendium/abstract_chart_provider.rb' + +Metrics/BlockLength: + Exclude: + - spec/**/* + Max: 25 + +Metrics/ClassLength: + Max: 100 + +Metrics/ModuleLength: + Max: 100 + +Metrics/LineLength: + Max: 150 + +Style/Alias: + EnforcedStyle: prefer_alias_method + +Style/BarePercentLiterals: + EnforcedStyle: percent_q + +Style/BlockDelimiters: + EnforcedStyle: line_count_based + +Style/BracesAroundHashParameters: + EnforcedStyle: context_dependent + +Style/DateTime: + Exclude: + - 'spec/compendium/param_types/date_spec.rb' + +Style/Documentation: + Enabled: false + +Style/FormatString: + EnforcedStyle: sprintf + +Style/FormatStringToken: + EnforcedStyle: unannotated + +Style/HashSyntax: + Exclude: + - 'Rakefile' + +Style/IfUnlessModifier: + Exclude: + - 'Gemfile' + +Style/MethodMissing: + Exclude: + - 'lib/compendium/presenters/settings/query.rb' + +Style/PercentLiteralDelimiters: + PreferredDelimiters: + default: '()' + '%i': '()' + '%I': '()' + '%w': '()' + '%W': '()' + +Style/RegexpLiteral: + EnforcedStyle: mixed + +Style/RescueModifier: + Enabled: false + +Style/StabbyLambdaParentheses: + EnforcedStyle: require_parentheses + +Style/SymbolArray: + MinSize: 3 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..640e9b0 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,32 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2018-01-26 13:11:40 -0500 using RuboCop version 0.52.1. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 15 +Metrics/AbcSize: + Max: 33 + +# Offense count: 4 +Metrics/CyclomaticComplexity: + Max: 7 + +# Offense count: 14 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 17 + +# Offense count: 3 +Metrics/PerceivedComplexity: + Max: 9 + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: braces, no_braces, context_dependent +Style/BracesAroundHashParameters: + Exclude: + - 'spec/compendium/presenters/option_spec.rb' diff --git a/Rakefile b/Rakefile index aeb05c0..87da91c 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,5 @@ -require "bundler/gem_tasks" +require 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) -task :default => :spec \ No newline at end of file +task :default => :spec diff --git a/app/controllers/compendium/reports_controller.rb b/app/controllers/compendium/reports_controller.rb index db9b093..bc9a3e6 100644 --- a/app/controllers/compendium/reports_controller.rb +++ b/app/controllers/compendium/reports_controller.rb @@ -19,7 +19,7 @@ def run end format.any do - template = template_exists?(@prefix, get_template_prefixes) ? @prefix : 'run' + template = template_exists?(@prefix, template_prefixes) ? @prefix : 'run' render action: template, locals: { report: @report } end end @@ -33,11 +33,7 @@ def export respond_to do |format| format.csv do - filename = @report.report_name.to_s.parameterize + '-' + Time.current.strftime('%Y%m%d%H%I%S') - response.headers['Content-Disposition'] = 'attachment; filename="' + filename + '.csv"' - - query = @report.queries[@report.exporters[:csv]] - render text: query.render_csv + render_csv end format.any do @@ -65,11 +61,10 @@ def find_report def find_query return unless params[:query] @query = @report.queries[params[:query]] + return unless @query - unless @query - flash[:error] = t(:invalid_report_query, scope: 'compendium.reports') - redirect_to action: :setup, report_name: params[:report_name] - end + flash[:error] = t(:invalid_report_query, scope: 'compendium.reports') + redirect_to action: :setup, report_name: params[:report_name] end def render_setup(opts = {}) @@ -89,16 +84,24 @@ def run_report @report.run(self, @query ? { only: @query.name } : {}) end - def get_template_prefixes + def template_prefixes paths = [] klass = self.class - begin + while klass != ActionController::Base paths << klass.name.underscore.gsub(/_controller$/, '') klass = klass.superclass - end while(klass != ActionController::Base) + end paths end + + def render_csv + filename = @report.report_name.to_s.parameterize + '-' + Time.current.strftime('%Y%m%d%H%I%S') + response.headers['Content-Disposition'] = 'attachment; filename="' + filename + '.csv"' + + query = @report.queries[@report.exporters[:csv]] + render text: query.render_csv + end end end diff --git a/app/helpers/compendium/reports_helper.rb b/app/helpers/compendium/reports_helper.rb index 8ece860..b2a5581 100644 --- a/app/helpers/compendium/reports_helper.rb +++ b/app/helpers/compendium/reports_helper.rb @@ -1,6 +1,7 @@ module Compendium module ReportsHelper private + def expose(*args) klass = args.pop if args.last.is_a?(Class) klass ||= "Compendium::Presenters::#{args.first.class}".constantize @@ -14,9 +15,7 @@ def render_report_setup(assigns) end def render_if_exists(options = {}) - if lookup_context.template_exists?(options[:partial] || options[:template], options[:path], options.key?(:partial)) - render(options) - end + render(options) if lookup_context.template_exists?(options[:partial] || options[:template], options[:path], options.key?(:partial)) end end -end \ No newline at end of file +end diff --git a/compendium.gemspec b/compendium.gemspec index 8b1db4e..f9e1fef 100644 --- a/compendium.gemspec +++ b/compendium.gemspec @@ -1,28 +1,28 @@ -# -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'compendium/version' Gem::Specification.new do |gem| - gem.name = "compendium" + gem.name = 'compendium' gem.version = Compendium::VERSION - gem.authors = ["Daniel Vandersluis"] - gem.email = ["dvandersluis@selfmgmt.com"] - gem.description = %q{Ruby on Rails reporting framework} - gem.summary = %q{Ruby on Rails reporting framework} - gem.homepage = "https://github.com/dvandersluis/compendium" - gem.license = "MIT" + gem.authors = ['Daniel Vandersluis'] + gem.email = ['dvandersluis@selfmgmt.com'] + gem.description = 'Ruby on Rails reporting framework' + gem.summary = 'Ruby on Rails reporting framework' + gem.homepage = 'https://github.com/dvandersluis/compendium' + gem.license = 'MIT' - gem.files = `git ls-files`.split($/) - gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.files = `git ls-files`.split($/) # rubocop:disable Style/SpecialGlobalVars + gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) - gem.require_paths = ["lib"] + gem.require_paths = ['lib'] - gem.add_dependency 'rails', '>= 3.0.0', '< 4' - gem.add_dependency 'sass-rails', '>= 3.0.0' - gem.add_dependency 'compass-rails', '>= 1.0.0' gem.add_dependency 'collection_of', '1.0.6' + gem.add_dependency 'compass-rails', '>= 1.0.0' gem.add_dependency 'inheritable_attr', '>= 1.0.0' + gem.add_dependency 'rails', '>= 3.0.0', '< 4' + gem.add_dependency 'sass-rails', '>= 3.0.0' gem.add_development_dependency 'rake', '> 11.0.1', '< 12' gem.add_development_dependency 'rspec', '~> 3.7.0' + gem.add_development_dependency 'rubocop', '0.52.1' end diff --git a/config/initializers/rails/active_record/connection_adapters/quoting.rb b/config/initializers/rails/active_record/connection_adapters/quoting.rb index 83f275d..dbaabfe 100644 --- a/config/initializers/rails/active_record/connection_adapters/quoting.rb +++ b/config/initializers/rails/active_record/connection_adapters/quoting.rb @@ -3,12 +3,16 @@ # crash. # Override AR::ConnectionAdapters::Quoting to forward a SimpleDelegator's object to be quoted. -module ActiveRecord::ConnectionAdapters::Quoting - def quote_with_simple_delegator(value, column = nil) - return value.quoted_id if value.respond_to?(:quoted_id) - value = value.__getobj__ if value.is_a?(SimpleDelegator) - quote_without_simple_delegator(value, column) - end +module ActiveRecord + module ConnectionAdapters + module Quoting + def quote_with_simple_delegator(value, column = nil) + return value.quoted_id if value.respond_to?(:quoted_id) + value = value.__getobj__ if value.is_a?(SimpleDelegator) + quote_without_simple_delegator(value, column) + end - alias_method_chain :quote, :simple_delegator -end \ No newline at end of file + alias_method_chain :quote, :simple_delegator + end + end +end diff --git a/config/initializers/ruby/numeric.rb b/config/initializers/ruby/numeric.rb index 404cb4d..74d7965 100644 --- a/config/initializers/ruby/numeric.rb +++ b/config/initializers/ruby/numeric.rb @@ -23,4 +23,4 @@ class Numeric def numeric? true end -end \ No newline at end of file +end diff --git a/lib/compendium/abstract_chart_provider.rb b/lib/compendium/abstract_chart_provider.rb index cf98d8f..f120259 100644 --- a/lib/compendium/abstract_chart_provider.rb +++ b/lib/compendium/abstract_chart_provider.rb @@ -40,4 +40,4 @@ def respond_to_missing?(name, include_private = false) super end end -end \ No newline at end of file +end diff --git a/lib/compendium/context_wrapper.rb b/lib/compendium/context_wrapper.rb index 3e377d8..88ff022 100644 --- a/lib/compendium/context_wrapper.rb +++ b/lib/compendium/context_wrapper.rb @@ -24,4 +24,4 @@ def respond_to_missing?(name, include_private = false) delegator end end -end \ No newline at end of file +end diff --git a/lib/compendium/dsl.rb b/lib/compendium/dsl.rb index 8417e3f..aad6916 100644 --- a/lib/compendium/dsl.rb +++ b/lib/compendium/dsl.rb @@ -1,10 +1,11 @@ require 'collection_of' require 'inheritable_attr' require 'compendium/option' +require 'compendium/queries/query' require 'active_support/core_ext/class/attribute' module Compendium - module DSL + module DSL # rubocop:disable Metrics/ModuleLength def self.extended(klass) klass.inheritable_attr :queries, default: ::Collection[Queries::Query] klass.inheritable_attr :options, default: ::Collection[Option] @@ -50,7 +51,7 @@ def metric(name, *args, &block) # ie. if you need a metric that counts a column, there's no need to explicitly create a query # and just pass it into a metric query = define_query("__metric_#{name}", {}, &block) - query.add_metric(name, -> result { result.first }, opts) + query.add_metric(name, -> (result) { result.first }, opts) end end @@ -113,7 +114,7 @@ def respond_to_missing?(name, *args) private - def each_query(query_names, &block) + def each_query(query_names) query_names.each do |query_name| raise ArgumentError, "query #{query_name} is not defined" unless queries.key?(query_name) yield queries[query_name] @@ -122,31 +123,24 @@ def each_query(query_names, &block) def define_query(name, opts, &block) params = [name.to_sym, opts, block] - query_type = Queries::Query - if opts.key?(:collection) - query_type = Queries::Collection - elsif opts.key?(:through) + if opts.key?(:through) # Ensure each through query is defined through = [opts[:through]].flatten - through.each { |q| raise ArgumentError, "query #{q} is not defined" unless self.queries.include?(q.to_sym) } + through.each { |q| raise ArgumentError, "query #{q} is not defined" unless queries.include?(q.to_sym) } - query_type = Queries::Through params.insert(1, through) - elsif opts.fetch(:count, false) - query_type = Queries::Count elsif opts.fetch(:sum, false) - query_type = Queries::Sum params.insert(1, opts[:sum]) end - query = query_type.new(*params) + query = query_type(opts).new(*params) query.report = self metrics[name] = opts[:metric] if opts.key?(:metric) if queries[name] - raise Queries::CannotRedefineType unless queries[name].instance_of?(query_type) + raise Queries::CannotRedefineType unless queries[name].instance_of?(query.class) queries.delete(name) end @@ -155,9 +149,23 @@ def define_query(name, opts, &block) query end + def query_type(opts) + if opts.key?(:collection) + Queries::Collection + elsif opts.key?(:through) + Queries::Through + elsif opts.fetch(:count, false) + Queries::Count + elsif opts.fetch(:sum, false) + Queries::Sum + else + Queries::Query + end + end + def add_params_validations(name, validations) return if validations.blank? - self.params_class.validates name, validations + params_class.validates name, validations end end end diff --git a/lib/compendium/engine.rb b/lib/compendium/engine.rb index 5c7b3e6..42c85c9 100644 --- a/lib/compendium/engine.rb +++ b/lib/compendium/engine.rb @@ -5,4 +5,4 @@ module Compendium class Engine < ::Rails::Engine end end -end \ No newline at end of file +end diff --git a/lib/compendium/metric.rb b/lib/compendium/metric.rb index 113bd49..c0d2c8f 100644 --- a/lib/compendium/metric.rb +++ b/lib/compendium/metric.rb @@ -30,4 +30,4 @@ def condition_failed?(ctx) (options.key?(:if) && !ctx.instance_exec(&options[:if])) || (options.key?(:unless) && ctx.instance_exec(&options[:unless])) end end -end \ No newline at end of file +end diff --git a/lib/compendium/open_hash.rb b/lib/compendium/open_hash.rb index 6ce7c83..141cf30 100644 --- a/lib/compendium/open_hash.rb +++ b/lib/compendium/open_hash.rb @@ -28,21 +28,21 @@ def convert_value(value) end end - def method_missing(name, *args, &block) + def method_missing(name, *args, &block) # rubocop:disable Metrics/CyclomaticComplexity method = name.to_s case method - when %r{.=$} + when /.=$/ super unless args.length == 1 return self[method[0...-1]] = args.first - when %r{.\?$} + when /.\?$/ super unless args.empty? - return self.key?(method[0...-1].to_sym) + return key?(method[0...-1].to_sym) - when %r{^_.} + when /^_./ super unless args.empty? - return self[method[1..-1]] if self.key?(method[1..-1].to_sym) + return self[method[1..-1]] if key?(method[1..-1].to_sym) else return self[method] if key?(method) || !respond_to?(method) @@ -55,11 +55,11 @@ def respond_to_missing?(name, include_private = false) method = name.to_s case method - when %r{.[=?]$} - return true if self.key?(method[0...-1]) + when /.[=?]$/ + return true if key?(method[0...-1]) - when %r{^_.} - return true if self.key?(method[1..-1]) + when /^_./ + return true if key?(method[1..-1]) end super diff --git a/lib/compendium/option.rb b/lib/compendium/option.rb index e6bdedc..543f80c 100644 --- a/lib/compendium/option.rb +++ b/lib/compendium/option.rb @@ -5,13 +5,14 @@ module Compendium class Option - attr_accessor :name, :type, :default, :choices, :options + attr_reader :type + attr_accessor :name, :default, :choices, :options delegate :boolean?, :date?, :dropdown?, :radio?, :scalar?, to: :type - delegate :merge, :merge!, :[], to: :@options + delegate :merge, :merge!, :[], :[]=, to: :@options def initialize(hash = {}) - raise ArgumentError, "name must be provided" unless hash.key?(:name) + raise ArgumentError, 'name must be provided' unless hash.key?(:name) @name = hash.delete(:name).to_sym @default = hash.delete(:default) @@ -35,4 +36,4 @@ def respond_to_missing?(name, include_private = false) super end end -end \ No newline at end of file +end diff --git a/lib/compendium/param_types/boolean.rb b/lib/compendium/param_types/boolean.rb index 2127c83..275aa77 100644 --- a/lib/compendium/param_types/boolean.rb +++ b/lib/compendium/param_types/boolean.rb @@ -1,10 +1,17 @@ +require 'compendium/param_types/param' + module Compendium module ParamTypes class Boolean < Param def initialize(obj, *) - # If given 0, 1, or a version thereof (ie. "0"), pass it along - return super obj.to_i if obj.numeric? && (0..1).cover?(obj.to_i) - super !!obj ? 0 : 1 + value = if obj.numeric? && (0..1).cover?(obj.to_i) + # If given 0, 1, or a version thereof (ie. "0"), pass it along + obj.to_i + else + obj ? 0 : 1 + end + + super value end def boolean? diff --git a/lib/compendium/param_types/date.rb b/lib/compendium/param_types/date.rb index a267607..c3cea53 100644 --- a/lib/compendium/param_types/date.rb +++ b/lib/compendium/param_types/date.rb @@ -2,10 +2,10 @@ module Compendium module ParamTypes class Date < Param def initialize(obj, *) - if obj.respond_to?(:to_date) - obj = obj.to_date + obj = if obj.respond_to?(:to_date) + obj.to_date else - obj = ::Date.parse(obj) rescue nil + ::Date.parse(obj) rescue nil end super obj diff --git a/lib/compendium/presenters/base.rb b/lib/compendium/presenters/base.rb index bcc3deb..4c4b11f 100644 --- a/lib/compendium/presenters/base.rb +++ b/lib/compendium/presenters/base.rb @@ -1,30 +1,32 @@ -module Compendium::Presenters - class Base - def self.presents(name) - define_method(name) do - @object +module Compendium + module Presenters + class Base + def self.presents(name) + define_method(name) do + @object + end end - end - def initialize(template, object) - @object = object - @template = template - end + def initialize(template, object) + @object = object + @template = template + end - def to_s - "#<#{self.class.name}:0x00#{'%x' % (object_id << 1)}>" - end + def to_s + "#<#{self.class.name}:0x00#{sprintf('%x', object_id << 1)}>" + end - private + private - def method_missing(*args, &block) - return @template.send(*args, &block) if @template.respond_to?(args.first) - super - end + def method_missing(*args, &block) + return @template.send(*args, &block) if @template.respond_to?(args.first) + super + end - def respond_to_missing?(*args) - return true if @template.respond_to?(*args) - super + def respond_to_missing?(*args) + return true if @template.respond_to?(*args) + super + end end end end diff --git a/lib/compendium/presenters/chart.rb b/lib/compendium/presenters/chart.rb index ddcbad3..3d579e8 100644 --- a/lib/compendium/presenters/chart.rb +++ b/lib/compendium/presenters/chart.rb @@ -1,84 +1,86 @@ require 'compendium/presenters/query' require 'active_support/core_ext/array/extract_options' -module Compendium::Presenters - class Chart < Query - attr_reader :data, :params, :container, :chart_provider - attr_accessor :options - - def initialize(template, object, *args, &setup) - super(template, object) - - self.options = args.extract_options! - type, container = args - - if remote? - # If the query hasn't run yet, render a chart that loads its data remotely (ie. through AJAX) - # ie. if rendering a query from a report class directly - @data = query.url - @params = collect_params - else - @data = options[:index] ? results.records[options[:index]] : results - @data = @data.records if @data.is_a?(Compendium::ResultSet) - @data = @data[0...-1] if query.options[:totals] +module Compendium + module Presenters + class Chart < Query + attr_reader :data, :params, :container, :chart_provider + attr_accessor :options + + def initialize(template, object, *args, &setup) + super(template, object) + + self.options = args.extract_options! + type, container = args + + if remote? + # If the query hasn't run yet, render a chart that loads its data remotely (ie. through AJAX) + # ie. if rendering a query from a report class directly + @data = query.url + @params = collect_params + else + @data = options[:index] ? results.records[options[:index]] : results + @data = @data.records if @data.is_a?(Compendium::ResultSet) + @data = @data[0...-1] if query.options[:totals] + end + + @container = container || query.name + + initialize_chart_provider(type, &setup) end - @container = container || query.name - - initialize_chart_provider(type, &setup) - end + def render + chart_provider.render(@template, @container) + end - def render - chart_provider.render(@template, @container) - end + # You can force the chart to render remote data, even if the query has already run by passing the remote: true option + def remote? + !query.ran? || options.fetch(:remote, false) + end - # You can force the chart to render remote data, even if the query has already run by passing the remote: true option - def remote? - !query.ran? || options.fetch(:remote, false) - end + private - private + def provider + provider = Compendium.config.chart_provider + require "compendium/#{provider.downcase}" + provider.is_a?(Class) ? provider : Compendium::ChartProvider.const_get(provider) + end - def provider - provider = Compendium.config.chart_provider - require "compendium/#{provider.downcase}" - provider.is_a?(Class) ? provider : Compendium::ChartProvider.const_get(provider) - end + def initialize_chart_provider(type, &setup) + @chart_provider = provider.new(type, @data, @params, &setup) + end - def initialize_chart_provider(type, &setup) - @chart_provider = provider.new(type, @data, @params, &setup) - end + def collect_params + params = {} + params[:report] = options[:params] if options[:params] - def collect_params - params = {} - params[:report] = options[:params] if options[:params] + if remote? && protected_against_csrf? + # If we're loading remotely, and CSRF protection is enabled, + # automatically include the CSRF token in AJAX params + params.merge!(form_authenticity_param) + end - if remote? && protected_against_csrf? - # If we're loading remotely, and CSRF protection is enabled, - # automatically include the CSRF token in AJAX params - params.merge!(form_authenticity_param) + params end - params - end - - def protected_against_csrf? - @template.controller.send(:protect_against_forgery?) - end + def protected_against_csrf? + @template.controller.send(:protect_against_forgery?) + end - def form_authenticity_param - return {} unless protected_against_csrf? - { @template.controller.request_forgery_protection_token => @template.controller.send(:form_authenticity_token) } - end + def form_authenticity_param + return {} unless protected_against_csrf? + { @template.controller.request_forgery_protection_token => @template.controller.send(:form_authenticity_token) } + end - def method_missing(name, *args, &block) - return chart_provider.send(name, *args, &block) if chart_provider.respond_to?(name) - super - end + def method_missing(name, *args, &block) + return chart_provider.send(name, *args, &block) if chart_provider.respond_to?(name) + super + end - def respond_to_missing?(name, include_private = false) - return true if chart_provider.respond_to?(name) - super + def respond_to_missing?(name, include_private = false) + return true if chart_provider.respond_to?(name) + super + end end end end diff --git a/lib/compendium/presenters/csv.rb b/lib/compendium/presenters/csv.rb index dc7682c..af8695d 100644 --- a/lib/compendium/presenters/csv.rb +++ b/lib/compendium/presenters/csv.rb @@ -15,7 +15,7 @@ def render csv << row.map { |key, val| formatted_value(key, val) } end - if has_totals_row? + if totals_row? totals[totals.keys.first] = translate(:total) csv << totals.map do |key, val| formatted_value(key, val) unless settings.skipped_total_cols.include?(key.to_sym) diff --git a/lib/compendium/presenters/option.rb b/lib/compendium/presenters/option.rb index 49be584..87ccfe2 100644 --- a/lib/compendium/presenters/option.rb +++ b/lib/compendium/presenters/option.rb @@ -1,7 +1,9 @@ +require 'active_support/core_ext/string/output_safety' + module Compendium module Presenters class Option < Base - MISSING_CHOICES_ERROR = "choices must be specified" + MISSING_CHOICES_ERROR = 'choices must be specified'.freeze presents :option delegate :hidden?, to: :option @@ -11,67 +13,39 @@ def name end def label(form) - if option.note? - key = option.note == true ? :"#{option.name}_note" : option.note - note = t("options.#{key}", cascade: { offset: 2 }) - end - - if option.note? && defined?(AccessibleTooltip) - title = t("options.#{option.name}_note_title", default: '', cascade: { offset: 2 }) - tooltip = accessible_tooltip(:help, label: name, title: title) { note } - return form.label option.name, tooltip - else - label = case option.type.to_sym - when :boolean, :radio - name - - else - form.label option.name, name - end + return label_with_accessible_tooltip(form) if option.note? && defined?(AccessibleTooltip) - out = ActiveSupport::SafeBuffer.new - out << content_tag(:span, label, class: 'option-label') - out << content_tag(:div, note, class: 'option-note') if option.note? - out - end + out = ActiveSupport::SafeBuffer.new + out << content_tag(:span, label_content(form), class: 'option-label') + out << content_tag(:div, note_text, class: 'option-note') if option.note? + out end def note - if option.note? - key = option.note === true ? :"#{option.name}_note" : option.note - content_tag(:div, t(key), class: 'option-note') - end + return unless option.note? + content_tag(:div, t(note_key), class: 'option-note') end def input(ctx, form) out = ActiveSupport::SafeBuffer.new - case option.type.to_sym + raise ArgumentError, MISSING_CHOICES_ERROR if missing_choices? + + # rubocop:disable Layout/EmptyLinesAroundArguments + out << case option.type.to_sym when :scalar - out << scalar_field(form) + scalar_field(form) when :date - out << date_field(form) + date_field(form) when :dropdown - raise ArgumentError, MISSING_CHOICES_ERROR unless option.choices - - choices = option.choices - choices = ctx.instance_exec(&choices) if choices.respond_to?(:call) - out << dropdown(form, choices, option.options) + dropdown_field(form, ctx) when :boolean, :radio - choices = if option.radio? - raise ArgumentError, MISSING_CHOICES_ERROR unless option.choices - option.choices - else - %w(true false) - end - - choices.each.with_index { |choice, index| out << radio_button(form, choice, index) } + radio_fields(form) end - - out + # rubocop:enable Layout/EmptyLinesAroundArguments end def hidden_field(form) @@ -80,6 +54,20 @@ def hidden_field(form) private + def note_key + return unless option.note? + option.note == true ? :"#{option.name}_note" : option.note + end + + def note_text + return unless option.note? + t("options.#{note_key}", cascade: { offset: 2 }) + end + + def missing_choices? + !option.choices && (option.radio? || option.dropdown?) + end + def date_field(form, include_time = false) content_tag('div', class: 'option-date') do if defined?(CalendarDateSelect) @@ -96,12 +84,20 @@ def scalar_field(form) end end - def dropdown(form, choices = {}, options = {}) + def dropdown_field(form, ctx) + choices = option.choices + choices = ctx.instance_exec(&choices) if choices.respond_to?(:call) + content_tag('div', class: 'option-dropdown') do - form.select option.name, choices, options.symbolize_keys + form.select option.name, choices, option.options.symbolize_keys end end + def radio_fields(form) + choices = option.radio? ? option.choices : %w(true false) + choices.each.with_object(ActiveSupport::SafeBuffer.new).with_index { |(choice, out), index| out << radio_button(form, choice, index) } + end + def radio_button(form, label, value) content_tag('div', class: 'option-radio') do div_content = ActiveSupport::SafeBuffer.new @@ -109,6 +105,22 @@ def radio_button(form, label, value) div_content << form.label(option.name, t(label), value: value) end end + + def label_with_accessible_tooltip(form) + title = t("options.#{option.name}_note_title", default: '', cascade: { offset: 2 }) + tooltip = accessible_tooltip(:help, label: name, title: title) { note_text } + form.label option.name, tooltip + end + + def label_content(form) + case option.type.to_sym + when :boolean, :radio + name + + else + form.label option.name, name + end + end end end end diff --git a/lib/compendium/presenters/settings/query.rb b/lib/compendium/presenters/settings/query.rb index 005ce0f..e8f86dc 100644 --- a/lib/compendium/presenters/settings/query.rb +++ b/lib/compendium/presenters/settings/query.rb @@ -19,9 +19,9 @@ def update(&block) instance_exec(self, &block) end - def method_missing(name, *args, &block) + def method_missing(name, *args, &_block) if block_given? - @settings[name] = block.call(*args) + @settings[name] = yield(*args) elsif !args.empty? @settings[name] = args.length == 1 ? args.first : args elsif name.to_s.end_with?('?') @@ -31,6 +31,10 @@ def method_missing(name, *args, &block) @settings[name] end end + + def respond_to_missing?(*) + true + end end end end diff --git a/lib/compendium/presenters/settings/table.rb b/lib/compendium/presenters/settings/table.rb index 47d0f63..46db222 100644 --- a/lib/compendium/presenters/settings/table.rb +++ b/lib/compendium/presenters/settings/table.rb @@ -20,7 +20,7 @@ def initialize(*) skipped_total_cols [] end - def set_headings(headings) + def set_headings(headings) # rubocop:disable Naming/AccessorMethodName headings.map!(&:to_sym) @headings = Hash[headings.zip(headings)].with_indifferent_access end diff --git a/lib/compendium/presenters/table.rb b/lib/compendium/presenters/table.rb index dfc5ad0..53ca38a 100644 --- a/lib/compendium/presenters/table.rb +++ b/lib/compendium/presenters/table.rb @@ -15,10 +15,7 @@ def initialize(*) @settings.update(&query.table_settings) if query.table_settings yield @settings if block_given? - if has_totals_row? - @totals = @records.pop - totals[totals.keys.first] = translate(:total) - end + setup_totals if totals_row? end def render @@ -30,7 +27,7 @@ def render records.each { |row| tbody << build_row(row, settings.row_class, &data_proc) } tbody end - table << content_tag(:tfoot, build_row(totals, @settings.totals_class, :th, &totals_proc)) if has_totals_row? + table << content_tag(:tfoot, build_row(totals, @settings.totals_class, :th, &totals_proc)) if totals_row? table end end @@ -41,7 +38,7 @@ def headings @settings.headings end - def has_totals_row? + def totals_row? query.options.fetch(:totals, false) end @@ -77,25 +74,28 @@ def formatted_heading(v) def formatted_value(k, v) if @settings.formatters[k] @settings.formatters[k].call(v) - else - if v.numeric? - if v.zero? && @settings.display_zero_as? - @settings.display_zero_as - else - sprintf(@settings.number_format, v) - end - elsif v.nil? - @settings.display_nil_as + elsif v.numeric? + if v.zero? && @settings.display_zero_as? + @settings.display_zero_as + else + sprintf(@settings.number_format, v) end + elsif v.nil? + @settings.display_nil_as end || v end def translate(v, opts = {}) opts.reverse_merge!(scope: settings.i18n_scope) if settings.i18n_scope? - opts[:default] = -> * { I18n.t(v, scope: 'compendium') } + opts[:default] = -> (*) { I18n.t(v, scope: 'compendium') } I18n.t(v, opts) end + def setup_totals + @totals = @records.pop + totals[totals.keys.first] = translate(:total) + end + def settings_class Settings::Table end diff --git a/lib/compendium/queries/collection.rb b/lib/compendium/queries/collection.rb index dc9263e..411939f 100644 --- a/lib/compendium/queries/collection.rb +++ b/lib/compendium/queries/collection.rb @@ -14,10 +14,9 @@ def initialize(*) def run(params, context = self) collection_values = get_collection_values(context, params) - results = collection_values.inject({}) do |r, (key, value)| + results = collection_values.each_with_object({}) do |(key, value), r| res = collect_results(context, params, key, value) r[key] = res unless res.empty? - r end # A CollectionQuery's results will be a ResultSet of ResultSets diff --git a/lib/compendium/queries/query.rb b/lib/compendium/queries/query.rb index 1dfd7af..25c4158 100644 --- a/lib/compendium/queries/query.rb +++ b/lib/compendium/queries/query.rb @@ -8,6 +8,10 @@ module Compendium module Queries class Query + autoload :Render, 'compendium/queries/query/render' + + include Render + attr_reader :name, :results, :metrics, :filters attr_accessor :options, :proc, :report, :table_settings @@ -32,7 +36,7 @@ def run(params, context = self) # If running a query directly from a class rather than an instance, the class's query should # not be affected/modified, so run the query without a reference back to the report. # Otherwise, if the class is subsequently instantiated, the instance will already have results. - dup.tap{ |q| q.report = nil }.run(params, context) + dup.tap { |q| q.report = nil }.run(params, context) else collect_results(context, params) collect_metrics(context) @@ -43,7 +47,7 @@ def run(params, context = self) # Get a URL for this query (format: :json set by default) def url(params = {}) - report.url(params.merge(query: self.name)) + report.url(params.merge(query: name)) end def add_metric(name, proc, options = {}) @@ -54,26 +58,6 @@ def add_filter(filter) @filters << filter end - def render_table(template, *options, &block) - Compendium::Presenters::Table.new(template, self, *options, &block).render unless empty? - end - - def render_csv(&block) - Compendium::Presenters::CSV.new(self, &block).render unless empty? - end - - # Allow access to the chart object without having to explicitly render it - def chart(template, *options, &block) - # Access the actual chart object - Compendium::Presenters::Chart.new(template, self, *options, &block) - end - - def render_chart(template, *options, &block) - # A query can be rendered regardless of if it has data or not - # Rendering a chart with no result set builds a chart scaffold which can be updated through AJAX - chart(template, *options, &block).render - end - def ran? !@results.nil? end @@ -101,11 +85,11 @@ def collect_results(context, *params) end def collect_metrics(context) - metrics.each{ |m| m.run(context, results) } unless results.empty? + metrics.each { |m| m.run(context, results) } unless results.empty? end def fetch_results(command) - (options.fetch(:collect, nil) == :active_record) ? command : execute_command(command) + options.fetch(:collect, nil) == :active_record ? command : execute_command(command) end def filter_results(results, params) @@ -114,14 +98,14 @@ def filter_results(results, params) if results.respond_to? :with_indifferent_access results = results.with_indifferent_access else - results.map! &:with_indifferent_access + results.map!(&:with_indifferent_access) end filters.each do |f| - if f.arity == 2 - results = f.call(results, params) + results = if f.arity == 2 + f.call(results, params) else - results = f.call(results) + f.call(results) end end diff --git a/lib/compendium/queries/query/render.rb b/lib/compendium/queries/query/render.rb new file mode 100644 index 0000000..450be29 --- /dev/null +++ b/lib/compendium/queries/query/render.rb @@ -0,0 +1,27 @@ +module Compendium + module Queries + class Query + module Render + def render_table(template, *options, &block) + Compendium::Presenters::Table.new(template, self, *options, &block).render unless empty? + end + + def render_csv(&block) + Compendium::Presenters::CSV.new(self, &block).render unless empty? + end + + def render_chart(template, *options, &block) + # A query can be rendered regardless of if it has data or not + # Rendering a chart with no result set builds a chart scaffold which can be updated through AJAX + chart(template, *options, &block).render + end + + # Allow access to the chart object without having to explicitly render it + def chart(template, *options, &block) + # Access the actual chart object + Compendium::Presenters::Chart.new(template, self, *options, &block) + end + end + end + end +end diff --git a/lib/compendium/report.rb b/lib/compendium/report.rb index b89094e..37b8d9b 100644 --- a/lib/compendium/report.rb +++ b/lib/compendium/report.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/string/inflections' require 'compendium/dsl' +require 'compendium/context_wrapper' module Compendium class Report @@ -22,16 +23,16 @@ def inherited(report) # Each Report object has its own Params class so that validations can be added without affecting other # reports. However, validations also need to be inherited, so when inheriting a report, subclass its # params_class - report.params_class = Class.new(self.params_class) - report.params_class.class_eval %Q{ + report.params_class = Class.new(params_class) + report.params_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.model_name ActiveModel::Name.new(Compendium::Params, Compendium, "compendium.params.#{report.name.underscore rescue 'report'}") end - } + RUBY end def report_name - name.underscore.gsub(/_report$/,'').to_sym + name.underscore.gsub(/_report$/, '').to_sym end # Get a URL for this report (format: :json set by default) @@ -61,8 +62,8 @@ def respond_to_missing?(name, include_private = false) private def path_helper(params) - raise ActionController::RoutingError, "compendium_reports_run_path must be defined" unless route_helper_defined? - Rails.application.routes.url_helpers.compendium_reports_run_path(self.report_name, params.reverse_merge(format: :json)) + raise ActionController::RoutingError, 'compendium_reports_run_path must be defined' unless route_helper_defined? + Rails.application.routes.url_helpers.compendium_reports_run_path(report_name, params.reverse_merge(format: :json)) end def route_helper_defined? @@ -96,17 +97,17 @@ def run(context = nil, options = {}) queries end - queries_to_run.each{ |q| self.results[q.name] = q.run(params, ContextWrapper.wrap(context, self)) } + queries_to_run.each { |q| results[q.name] = q.run(params, ContextWrapper.wrap(context, self)) } self end def metrics - Collection[Metric, queries.map{ |q| q.metrics.to_a }.flatten] + Collection[Metric, queries.map { |q| q.metrics.to_a }.flatten] end def exports?(type) - return exporters[type.to_sym] + exporters[type.to_sym] end private @@ -119,14 +120,17 @@ def method_missing(name, *args, &block) return queries[name] if queries.keys.include?(name) return results[prefix] if name.to_s.end_with?('_results') && queries.keys.include?(prefix) return params[name] if options.keys.include?(name) - return !!params[prefix] if name.to_s.end_with?('?') && options.keys.include?(prefix) + return params[prefix] if name.to_s.end_with?('?') && options.keys.include?(prefix) super end def respond_to_missing?(name, include_private = false) - prefix = name.to_s.sub(/_results\Z/, '').to_sym + prefix = name.to_s.sub(/(?:_results|\?)\Z/, '').to_sym + return true if queries.keys.include?(name) return true if name.to_s.end_with?('_results') && queries.keys.include?(prefix) + return true if options.keys.include?(name) + return true if name.to_s.end_with?('?') && options.keys.include?(prefix) super end end diff --git a/lib/compendium/result_set.rb b/lib/compendium/result_set.rb index 409363b..8fd041b 100644 --- a/lib/compendium/result_set.rb +++ b/lib/compendium/result_set.rb @@ -3,10 +3,11 @@ module Compendium class ResultSet - delegate :first, :last, :to_a, :empty?, :each, :map, :inject, :select, :detect, :[], :count, :length, :size, :==, to: :records + include Enumerable + delegate :each, :empty?, :length, :size, :==, to: :records attr_reader :records - alias :all :records + alias_method :all, :records def initialize(records) @records = if records.respond_to?(:map) @@ -26,7 +27,7 @@ def keys def as_json(options = {}) return records unless records.first.respond_to?(:except) - records.map{ |r| r.except(*options[:except]) } + records.map { |r| r.except(*options[:except]) } end end end diff --git a/lib/compendium/version.rb b/lib/compendium/version.rb index 4c7424f..1e24088 100644 --- a/lib/compendium/version.rb +++ b/lib/compendium/version.rb @@ -1,3 +1,3 @@ module Compendium - VERSION = '1.2.2' + VERSION = '1.2.2'.freeze end diff --git a/spec/compendium/context_wrapper_spec.rb b/spec/compendium/context_wrapper_spec.rb index e5501de..90a3bf8 100644 --- a/spec/compendium/context_wrapper_spec.rb +++ b/spec/compendium/context_wrapper_spec.rb @@ -25,7 +25,7 @@ def wrapper_num end describe Compendium::ContextWrapper do - describe ".wrap" do + describe '.wrap' do let(:w1) { Wrapper1.new } let(:w2) { Wrapper2.new } let(:w3) { Wrapper3.new } @@ -39,22 +39,22 @@ def wrapper_num specify { expect(subject.test_val).to eq(123) } specify { expect(subject.wrapped).to eq(true) } - it "should not affect the original objects" do + it 'should not affect the original objects' do subject expect(w1).not_to respond_to :wrapped expect(w2).not_to respond_to :test_val end - it "should yield a block if given" do + it 'should yield a block if given' do expect(described_class.wrap(w2, w1) { test_val }).to eq(123) end - context "overriding methods" do + context 'overriding methods' do subject { described_class.wrap(w4, w3) } specify { expect(subject.wrapper_num).to eq(4) } end - context "nested wrapping" do + context 'nested wrapping' do let(:inner) { described_class.wrap(w2, w1) } subject { described_class.wrap(inner, w3) } @@ -62,7 +62,7 @@ def wrapper_num it { is_expected.to respond_to :wrapped } it { is_expected.to respond_to :wrapper_num } - it "should not extend the inner wrap" do + it 'should not extend the inner wrap' do subject expect(inner).not_to respond_to :wrapper_num end diff --git a/spec/compendium/dsl_spec.rb b/spec/compendium/dsl_spec.rb index 74bd75e..26beccd 100644 --- a/spec/compendium/dsl_spec.rb +++ b/spec/compendium/dsl_spec.rb @@ -9,35 +9,35 @@ end end - describe "#option" do + describe '#option' do before { subject.option :starting_on, :date } specify { expect(subject.options).to include :starting_on } specify { expect(subject.options[:starting_on]).to be_date } - it "should allow previously defined options to be redefined" do + it 'should allow previously defined options to be redefined' do subject.option :starting_on, :boolean expect(subject.options[:starting_on]).to be_boolean expect(subject.options[:starting_on]).not_to be_date end - it "should allow overriding default value" do + it 'should allow overriding default value' do proc = -> { Date.new(2013, 6, 1) } subject.option :starting_on, :date, default: proc expect(subject.options[:starting_on].default).to eq(proc) end - it "should add validations" do + it 'should add validations' do subject.option :foo, validates: { presence: true } expect(subject.params_class.validators_on(:foo)).not_to be_empty end - it "should not add validations if no validates option is given" do + it 'should not add validations if no validates option is given' do expect(subject.params_class).not_to receive :validates subject.option :foo end - it "should not bleed overridden options into the superclass" do + it 'should not bleed overridden options into the superclass' do r = Class.new(subject) r.option :starting_on, :boolean r.option :new, :date @@ -45,7 +45,7 @@ end end - describe "#query" do + describe '#query' do let(:proc1) { -> { :proc1 } } let(:proc2) { -> { :proc2 } } @@ -61,12 +61,12 @@ specify { expect(subject.queries).to include :test } - it "should relate the new query back to the report instance" do + it 'should relate the new query back to the report instance' do r = subject.new expect(r.test.report).to eq(r) end - it "should relate a query to the report class" do + it 'should relate a query to the report class' do expect(subject.test.report).to eq(subject) end @@ -89,7 +89,7 @@ end it 'should not allow replacing a query with a different type' do - expect { subject.query :test, count: true }.to raise_error { Compendium::Queries::CannotRedefineType } + expect { subject.query :test, count: true }.to raise_error(Compendium::Queries::CannotRedefineType) expect(subject.test).to be_instance_of Compendium::Queries::Query end @@ -100,7 +100,7 @@ end end - context "when given a through option" do + context 'when given a through option' do before { report_class.query :through, through: :test } subject { report_class.queries[:through] } @@ -108,17 +108,17 @@ specify { expect(subject.through).to eq([:test]) } end - context "when given a collection option" do + context 'when given a collection option' do subject { report_class.queries[:collection] } - context "that is an enumerable" do + context 'that is an enumerable' do before { report_class.query :collection, collection: [] } it { is_expected.to be_a Compendium::Queries::Collection } end - context "that is a symbol" do - let(:query) { double("Query") } + context 'that is a symbol' do + let(:query) { double('Query') } before do allow_any_instance_of(Compendium::Queries::Query).to receive(:get_associated_query).with(:query).and_return(query) @@ -128,23 +128,23 @@ specify { expect(subject.collection).to eq(:query) } end - context "that is a query" do - let(:query) { Compendium::Queries::Query.new(:query, {}, ->{}) } + context 'that is a query' do + let(:query) { Compendium::Queries::Query.new(:query, {}, -> {}) } before { report_class.query :collection, collection: query } specify { expect(subject.collection).to eq(query) } end end - context "when given a count option" do - subject{ report_class.queries[:counted] } + context 'when given a count option' do + subject { report_class.queries[:counted] } - context "set to true" do + context 'set to true' do before { report_class.query :counted, count: true } it { is_expected.to be_a Compendium::Queries::Count } end - context "set to false" do + context 'set to false' do before { report_class.query :counted, count: false } it { is_expected.to be_a Compendium::Queries::Query } it { is_expected.not_to be_a Compendium::Queries::Count } @@ -152,7 +152,7 @@ end context 'when given a sum option' do - subject{ report_class.queries[:summed] } + subject { report_class.queries[:summed] } context 'set to a truthy value' do before { report_class.query :summed, sum: :assoc_count } @@ -169,38 +169,38 @@ end end - describe "#chart" do + describe '#chart' do before { subject.chart(:chart) } specify { expect(subject.queries).to include :chart } end - describe "#data" do + describe '#data' do before { subject.data(:data) } specify { expect(subject.queries).to include :data } end - describe "#metric" do - let(:metric_proc) { ->{ :metric } } + describe '#metric' do + let(:metric_proc) { -> { :metric } } before do subject.query :test subject.metric :test_metric, metric_proc, through: :test end - it "should add a metric to the given query" do + it 'should add a metric to the given query' do expect(subject.queries[:test].metrics.first.name).to eq(:test_metric) end - it "should set the metric command" do + it 'should set the metric command' do expect(subject.queries[:test].metrics.first.command).to eq(metric_proc) end - context "when through is specified" do - it "should raise an error if specified for an invalid query" do - expect{ subject.metric :test_metric, metric_proc, through: :fake }.to raise_error ArgumentError, 'query fake is not defined' + context 'when through is specified' do + it 'should raise an error if specified for an invalid query' do + expect { subject.metric :test_metric, metric_proc, through: :fake }.to raise_error ArgumentError, 'query fake is not defined' end - it "should allow metrics to be defined with a block" do + it 'should allow metrics to be defined with a block' do subject.metric :block_metric, through: :test do 123 end @@ -208,44 +208,44 @@ expect(subject.queries[:test].metrics[:block_metric].run(self, nil)).to eq(123) end - it "should allow metrics to be defined with a lambda" do - subject.metric :block_metric, -> * { 123 }, through: :test + it 'should allow metrics to be defined with a lambda' do + subject.metric :block_metric, -> (*) { 123 }, through: :test expect(subject.queries[:test].metrics[:block_metric].run(self, nil)).to eq(123) end end - context "when through is not specified" do + context 'when through is not specified' do before { subject.metric(:no_through_metric) { |data| data } } specify { expect(subject.queries).to include :__metric_no_through_metric } - it "should return the result of the query as the result of the metric" do + it 'should return the result of the query as the result of the metric' do expect(subject.queries[:__metric_no_through_metric].metrics[:no_through_metric].run(self, [123])).to eq(123) end end end - describe "#filter" do - let(:filter_proc) { ->{ :filter } } + describe '#filter' do + let(:filter_proc) { -> { :filter } } - it "should add a filter to the given query" do + it 'should add a filter to the given query' do subject.query :test subject.filter :test, &filter_proc expect(subject.queries[:test].filters).to include filter_proc end - it "should raise an error if there is no query of the given name" do - expect { subject.filter :test, &filter_proc }.to raise_error(ArgumentError, "query test is not defined") + it 'should raise an error if there is no query of the given name' do + expect { subject.filter :test, &filter_proc }.to raise_error(ArgumentError, 'query test is not defined') end - it "should allow multiple filters to be defined for the same query" do + it 'should allow multiple filters to be defined for the same query' do subject.query :test subject.filter :test, &filter_proc - subject.filter :test, &->{ :another_filter } + subject.filter :test, &-> { :another_filter } expect(subject.queries[:test].filters.count).to eq(2) end - it "should allow a filter to be applied to multiple queries at once" do + it 'should allow a filter to be applied to multiple queries at once' do subject.query :query1 subject.query :query2 subject.filter :query1, :query2, &filter_proc @@ -264,7 +264,7 @@ end it 'should raise an error if there is no query of the given name' do - expect { subject.table :test, &table_proc }.to raise_error(ArgumentError, "query test is not defined") + expect { subject.table :test, &table_proc }.to raise_error(ArgumentError, 'query test is not defined') end it 'should allow table settings to be applied to multiple queries at once' do @@ -294,13 +294,13 @@ end end - it "should allow previously defined queries to be redefined by name" do + it 'should allow previously defined queries to be redefined by name' do subject.query :test_query subject.test_query foo: :bar - expect(subject.queries[:test_query].options).to eq({ foo: :bar }) + expect(subject.queries[:test_query].options).to eq(foo: :bar) end - it "should allow previously defined queries to be accessed by name" do + it 'should allow previously defined queries to be accessed by name' do subject.query :test_query expect(subject.test_query).to eq(subject.queries[:test_query]) end diff --git a/spec/compendium/metric_spec.rb b/spec/compendium/metric_spec.rb index d0e5a6d..aad5ac2 100644 --- a/spec/compendium/metric_spec.rb +++ b/spec/compendium/metric_spec.rb @@ -12,58 +12,58 @@ def calculate(data) subject { described_class.new(:test_metric, :query, nil) } - describe "#run" do - it "should delegate the command to the context when the command is a symbol" do + describe '#run' do + it 'should delegate the command to the context when the command is a symbol' do subject.command = :calculate expect(subject.run(ctx, data)).to eq(1) end - it "should call the command when it is a proc" do - subject.command = -> d { d.flatten.inject(:+) } + it 'should call the command when it is a proc' do + subject.command = -> (d) { d.flatten.inject(:+) } expect(subject.run(ctx, data)).to eq(21) end - it "should allow procs that refer back to the context" do - subject.command = -> d { calculate(d) * 2 } + it 'should allow procs that refer back to the context' do + subject.command = -> (d) { calculate(d) * 2 } expect(subject.run(ctx, data)).to eq(2) end - context "when an if proc is given" do - before { subject.command = -> * { 100 } } + context 'when an if proc is given' do + before { subject.command = -> (*) { 100 } } - it "should calculate the metric if the proc evaluates to true" do - subject.options[:if] = ->{ true } + it 'should calculate the metric if the proc evaluates to true' do + subject.options[:if] = -> { true } expect(subject.run(ctx, data)).to eq(100) end - it "should not calculate the metric if the proc evaluates to false" do - subject.options[:if] = ->{ false } + it 'should not calculate the metric if the proc evaluates to false' do + subject.options[:if] = -> { false } expect(subject.run(ctx, data)).to be_nil end - it "should clear the result if the proc evaluates to false" do - subject.options[:if] = ->{ false } + it 'should clear the result if the proc evaluates to false' do + subject.options[:if] = -> { false } subject.result = 123 subject.run(ctx, data) expect(subject.result).to be_nil end end - context "when an unless proc is given" do - before { subject.command = -> * { 100 } } + context 'when an unless proc is given' do + before { subject.command = -> (*) { 100 } } - it "should calculate the metric if the proc evaluates to false" do - subject.options[:unless] = ->{ false } + it 'should calculate the metric if the proc evaluates to false' do + subject.options[:unless] = -> { false } expect(subject.run(ctx, data)).to eq(100) end - it "should not calculate the metric if the proc evaluates to true" do - subject.options[:unless] = ->{ true } + it 'should not calculate the metric if the proc evaluates to true' do + subject.options[:unless] = -> { true } expect(subject.run(ctx, data)).to be_nil end - it "should clear the result if the proc evaluates to false" do - subject.options[:unless] = ->{ true } + it 'should clear the result if the proc evaluates to false' do + subject.options[:unless] = -> { true } subject.result = 123 subject.run(ctx, data) expect(subject.result).to be_nil @@ -71,14 +71,14 @@ def calculate(data) end end - describe "#ran?" do - it "should return true if there are any results" do + describe '#ran?' do + it 'should return true if there are any results' do allow(subject).to receive_messages(result: 123) expect(subject).to have_ran end - it "should return false if there are no results" do + it 'should return false if there are no results' do expect(subject).not_to have_ran end end -end \ No newline at end of file +end diff --git a/spec/compendium/option_spec.rb b/spec/compendium/option_spec.rb index 1d25514..147f0a9 100644 --- a/spec/compendium/option_spec.rb +++ b/spec/compendium/option_spec.rb @@ -1,12 +1,12 @@ require 'compendium/option' describe Compendium::Option do - it "should raise an ArgumentError if no name is given" do - expect { described_class.new }.to raise_error ArgumentError, "name must be provided" + it 'should raise an ArgumentError if no name is given' do + expect { described_class.new }.to raise_error ArgumentError, 'name must be provided' end - it "should set up type predicates from the type option" do + it 'should set up type predicates from the type option' do o = described_class.new(name: :option, type: :date) expect(o).to be_date end -end \ No newline at end of file +end diff --git a/spec/compendium/param_types/boolean_spec.rb b/spec/compendium/param_types/boolean_spec.rb index 14e4f2c..e9a3a6b 100644 --- a/spec/compendium/param_types/boolean_spec.rb +++ b/spec/compendium/param_types/boolean_spec.rb @@ -2,7 +2,7 @@ require 'compendium/param_types/boolean' describe Compendium::ParamTypes::Boolean do - subject{ described_class.new(true) } + subject { described_class.new(true) } it { is_expected.not_to be_scalar } it { is_expected.to be_boolean } @@ -10,46 +10,46 @@ it { is_expected.not_to be_dropdown } it { is_expected.not_to be_radio } - it "should pass along 0 and 1" do + it 'should pass along 0 and 1' do expect(described_class.new(0)).to eq(0) expect(described_class.new(1)).to eq(1) end - it "should convert a numeric string to a number" do + it 'should convert a numeric string to a number' do expect(described_class.new('0')).to eq(0) expect(described_class.new('1')).to eq(1) end - it "should return 0 for a truthy value" do + it 'should return 0 for a truthy value' do expect(described_class.new(true)).to eq(0) expect(described_class.new(:abc)).to eq(0) end - it "should return 1 for a falsey value" do + it 'should return 1 for a falsey value' do expect(described_class.new(false)).to eq(1) expect(described_class.new(nil)).to eq(1) end - describe "#value" do - it "should return true for a truthy value" do + describe '#value' do + it 'should return true for a truthy value' do expect(described_class.new(true).value).to eq(true) expect(described_class.new(:abc).value).to eq(true) expect(described_class.new(0).value).to eq(true) end - it "should return false for a falsey value" do + it 'should return false for a falsey value' do expect(described_class.new(false).value).to eq(false) expect(described_class.new(nil).value).to eq(false) expect(described_class.new(1).value).to eq(false) end end - describe "#!" do - it "should return false if the boolean is true" do + describe '#!' do + it 'should return false if the boolean is true' do expect(!described_class.new(true)).to eq(false) end - it "should return true if the boolean is false" do + it 'should return true if the boolean is false' do expect(!described_class.new(false)).to eq(true) end end diff --git a/spec/compendium/param_types/date_spec.rb b/spec/compendium/param_types/date_spec.rb index 4893eab..d9e5724 100644 --- a/spec/compendium/param_types/date_spec.rb +++ b/spec/compendium/param_types/date_spec.rb @@ -2,7 +2,7 @@ require 'compendium/param_types/date' describe Compendium::ParamTypes::Date do - subject{ described_class.new(Date.today) } + subject { described_class.new(Date.today) } it { is_expected.not_to be_scalar } it { is_expected.not_to be_boolean } @@ -10,14 +10,14 @@ it { is_expected.not_to be_dropdown } it { is_expected.not_to be_radio } - it "should convert date strings to date objects" do - p = described_class.new("2010-05-20") + it 'should convert date strings to date objects' do + p = described_class.new('2010-05-20') expect(p).to eq(Date.new(2010, 5, 20)) end - it "should convert other date/time formats to date objects" do - described_class.new(DateTime.new(2010, 5, 20, 10, 30, 59)) == Date.new(2010, 5, 20) - described_class.new(Time.new(2010, 5, 20, 10, 30, 59)) == Date.new(2010, 5, 20) - described_class.new(Date.new(2010, 5, 20)) == Date.new(2010, 5, 20) + it 'should convert other date/time formats to date objects' do + expect(described_class.new(DateTime.new(2010, 5, 20, 10, 30, 59))).to eq(Date.new(2010, 5, 20)) + expect(described_class.new(Time.new(2010, 5, 20, 10, 30, 59))).to eq(Date.new(2010, 5, 20)) + expect(described_class.new(Date.new(2010, 5, 20))).to eq(Date.new(2010, 5, 20)) end end diff --git a/spec/compendium/param_types/dropdown_spec.rb b/spec/compendium/param_types/dropdown_spec.rb index b3ab593..ee053af 100644 --- a/spec/compendium/param_types/dropdown_spec.rb +++ b/spec/compendium/param_types/dropdown_spec.rb @@ -2,7 +2,7 @@ require 'compendium/param_types/dropdown' describe Compendium::ParamTypes::Dropdown do - subject{ described_class.new(0, %w(a b c)) } + subject { described_class.new(0, %w(a b c)) } it { is_expected.not_to be_scalar } it { is_expected.not_to be_boolean } diff --git a/spec/compendium/param_types/param_spec.rb b/spec/compendium/param_types/param_spec.rb index c8a6e30..cdfff5f 100644 --- a/spec/compendium/param_types/param_spec.rb +++ b/spec/compendium/param_types/param_spec.rb @@ -2,7 +2,7 @@ require 'compendium/param_types/param' describe Compendium::ParamTypes::Param do - subject{ described_class.new(:test) } + subject { described_class.new(:test) } it { is_expected.not_to be_scalar } it { is_expected.not_to be_boolean } @@ -10,7 +10,7 @@ it { is_expected.not_to be_dropdown } it { is_expected.not_to be_radio } - describe "#==" do + describe '#==' do it "should compare to the param's value" do allow(subject).to receive_messages(value: :test_value) expect(subject).to eq(:test_value) diff --git a/spec/compendium/param_types/radio_spec.rb b/spec/compendium/param_types/radio_spec.rb index 0e65a9e..d74d916 100644 --- a/spec/compendium/param_types/radio_spec.rb +++ b/spec/compendium/param_types/radio_spec.rb @@ -2,7 +2,7 @@ require 'compendium/param_types/radio' describe Compendium::ParamTypes::Radio do - subject{ described_class.new(0, %w(a b c)) } + subject { described_class.new(0, %w(a b c)) } it { is_expected.not_to be_scalar } it { is_expected.not_to be_boolean } diff --git a/spec/compendium/param_types/scalar_spec.rb b/spec/compendium/param_types/scalar_spec.rb index 2f00c80..01d0d83 100644 --- a/spec/compendium/param_types/scalar_spec.rb +++ b/spec/compendium/param_types/scalar_spec.rb @@ -2,7 +2,7 @@ require 'compendium/param_types/scalar' describe Compendium::ParamTypes::Scalar do - subject{ described_class.new(123) } + subject { described_class.new(123) } it { is_expected.to be_scalar } it { is_expected.not_to be_boolean } @@ -10,7 +10,7 @@ it { is_expected.not_to be_dropdown } it { is_expected.not_to be_radio } - it "should not change values" do + it 'should not change values' do expect(subject).to eq(123) end end diff --git a/spec/compendium/param_types/with_choices_spec.rb b/spec/compendium/param_types/with_choices_spec.rb index af88b9a..8bb66cc 100644 --- a/spec/compendium/param_types/with_choices_spec.rb +++ b/spec/compendium/param_types/with_choices_spec.rb @@ -2,39 +2,39 @@ require 'compendium/param_types/with_choices' describe Compendium::ParamTypes::WithChoices do - subject{ described_class.new(0, %w(a b c)) } + subject { described_class.new(0, %w(a b c)) } it { is_expected.not_to be_boolean } it { is_expected.not_to be_date } it { is_expected.not_to be_dropdown } it { is_expected.not_to be_radio } - it "should return the index when given an index" do - p = described_class.new(1, [:foo, :bar, :baz]) + it 'should return the index when given an index' do + p = described_class.new(1, %i(foo bar baz)) expect(p).to eq(1) end - it "should return the index when given a value" do - p = described_class.new(:foo, [:foo, :bar, :baz]) + it 'should return the index when given a value' do + p = described_class.new(:foo, %i(foo bar baz)) expect(p).to eq(0) end - it "should return the index when given a string value" do - p = described_class.new("2", [:foo, :bar, :baz]) + it 'should return the index when given a string value' do + p = described_class.new('2', %i(foo bar baz)) expect(p).to eq(2) end - it "should raise an error if given an invalid index" do - expect { described_class.new(3, [:foo, :bar, :baz]) }.to raise_error IndexError + it 'should raise an error if given an invalid index' do + expect { described_class.new(3, %i(foo bar baz)) }.to raise_error IndexError end - it "should raise an error if given a value that is not included in the choices" do - expect { described_class.new(:quux, [:foo, :bar, :baz]) }.to raise_error IndexError + it 'should raise an error if given a value that is not included in the choices' do + expect { described_class.new(:quux, %i(foo bar baz)) }.to raise_error IndexError end - describe "#value" do - it "should return the value of the given choice" do - p = described_class.new(2, [:foo, :bar, :baz]) + describe '#value' do + it 'should return the value of the given choice' do + p = described_class.new(2, %i(foo bar baz)) expect(p.value).to eq(:baz) end end diff --git a/spec/compendium/params_spec.rb b/spec/compendium/params_spec.rb index a25e3e4..57786ba 100644 --- a/spec/compendium/params_spec.rb +++ b/spec/compendium/params_spec.rb @@ -1,35 +1,35 @@ require 'compendium/params' describe Compendium::Params do - let(:options) { + let(:options) do opts = Collection[Compendium::Option] - opts << Compendium::Option.new(name: :starting_on, type: :date, default: ->{ Date.today }) + opts << Compendium::Option.new(name: :starting_on, type: :date, default: -> { Date.today }) opts << Compendium::Option.new(name: :ending_on, type: :date) opts << Compendium::Option.new(name: :report_type, type: :radio, choices: [:big, :small]) opts << Compendium::Option.new(name: :boolean, type: :boolean) opts << Compendium::Option.new(name: :another_boolean, type: :boolean) opts << Compendium::Option.new(name: :number, type: :scalar) opts - } + end - subject{ described_class.new(@params, options) } + subject { described_class.new(@params, options) } - it "should only allow keys that are given as options" do + it 'should only allow keys that are given as options' do @params = { starting_on: '2013-10-15', foo: :bar } expect(subject.keys).not_to include :foo end - it "should set missing options to their default value" do + it 'should set missing options to their default value' do @params = {} expect(subject.starting_on).to eq(Date.today) end - it "should set missing options to nil if there is no default value" do + it 'should set missing options to nil if there is no default value' do @params = {} expect(subject.ending_on).to be_nil end - describe "#validations" do + describe '#validations' do let(:report_class) { Class.new(described_class) } context 'presence' do diff --git a/spec/compendium/presenters/base_spec.rb b/spec/compendium/presenters/base_spec.rb index a8349ca..4a57241 100644 --- a/spec/compendium/presenters/base_spec.rb +++ b/spec/compendium/presenters/base_spec.rb @@ -6,14 +6,14 @@ end describe Compendium::Presenters::Base do - let(:template) { double("Template", delegated?: true) } + let(:template) { double('Template', delegated?: true) } subject { TestPresenter.new(template, :test) } - it "should allow the object name to be overridden" do + it 'should allow the object name to be overridden' do expect(subject.test_obj).to eq(:test) end - it "should delegate missing methods to the template object" do + it 'should delegate missing methods to the template object' do expect(template).to receive(:delegated?) expect(subject).to be_delegated end diff --git a/spec/compendium/presenters/chart_spec.rb b/spec/compendium/presenters/chart_spec.rb index ee0bc06..148cd20 100644 --- a/spec/compendium/presenters/chart_spec.rb +++ b/spec/compendium/presenters/chart_spec.rb @@ -2,7 +2,14 @@ require 'compendium/presenters/chart' describe Compendium::Presenters::Chart do - let(:template) { double('Template', protect_against_forgery?: false, request_forgery_protection_token: :authenticity_token, form_authenticity_token: "ABCDEFGHIJ").as_null_object } + let(:template) do + double( + 'Template', + protect_against_forgery?: false, + request_forgery_protection_token: :authenticity_token, + form_authenticity_token: 'ABCDEFGHIJ' + ).as_null_object + end let(:query) { double('Query', name: 'test_query', results: results, ran?: true, options: {}).as_null_object } let(:results) { Compendium::ResultSet.new([]) } @@ -26,7 +33,7 @@ specify { expect(subject.container).to eq('test_query') } end - context "when options are given" do + context 'when options are given' do before { allow(results).to receive(:records) { { one: [] } } } subject { described_class.new(template, query, :pie, index: :one) } @@ -34,22 +41,22 @@ specify { expect(subject.container).to eq('test_query') } end - context "when the query has not been run" do + context 'when the query has not been run' do before { allow(query).to receive_messages(ran?: false, url: '/path/to/query.json') } subject { described_class.new(template, query, :pie, params: { foo: 'bar' }) } specify { expect(subject.data).to eq('/path/to/query.json') } - specify { expect(subject.params).to eq({ report: { foo: 'bar' } }) } + specify { expect(subject.params).to eq(report: { foo: 'bar' }) } - context "when CSRF protection is enabled" do + context 'when CSRF protection is enabled' do before { allow(template).to receive_messages(protect_against_forgery?: true) } - specify { expect(subject.params).to include authenticity_token: "ABCDEFGHIJ" } + specify { expect(subject.params).to include authenticity_token: 'ABCDEFGHIJ' } end - context "when CSRF protection is disabled" do - specify { expect(subject.params).to_not include authenticity_token: "ABCDEFGHIJ" } + context 'when CSRF protection is disabled' do + specify { expect(subject.params).to_not include authenticity_token: 'ABCDEFGHIJ' } end end end diff --git a/spec/compendium/presenters/csv_spec.rb b/spec/compendium/presenters/csv_spec.rb index 12426a1..efa4f16 100644 --- a/spec/compendium/presenters/csv_spec.rb +++ b/spec/compendium/presenters/csv_spec.rb @@ -1,7 +1,7 @@ require 'compendium/presenters/csv' describe Compendium::Presenters::CSV do - let(:results) { double('Results', records: [{ group: 'A', one: 1, two: 2 }, { group: 'B', one: 3, two: 4 }], keys: [:group, :one, :two]) } + let(:results) { double('Results', records: [{ group: 'A', one: 1, two: 2 }, { group: 'B', one: 3, two: 4 }], keys: %i(group one two)) } let(:query) { double('Query', results: results, options: {}, table_settings: nil) } let(:presenter) { described_class.new(query) } @@ -17,7 +17,7 @@ end it "should use the query's table settings" do - allow(query).to receive(:table_settings).and_return(-> * { number_format '%0.0f' }) + allow(query).to receive(:table_settings).and_return(-> (*) { number_format '%0.0f' }) expect(presenter.render).to eq("group,one,two\nA,1,2\nB,3,4\n") end diff --git a/spec/compendium/presenters/option_spec.rb b/spec/compendium/presenters/option_spec.rb index 5a316c4..f597088 100644 --- a/spec/compendium/presenters/option_spec.rb +++ b/spec/compendium/presenters/option_spec.rb @@ -9,40 +9,166 @@ t end + let(:form) { double('Form') } + let(:ctx) { double('Context') } let(:option) { Compendium::Option.new(name: :test_option) } subject { described_class.new(template, option) } - describe "#name" do - it "should pass the name through I18n" do + describe '#name' do + it 'should pass the name through I18n' do expect(template).to receive(:t).with('options.test_option', anything) subject.name end end - describe "#note" do + describe '#note' do before { allow(template).to receive(:content_tag) } - it "should return nil if no note is specified" do + it 'should return nil if no note is specified' do expect(subject.note).to be_nil end - it "should pass to I18n if the note option is set to true" do - option.merge!(note: true) + it 'should pass to I18n if the note option is set to true' do + option[:note] = true expect(template).to receive(:t).with(:test_option_note) subject.note end - it "should pass to I18n if the note option is set" do - option.merge!(note: :the_note) + it 'should pass to I18n if the note option is set' do + option[:note] = :the_note expect(template).to receive(:t).with(:the_note) subject.note end - it "should create the note within a div with class option-note" do - option.merge!(note: true) + it 'should create the note within a div with class option-note' do + option[:note] = true expect(template).to receive(:content_tag).with(:div, anything, class: 'option-note') subject.note end end + + describe '#label' do + context 'when the option has a note' do + before do + allow(template).to receive(:content_tag) + allow(form).to receive(:label) { |_field, name| name } + end + + context 'when a note is provided' do + before { option[:note] = :test } + + context 'when AccessibleTooltip is present' do + before do + stub_const('AccessibleTooltip', Object.new) + expect(template).to receive(:accessible_tooltip).and_yield + end + + it 'should return a label with the tooltip' do + expect(form).to receive(:label).with(:test_option, 'options.test') + subject.label(form) + end + end + + it 'should translate the note' do + expect(template).to receive(:t).with('options.test', anything) + subject.label(form) + end + + it 'should translate the option name if no specific note is given' do + option[:note] = true + expect(template).to receive(:t).with('options.test_option_note', anything) + subject.label(form) + end + + it 'should render the note' do + expect(template).to receive(:content_tag).with(:div, 'options.test', class: 'option-note') + subject.label(form) + end + end + + it 'should render the label' do + expect(template).to receive(:content_tag).with(:span, 'options.test_option', class: 'option-label') + subject.label(form) + end + end + end + + describe '#input' do + before do + allow(template).to receive(:content_tag).and_yield + + option.options = { foo: :bar } + option.choices = [1, 2, 3] + end + + context 'with a scalar option' do + before { option.type = :scalar } + + it 'should render an text field' do + expect(form).to receive(:text_field).with(:test_option) + subject.input(ctx, form) + end + end + + context 'with a date option' do + before { option.type = :date } + + it 'should render a text field' do + expect(form).to receive(:text_field).with(:test_option) + subject.input(ctx, form) + end + + it 'should render a calendar date select if defined' do + stub_const('CalendarDateSelect', Object.new) + expect(form).to receive(:calendar_date_select).with(:test_option, anything) + subject.input(ctx, form) + end + end + + context 'with a dropdown option' do + before { option.type = :dropdown } + + it 'should render a select field' do + expect(form).to receive(:select).with(:test_option, [1, 2, 3], { foo: :bar }) + subject.input(ctx, form) + end + + it 'should raise if there are no choices' do + option.choices = nil + expect { subject.input(ctx, form) }.to raise_error ArgumentError + end + end + + context 'with a boolean option' do + before { option.type = :boolean } + + it 'should render radio buttons and labels for true and false' do + expect(form).to receive(:radio_button).with(:test_option, 0) + expect(form).to receive(:label).with(:test_option, 'true', value: 0) + expect(form).to receive(:radio_button).with(:test_option, 1) + expect(form).to receive(:label).with(:test_option, 'false', value: 1) + subject.input(ctx, form) + end + end + + context 'with a radio option' do + before { option.type = :radio } + + it 'should render radio buttons and labels for each option' do + expect(form).to receive(:radio_button).with(:test_option, 0) + expect(form).to receive(:label).with(:test_option, 1, value: 0) + expect(form).to receive(:radio_button).with(:test_option, 1) + expect(form).to receive(:label).with(:test_option, 2, value: 1) + expect(form).to receive(:radio_button).with(:test_option, 2) + expect(form).to receive(:label).with(:test_option, 3, value: 2) + subject.input(ctx, form) + end + + it 'should raise if there are no choices' do + option.choices = nil + expect { subject.input(ctx, form) }.to raise_error ArgumentError + end + end + end end diff --git a/spec/compendium/presenters/settings/table_spec.rb b/spec/compendium/presenters/settings/table_spec.rb index 046d8b1..ffd49a2 100644 --- a/spec/compendium/presenters/settings/table_spec.rb +++ b/spec/compendium/presenters/settings/table_spec.rb @@ -57,7 +57,7 @@ it 'should be callable multiple times' do subject.skip_total_for :foo, :bar subject.skip_total_for :quux - expect(subject.skipped_total_cols).to eq([:foo, :bar, :quux]) + expect(subject.skipped_total_cols).to eq(%i(foo bar quux)) end it 'should not care about type' do diff --git a/spec/compendium/queries/collection_spec.rb b/spec/compendium/queries/collection_spec.rb index 52b6931..89dd31f 100644 --- a/spec/compendium/queries/collection_spec.rb +++ b/spec/compendium/queries/collection_spec.rb @@ -3,40 +3,40 @@ describe Compendium::Queries::Collection do let(:collection) { { one: 1, two: 2, three: 3 } } - subject { described_class.new(:collection_query, { collection: collection }, -> _, key, item { [item * 2] }) } + subject { described_class.new(:collection_query, { collection: collection }, -> (_, _key, item) { [item * 2] }) } - before { allow_any_instance_of(Compendium::Queries::Query).to receive(:execute_query) { |instance, cmd| cmd } } + before { allow_any_instance_of(Compendium::Queries::Query).to receive(:execute_query) { |_instance, cmd| cmd } } - describe "#run" do + describe '#run' do context do before { subject.run(nil) } specify { expect(subject.results).to be_a Compendium::ResultSet } - specify { expect(subject.results).to eq({ one: [2], two: [4], three: [6] }) } + specify { expect(subject.results).to eq(one: [2], two: [4], three: [6]) } - context "when given an array instead of a hash" do + context 'when given an array instead of a hash' do let(:collection) { [1, 2, 3] } specify { expect(subject.results).to be_a Compendium::ResultSet } - specify { expect(subject.results).to eq({ 1 => [2], 2 => [4], 3 => [6] }) } + specify { expect(subject.results).to eq(1 => [2], 2 => [4], 3 => [6]) } end end - it "should not collect empty results" do - subject.proc = -> _, key, item { [item] if item > 2 } + it 'should not collect empty results' do + subject.proc = -> (_, _key, item) { [item] if item > 2 } subject.run(nil) - expect(subject.results).to eq({ three: [3] }) + expect(subject.results).to eq(three: [3]) end - context "when given another query" do - let(:q) { Compendium::Queries::Query.new(:q, {}, -> * { { one: 1, two: 2, three: 3 } }) } - subject { described_class.new(:collection, { collection: q }, -> _, key, item { [ item * 2 ] }) } + context 'when given another query' do + let(:q) { Compendium::Queries::Query.new(:q, {}, -> (*) { { one: 1, two: 2, three: 3 } }) } + subject { described_class.new(:collection, { collection: q }, -> (_, _key, item) { [item * 2] }) } before { subject.run(nil) if RSpec.current_example.metadata.fetch(:run_query, true) } - specify { expect(subject.results).to eq({ one: [2], two: [4], three: [6] }) } + specify { expect(subject.results).to eq(one: [2], two: [4], three: [6]) } - it "should not re-run the query if it has already ran", run_query: false do + it 'should not re-run the query if it has already ran', run_query: false do q.run(nil) expect(q).not_to receive(:run) subject.run(nil) @@ -44,16 +44,16 @@ end context 'when given a proc' do - let(:proc) { -> * { [1, 2, 3] } } - subject { described_class.new(:collection, { collection: proc }, -> _, key, item { [ item * 2 ] }) } + let(:proc) { -> (*) { [1, 2, 3] } } + subject { described_class.new(:collection, { collection: proc }, -> (_, _key, item) { [item * 2] }) } it 'should use the collection from the proc' do subject.run(nil) - expect(subject.results).to eq({ 1 => [2], 2 => [4], 3 => [6] }) + expect(subject.results).to eq(1 => [2], 2 => [4], 3 => [6]) end context do - let(:proc) { -> * { raise ArgumentError } } + let(:proc) { -> (*) { raise ArgumentError } } it 'should not run the proc until runtime' do expect { subject }.to_not raise_error end diff --git a/spec/compendium/queries/count_spec.rb b/spec/compendium/queries/count_spec.rb index cbc4ada..f8e76f9 100644 --- a/spec/compendium/queries/count_spec.rb +++ b/spec/compendium/queries/count_spec.rb @@ -23,7 +23,7 @@ def count results = { 1 => 340, 2 => 204, 3 => 983 } if @order - results = results.sort_by{ |r| r[1] } + results = results.sort_by { |r| r[1] } results.reverse! if @reverse results = Hash[results] end @@ -33,21 +33,21 @@ def count end describe Compendium::Queries::Count do - subject { described_class.new(:counted_query, { count: true }, -> * { @counter }) } + subject { described_class.new(:counted_query, { count: true }, -> (*) { @counter }) } it 'should have a default order' do expect(subject.options[:order]).to eq('COUNT(*)') expect(subject.options[:reverse]).to eq(true) end - describe "#run" do - it "should call count on the proc result" do + describe '#run' do + it 'should call count on the proc result' do @counter = SingleCounter.new expect(@counter).to receive(:count).and_return(1234) subject.run(nil, self) end - it "should return the count" do + it 'should return the count' do @counter = SingleCounter.new expect(subject.run(nil, self)).to eq([1792]) end @@ -55,8 +55,8 @@ def count context 'when given a hash' do before { @counter = MultipleCounter.new } - it "should return a hash" do - expect(subject.run(nil, self)).to eq({ 3 => 983, 1 => 340, 2 => 204 }) + it 'should return a hash' do + expect(subject.run(nil, self)).to eq(3 => 983, 1 => 340, 2 => 204) end it 'should be ordered in descending order' do @@ -69,7 +69,7 @@ def count end end - it "should raise an error if the proc does not respond to count" do + it 'should raise an error if the proc does not respond to count' do @counter = Class.new expect { subject.run(nil, self) }.to raise_error Compendium::Queries::InvalidCommand end diff --git a/spec/compendium/queries/query_spec.rb b/spec/compendium/queries/query_spec.rb index d6b1447..818d82b 100644 --- a/spec/compendium/queries/query_spec.rb +++ b/spec/compendium/queries/query_spec.rb @@ -2,13 +2,13 @@ require 'compendium/queries/query' describe Compendium::Queries::Query do - describe "#initialize" do - let(:options) { double("Options") } - let(:proc) { double("Proc") } + describe '#initialize' do + let(:options) { double('Options') } + let(:proc) { double('Proc') } - context "when supplying a report" do + context 'when supplying a report' do let(:r) { Compendium::Report.new } - subject { described_class.new(r, :test, options, proc)} + subject { described_class.new(r, :test, options, proc) } specify { expect(subject.report).to eq(r) } specify { expect(subject.name).to eq(:test) } @@ -16,8 +16,8 @@ specify { expect(subject.proc).to eq(proc) } end - context "when not supplying a report" do - subject { described_class.new(:test, options, proc)} + context 'when not supplying a report' do + subject { described_class.new(:test, options, proc) } specify { expect(subject.report).to be_nil } specify { expect(subject.name).to eq(:test) } @@ -26,8 +26,8 @@ end end - describe "#run" do - let(:command) { -> * { [{ value: 1 }, { value: 2 }] } } + describe '#run' do + let(:command) { -> (*) { [{ value: 1 }, { value: 2 }] } } let(:query) do described_class.new(:test, {}, command) end @@ -36,44 +36,44 @@ allow(query).to receive(:fetch_results) { |c| c } end - it "should return the result of the query" do + it 'should return the result of the query' do results = query.run(nil) expect(results).to be_a Compendium::ResultSet expect(results.to_a).to eq([{ 'value' => 1 }, { 'value' => 2 }]) end - it "should mark the query as having ran" do + it 'should mark the query as having ran' do query.run(nil) expect(query).to have_run end - it "should not affect any cloned queries" do + it 'should not affect any cloned queries' do q2 = query.clone query.run(nil) expect(q2).not_to have_run end - it "should return an empty result set if running an query with no proc" do + it 'should return an empty result set if running an query with no proc' do query = described_class.new(:blank, {}, nil) expect(query.run(nil)).to be_empty end - it "should filter the result set if a filter is provided" do - query.add_filter(-> data { data.reject{ |d| d[:value].odd? } }) + it 'should filter the result set if a filter is provided' do + query.add_filter(-> (data) { data.reject { |d| d[:value].odd? } }) expect(query.run(nil).to_a).to eq([{ 'value' => 2 }]) end - it "should run multiple filters if given" do - query.add_filter(-> data { data.reject{ |d| d[:value].odd? } }) - query.add_filter(-> data { data.reject{ |d| d[:value].even? } }) + it 'should run multiple filters if given' do + query.add_filter(-> (data) { data.reject { |d| d[:value].odd? } }) + query.add_filter(-> (data) { data.reject { |d| d[:value].even? } }) expect(query.run(nil)).to be_empty end it 'should allow the result set to be a single hash when filters are present' do - query = described_class.new(:test, {}, -> * { { value1: 1, value2: 2, value3: 3 } }) + query = described_class.new(:test, {}, -> (*) { { value1: 1, value2: 2, value3: 3 } }) allow(query).to receive(:fetch_results) { |c| c } - query.add_filter(-> d { d }) + query.add_filter(-> (d) { d }) query.run(nil) expect(query.results.records).to eq({ value1: 1, value2: 2, value3: 3 }.with_indifferent_access) end @@ -86,7 +86,7 @@ end let(:command) do - -> c { -> * { c } }.(cmd) + -> (c) { -> (*) { c } }.call(cmd) end before { query.options[:order] = 'col1' } @@ -108,7 +108,7 @@ end end - context "when the query belongs to a report class" do + context 'when the query belongs to a report class' do let(:report) do Class.new(Compendium::Report) do query(:test) { [1, 2, 3] } @@ -117,70 +117,70 @@ subject { report.queries[:test] } - before { allow_any_instance_of(described_class).to receive(:fetch_results) { |instance, c| c } } + before { allow_any_instance_of(described_class).to receive(:fetch_results) { |_instance, c| c } } - it "should return its results" do + it 'should return its results' do expect(subject.run(nil)).to eq([1, 2, 3]) end - it "should not affect the report" do + it 'should not affect the report' do subject.run(nil) expect(report.queries[:test].results).to be_nil end - it "should not affect future instances of the report" do + it 'should not affect future instances of the report' do subject.run(nil) expect(report.new.queries[:test].results).to be_nil end end end - describe "#nil?" do + describe '#nil?' do it "should return true if the query's proc is nil" do expect(Compendium::Queries::Query.new(:test, {}, nil)).to be_nil end it "should return false if the query's proc is not nil" do - expect(Compendium::Queries::Query.new(:test, {}, ->{})).not_to be_nil + expect(Compendium::Queries::Query.new(:test, {}, -> {})).not_to be_nil end end - describe "#render_chart" do - let(:template) { double("Template") } - subject { described_class.new(:test, {}, -> * {}) } + describe '#render_chart' do + let(:template) { double('Template') } + subject { described_class.new(:test, {}, -> (*) {}) } - it "should initialize a new Chart presenter if the query has no results" do + it 'should initialize a new Chart presenter if the query has no results' do allow(subject).to receive_messages(empty?: true) - expect(Compendium::Presenters::Chart).to receive(:new).with(template, subject).and_return(double("Presenter").as_null_object) + expect(Compendium::Presenters::Chart).to receive(:new).with(template, subject).and_return(double('Presenter').as_null_object) subject.render_chart(template) end - it "should initialize a new Chart presenter if the query has results" do + it 'should initialize a new Chart presenter if the query has results' do allow(subject).to receive_messages(empty?: false) - expect(Compendium::Presenters::Chart).to receive(:new).with(template, subject).and_return(double("Presenter").as_null_object) + expect(Compendium::Presenters::Chart).to receive(:new).with(template, subject).and_return(double('Presenter').as_null_object) subject.render_chart(template) end end - describe "#render_table" do - let(:template) { double("Template") } - subject { described_class.new(:test, {}, -> * {}) } + describe '#render_table' do + let(:template) { double('Template') } + subject { described_class.new(:test, {}, -> (*) {}) } - it "should return nil if the query has no results" do + it 'should return nil if the query has no results' do allow(subject).to receive_messages(empty?: true) expect(subject.render_table(template)).to be_nil end - it "should initialize a new Table presenter if the query has results" do + it 'should initialize a new Table presenter if the query has results' do allow(subject).to receive_messages(empty?: false) - expect(Compendium::Presenters::Table).to receive(:new).with(template, subject).and_return(double("Presenter").as_null_object) + expect(Compendium::Presenters::Table).to receive(:new).with(template, subject).and_return(double('Presenter').as_null_object) subject.render_table(template) end end - describe "#url" do - let(:report) { double("Report") } - subject { described_class.new(:test, {}, ->{}) } + describe '#url' do + let(:report) { double('Report') } + subject { described_class.new(:test, {}, -> {}) } before { subject.report = report } it "should build a URL using its report's URL" do diff --git a/spec/compendium/queries/sum_spec.rb b/spec/compendium/queries/sum_spec.rb index a48fe7f..d7ae728 100644 --- a/spec/compendium/queries/sum_spec.rb +++ b/spec/compendium/queries/sum_spec.rb @@ -3,7 +3,7 @@ require 'compendium/report' class SingleSummer - def sum(col) + def sum(*) 1792 end end @@ -19,11 +19,11 @@ def reverse_order self end - def sum(col) + def sum(*) results = { 1 => 340, 2 => 204, 3 => 983 } if @order - results = results.sort_by{ |r| r[1] } + results = results.sort_by { |r| r[1] } results.reverse! if @reverse results = Hash[results] end @@ -33,21 +33,21 @@ def sum(col) end describe Compendium::Queries::Sum do - subject { described_class.new(:counted_query, :col, { sum: :col }, -> * { @counter }) } + subject { described_class.new(:counted_query, :col, { sum: :col }, -> (*) { @counter }) } it 'should have a default order' do expect(subject.options[:order]).to eq('SUM(col)') expect(subject.options[:reverse]).to eq(true) end - describe "#run" do - it "should call sum on the proc result" do + describe '#run' do + it 'should call sum on the proc result' do @counter = SingleSummer.new expect(@counter).to receive(:sum).with(:col).and_return(1234) subject.run(nil, self) end - it "should return the sum" do + it 'should return the sum' do @counter = SingleSummer.new expect(subject.run(nil, self)).to eq([1792]) end @@ -55,8 +55,8 @@ def sum(col) context 'when given a hash' do before { @counter = MultipleSummer.new } - it "should return a hash if given" do - expect(subject.run(nil, self)).to eq({ 3 => 983, 1 => 340, 2 => 204 }) + it 'should return a hash if given' do + expect(subject.run(nil, self)).to eq(3 => 983, 1 => 340, 2 => 204) end it 'should be ordered in descending order' do @@ -69,7 +69,7 @@ def sum(col) end end - it "should raise an error if the proc does not respond to sum" do + it 'should raise an error if the proc does not respond to sum' do @counter = Class.new expect { subject.run(nil, self) }.to raise_error Compendium::Queries::InvalidCommand end diff --git a/spec/compendium/queries/through_spec.rb b/spec/compendium/queries/through_spec.rb index 38485a1..a986267 100644 --- a/spec/compendium/queries/through_spec.rb +++ b/spec/compendium/queries/through_spec.rb @@ -1,14 +1,14 @@ require 'compendium/queries/through' describe Compendium::Queries::Through do - describe "#initialize" do - let(:options) { double("Options") } - let(:proc) { double("Proc") } - let(:through) { double("Query") } + describe '#initialize' do + let(:options) { double('Options') } + let(:proc) { double('Proc') } + let(:through) { double('Query') } - context "when supplying a report" do + context 'when supplying a report' do let(:r) { Compendium::Report.new } - subject { described_class.new(r, :test, through, options, proc)} + subject { described_class.new(r, :test, through, options, proc) } specify { expect(subject.report).to eq(r) } specify { expect(subject.name).to eq(:test) } @@ -17,8 +17,8 @@ specify { expect(subject.proc).to eq(proc) } end - context "when not supplying a report" do - subject { described_class.new(:test, through, options, proc)} + context 'when not supplying a report' do + subject { described_class.new(:test, through, options, proc) } specify { expect(subject.report).to be_nil } specify { expect(subject.name).to eq(:test) } @@ -28,63 +28,63 @@ end end - describe "#run" do - let(:parent1) { Compendium::Queries::Query.new(:parent1, {}, -> * { }) } - let(:parent2) { Compendium::Queries::Query.new(:parent2, {}, -> * { }) } - let(:parent3) { Compendium::Queries::Query.new(:parent3, {}, -> * { [[1, 2, 3]] }) } + describe '#run' do + let(:parent1) { Compendium::Queries::Query.new(:parent1, {}, -> (*) {}) } + let(:parent2) { Compendium::Queries::Query.new(:parent2, {}, -> (*) {}) } + let(:parent3) { Compendium::Queries::Query.new(:parent3, {}, -> (*) { [[1, 2, 3]] }) } before { allow(parent3).to receive(:execute_query) { |cmd| cmd } } - it "should pass along the params if the proc collects it" do + it 'should pass along the params if the proc collects it' do params = { one: 1, two: 2 } - q = described_class.new(:through, parent3, {}, -> r, params { params }) + q = described_class.new(:through, parent3, {}, -> (_r, p) { p }) expect(q.run(params)).to eq(params) end - it "should pass along the params if the proc has a splat argument" do + it 'should pass along the params if the proc has a splat argument' do params = { one: 1, two: 2 } - q = described_class.new(:through, parent3, {}, -> *args { args }) + q = described_class.new(:through, parent3, {}, -> (*args) { args }) expect(q.run(params)).to eq([[[1, 2, 3]], params.with_indifferent_access]) end it "should not pass along the params if the proc doesn't collects it" do params = { one: 1, two: 2 } - q = described_class.new(:through, parent3, {}, -> r { r }) + q = described_class.new(:through, parent3, {}, -> (r) { r }) expect(q.run(params)).to eq([[1, 2, 3]]) end - it "should not affect its parent query" do - q = described_class.new(:through, parent3, {}, -> r { r.map!{ |i| i * 2 } }) + it 'should not affect its parent query' do + q = described_class.new(:through, parent3, {}, -> (r) { r.map! { |i| i * 2 } }) expect(q.run(nil)).to eq([[1, 2, 3, 1, 2, 3]]) expect(parent3.results).to eq([[1, 2, 3]]) end - context "with a single parent" do - subject { described_class.new(:sub, parent1, {}, -> r { r.first }) } + context 'with a single parent' do + subject { described_class.new(:sub, parent1, {}, -> (r) { r.first }) } - it "should not try to run a through query if the parent query has no results" do + it 'should not try to run a through query if the parent query has no results' do expect { subject.run(nil) }.to_not raise_error expect(subject.results).to be_empty end end - context "with multiple parents" do - subject { described_class.new(:sub, [parent1, parent2], {}, -> r { r.first }) } + context 'with multiple parents' do + subject { described_class.new(:sub, [parent1, parent2], {}, -> (r) { r.first }) } - it "should not try to run a through query with multiple parents all of which have no results" do + it 'should not try to run a through query with multiple parents all of which have no results' do expect { subject.run(nil) }.to_not raise_error expect(subject.results).to be_empty end - it "should allow non blank queries" do + it 'should allow non blank queries' do subject.through = parent3 subject.run(nil) expect(subject.results).to eq([1, 2, 3]) end end - context "when the through option is an actual query" do - subject { described_class.new(:sub, parent3, {}, -> r { r.first }) } + context 'when the through option is an actual query' do + subject { described_class.new(:sub, parent3, {}, -> (r) { r.first }) } before { subject.run(nil) } diff --git a/spec/compendium/report_spec.rb b/spec/compendium/report_spec.rb index 40813ea..7f10afc 100644 --- a/spec/compendium/report_spec.rb +++ b/spec/compendium/report_spec.rb @@ -1,4 +1,5 @@ -require 'compendium/report' +require 'spec_helper' +require 'compendium/queries' describe Compendium::Report do subject { described_class } @@ -6,17 +7,17 @@ specify { expect(subject.queries).to be_empty } specify { expect(subject.options).to be_empty } - it "should not do anything when run" do + it 'should not do anything when run' do report = subject.new report.run expect(report.results).to be_empty end - context "with multiple instances" do + context 'with multiple instances' do let(:report_class) do Class.new(Compendium::Report) do query :test - metric :test_metric, ->{}, through: :test + metric :test_metric, -> {}, through: :test end end @@ -28,13 +29,13 @@ specify { expect(subject.metrics).to_not equal report2.metrics } end - describe ".report_name" do + describe '.report_name' do subject { TestReport = Class.new(described_class) } specify { expect(subject.report_name).to eq(:test) } end - describe "#run" do - context do + describe '#run' do + context 'test' do let(:report_class) do Class.new(Compendium::Report) do option :first, :date @@ -44,7 +45,7 @@ [params[:first].__getobj__, params[:second].__getobj__] end - metric :lambda_metric, -> results { results.to_a.max }, through: :test + metric :lambda_metric, -> (results) { results.to_a.max }, through: :test metric(:block_metric, through: :test) { |results| results.to_a.max } metric(:implicit_metric) { [1, 2, 3].count } end @@ -54,42 +55,42 @@ let!(:report2) { report_class.new } before do - allow_any_instance_of(Compendium::Queries::Query).to receive(:fetch_results) { |instance, c| c } + allow_any_instance_of(Compendium::Queries::Query).to receive(:fetch_results) { |_instance, c| c } subject.run end specify { expect(subject.test_results.records).to eq [Date.new(2010, 10, 10), Date.new(2011, 11, 11)] } - it "should allow metric results to be accessed through a query" do + it 'should allow metric results to be accessed through a query' do expect(subject.test.metrics[:lambda_metric].result).to eq(Date.new(2011, 11, 11)) end - it "should run its metrics defined as a lambda" do + it 'should run its metrics defined as a lambda' do expect(subject.metrics[:lambda_metric].result).to eq(Date.new(2011, 11, 11)) end - it "should run its metrics defined as a block" do + it 'should run its metrics defined as a block' do expect(subject.metrics[:block_metric].result).to eq(Date.new(2011, 11, 11)) end - it "should run its implicit metrics" do + it 'should run its implicit metrics' do expect(subject.metrics[:implicit_metric].result).to eq(3) end - it "should not affect other instances of the report class" do + it 'should not affect other instances of the report class' do expect(report2.test.results).to be_nil expect(report2.metrics[:lambda_metric].result).to be_nil end - it "should not affect the class collections" do + it 'should not affect the class collections' do expect(report_class.test.results).to be_nil end - context "with through queries" do + context 'with through queries' do let(:report_class) do Class.new(Compendium::Report) do option :first, :boolean, default: false - query(:test) { |params| !!params[:first] ? [100, 200, 400, 800] : [1600, 3200, 6400]} + query(:test) { |params| params[:first].value ? [100, 200, 400, 800] : [1600, 3200, 6400] } query(:through, through: :test) { |results| [results.first] } end end @@ -102,7 +103,7 @@ expect(report2.test).not_to have_run end - it "should not affect other instances" do + it 'should not affect other instances' do report2.queries.each { |q| allow(q).to receive(:fetch_results) { |c| c } } report2.run expect(report2.through.results).to eq([1600]) @@ -110,7 +111,7 @@ end end - context "when specifying which queries to run" do + context 'when specifying which queries to run' do let(:report_class) do Class.new(Compendium::Report) do query :first @@ -120,27 +121,27 @@ subject { report_class.new } - it "should raise an error if given :only and :except options" do - expect{ subject.run(nil, only: :first, except: :second) }.to raise_error(ArgumentError) + it 'should raise an error if given :only and :except options' do + expect { subject.run(nil, only: :first, except: :second) }.to raise_error(ArgumentError) end - it "should raise an error if given an invalid query name" do - expect{ subject.run(nil, only: :foo) }.to raise_error(ArgumentError) + it 'should raise an error if given an invalid query name' do + expect { subject.run(nil, only: :foo) }.to raise_error(ArgumentError) end - it "should run all queries if nothing is specified" do + it 'should run all queries if nothing is specified' do subject.run(nil) expect(subject.first).to have_run expect(subject.second).to have_run end - it "should only run queries specified by :only" do + it 'should only run queries specified by :only' do subject.run(nil, only: :first) expect(subject.first).to have_run expect(subject.second).not_to have_run end - it "should allow multiple queries to be specified by :only" do + it 'should allow multiple queries to be specified by :only' do report_class.query(:third) {} subject.run(nil, only: [:first, :third]) expect(subject.first).to have_run @@ -148,25 +149,25 @@ expect(subject.third).to have_run end - it "should not run through queries related to a query specified by only if not also specified" do + it 'should not run through queries related to a query specified by only if not also specified' do report_class.query(:through, through: :first) {} subject.run(nil, only: :first) expect(subject.through).not_to have_run end - it "should run through queries related to a query specified by only if also specified" do + it 'should run through queries related to a query specified by only if also specified' do report_class.query(:through, through: :first) {} subject.run(nil, only: [:first, :through]) expect(subject.through).to have_run end - it "should not run queries specified by :except" do + it 'should not run queries specified by :except' do subject.run(nil, except: :first) expect(subject.first).not_to have_run expect(subject.second).to have_run end - it "should allow multiple queries to be specified by :except" do + it 'should allow multiple queries to be specified by :except' do report_class.query(:third) {} subject.run(nil, except: [:first, :third]) expect(subject.first).not_to have_run @@ -174,7 +175,7 @@ expect(subject.third).not_to have_run end - it "should not run through queries excepted related to a query even if the main query is not excepted" do + it 'should not run through queries excepted related to a query even if the main query is not excepted' do report_class.query(:through, through: :first) {} subject.run(nil, except: :through) expect(subject.through).not_to have_run @@ -183,7 +184,7 @@ end end - describe "predicate methods" do + context 'class name predicates' do before do OneReport = Class.new(Compendium::Report) TwoReport = Class.new(Compendium::Report) @@ -207,21 +208,21 @@ specify { expect(TwoReport).to be_two } end - describe "parameters" do + describe 'parameters' do let(:report_class) { Class.new(subject) } let(:report_class2) { Class.new(report_class) } - it "should include ancestors params" do + it 'should include ancestors params' do expect(report_class.params_class.ancestors).to include subject.params_class end - it "should inherit validations" do + it 'should inherit validations' do report_class.params_class.validates :foo, presence: true expect(report_class2.params_class.validators_on(:foo)).not_to be_nil end end - describe "#valid?" do + describe '#valid?' do context 'built-in validations' do let(:report_class) do Class.new(described_class) do @@ -229,12 +230,12 @@ end end - it "should return true if there are no validation failures" do + it 'should return true if there are no validation failures' do r = report_class.new(id: 5) expect(r).to be_valid end - it "should return false if there are validation failures" do + it 'should return false if there are validation failures' do r = report_class.new(id: nil) expect(r).not_to be_valid expect(r.errors.keys).to include :id @@ -252,12 +253,12 @@ end end - it "should return true if there are no validation failures" do + it 'should return true if there are no validation failures' do r = report_class.new(number: 4) expect(r).to be_valid end - it "should return false if there are validation failures" do + it 'should return false if there are validation failures' do r = report_class.new(number: 5) expect(r).not_to be_valid expect(r.errors.keys).to include :number @@ -266,7 +267,7 @@ end describe '.filter' do - let(:filter_proc) { -> * {} } + let(:filter_proc) { -> (*) {} } let(:report_class) do Class.new(described_class) do @@ -319,4 +320,69 @@ expect(subject.exports?(:xls)).to be_falsey end end + + describe '#method_missing' do + let(:report_class) do + Class.new(described_class) do + option :foo, :scalar + option :bar, :scalar + query :my_query + end + end + + subject { report_class.new(foo: 123) } + + it 'should return a query if given a query name' do + expect(subject.my_query).to eq(subject.queries[:my_query]) + end + + it 'should return query results if given a query_results' do + subject.run + expect(subject.my_query_results).to eql(subject.results[:my_query]) + end + + it 'should return the param value if given a param name' do + expect(subject.foo).to eq(123) + end + + it 'should return the param truthiness if given a param predicate' do + expect(subject.foo?).to be_truthy + expect(subject.bar?).to be_falsey + end + end + + describe '#respond_to_missing?' do + let(:report_class) do + Class.new(described_class) do + option :foo, :scalar + query :my_query + end + end + + subject { report_class.new } + + it 'should accept the name of a query' do + expect(subject).to respond_to :my_query + end + + it 'should accept the name of a query with _results' do + expect(subject).to respond_to :my_query_results + end + + it 'should accept the name of an option' do + expect(subject).to respond_to :foo + end + + it 'should accept the name of an option as a predicate' do + expect(subject).to respond_to :foo? + end + + it 'should not accept the name of an option with _results' do + expect(subject).to_not respond_to :foo_results + end + + it 'should not accept the name of a query as a predicate' do + expect(subject).to_not respond_to :my_query? + end + end end diff --git a/spec/compendium/result_set_spec.rb b/spec/compendium/result_set_spec.rb index 3537770..3e85789 100644 --- a/spec/compendium/result_set_spec.rb +++ b/spec/compendium/result_set_spec.rb @@ -1,26 +1,26 @@ require 'compendium/result_set' describe Compendium::ResultSet do - describe "#initialize" do - subject{ described_class.new(results).records } + describe '#initialize' do + subject { described_class.new(results).records } - context "when given an array" do + context 'when given an array' do let(:results) { [1, 2, 3] } it { is_expected.to eq([1, 2, 3]) } end - context "when given an array of hashes" do - let(:results) { [{one: 1}, {two: 2}] } - it { is_expected.to eq([{"one" => 1}, {"two" => 2}]) } + context 'when given an array of hashes' do + let(:results) { [{ one: 1 }, { two: 2 }] } + it { is_expected.to eq([{ 'one' => 1 }, { 'two' => 2 }]) } specify { expect(subject.first).to be_a ActiveSupport::HashWithIndifferentAccess } end - context "when given a hash" do + context 'when given a hash' do let(:results) { { one: 1, two: 2 } } - it { is_expected.to eq({ one: 1, two: 2 }) } + it { is_expected.to eq(one: 1, two: 2) } end - context "when given a scalar" do + context 'when given a scalar' do let(:results) { 3 } it { is_expected.to eq([3]) } end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b84478f..02ca232 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,7 @@ -$:.unshift File.expand_path("../../app/classes", __FILE__) - RSpec.configure do |rspec| rspec.mock_with :rspec do |mocks| mocks.yield_receiver_to_any_instance_implementation_blocks = true end end + +require 'compendium' From 0b3bcca92bf96798cb995c70d3c1050c4e7cb9f7 Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Mon, 29 Jan 2018 11:10:34 -0500 Subject: [PATCH 06/23] Replace autoload with actual requires --- lib/compendium.rb | 25 +++++++++++++------------ lib/compendium/param_types.rb | 14 +++++++------- lib/compendium/presenters.rb | 16 ++++++++-------- lib/compendium/presenters/settings.rb | 4 ++-- lib/compendium/queries.rb | 10 +++++----- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/lib/compendium.rb b/lib/compendium.rb index da717f1..9699c6a 100644 --- a/lib/compendium.rb +++ b/lib/compendium.rb @@ -4,18 +4,19 @@ require 'active_support/configurable' module Compendium - autoload :AbstractChartProvider, 'compendium/abstract_chart_provider' - autoload :ChartProvider, 'compendium/abstract_chart_provider' - autoload :ContextWrapper, 'compendium/context_wrapper' - autoload :DSL, 'compendium/dsl' - autoload :Metric, 'compendium/metric' - autoload :Option, 'compendium/option' - autoload :Params, 'compendium/params' - autoload :ParamTypes, 'compendium/param_types' - autoload :Presenters, 'compendium/presenters' - autoload :Queries, 'compendium/queries' - autoload :ResultSet, 'compendium/result_set' - autoload :Report, 'compendium/report' + require 'compendium/abstract_chart_provider' + require 'compendium/abstract_chart_provider' + require 'compendium/context_wrapper' + require 'compendium/dsl' + require 'compendium/engine' + require 'compendium/metric' + require 'compendium/option' + require 'compendium/params' + require 'compendium/param_types' + require 'compendium/presenters' + require 'compendium/queries' + require 'compendium/result_set' + require 'compendium/report' def self.reports @reports ||= [] diff --git a/lib/compendium/param_types.rb b/lib/compendium/param_types.rb index 0c988dd..0247a61 100644 --- a/lib/compendium/param_types.rb +++ b/lib/compendium/param_types.rb @@ -1,11 +1,11 @@ module Compendium module ParamTypes - autoload :Param, 'compendium/param_types/param' - autoload :Boolean, 'compendium/param_types/boolean' - autoload :Date, 'compendium/param_types/date' - autoload :Dropdown, 'compendium/param_types/dropdown' - autoload :WithChoices, 'compendium/param_types/with_choices' - autoload :Radio, 'compendium/param_types/radio' - autoload :Scalar, 'compendium/param_types/scalar' + require 'compendium/param_types/param' + require 'compendium/param_types/boolean' + require 'compendium/param_types/date' + require 'compendium/param_types/dropdown' + require 'compendium/param_types/with_choices' + require 'compendium/param_types/radio' + require 'compendium/param_types/scalar' end end diff --git a/lib/compendium/presenters.rb b/lib/compendium/presenters.rb index 8e39411..a4ba5dd 100644 --- a/lib/compendium/presenters.rb +++ b/lib/compendium/presenters.rb @@ -1,12 +1,12 @@ module Compendium module Presenters - autoload :Base, 'compendium/presenters/base' - autoload :Chart, 'compendium/presenters/chart' - autoload :CSV, 'compendium/presenters/csv' - autoload :Metric, 'compendium/presenters/metric' - autoload :Option, 'compendium/presenters/option' - autoload :Query, 'compendium/presenters/query' - autoload :Table, 'compendium/presenters/table' - autoload :Settings, 'compendium/presenters/settings' + require 'compendium/presenters/base' + require 'compendium/presenters/chart' + require 'compendium/presenters/csv' + require 'compendium/presenters/metric' + require 'compendium/presenters/option' + require 'compendium/presenters/query' + require 'compendium/presenters/table' + require 'compendium/presenters/settings' end end diff --git a/lib/compendium/presenters/settings.rb b/lib/compendium/presenters/settings.rb index 06c72f3..6dfddef 100644 --- a/lib/compendium/presenters/settings.rb +++ b/lib/compendium/presenters/settings.rb @@ -1,8 +1,8 @@ module Compendium module Presenters module Settings - autoload :Query, 'compendium/presenters/settings/query' - autoload :Table, 'compendium/presenters/settings/table' + require 'compendium/presenters/settings/query' + require 'compendium/presenters/settings/table' end end end diff --git a/lib/compendium/queries.rb b/lib/compendium/queries.rb index c6bb231..a117145 100644 --- a/lib/compendium/queries.rb +++ b/lib/compendium/queries.rb @@ -1,9 +1,9 @@ module Compendium module Queries - autoload :Query, 'compendium/queries/query' - autoload :Collection, 'compendium/queries/collection' - autoload :Count, 'compendium/queries/count' - autoload :Sum, 'compendium/queries/sum' - autoload :Through, 'compendium/queries/through' + require 'compendium/queries/query' + require 'compendium/queries/collection' + require 'compendium/queries/count' + require 'compendium/queries/sum' + require 'compendium/queries/through' end end From 8fbc34c6b5bcabcae2909aeab88d54407f01f9ef Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Mon, 29 Jan 2018 11:12:36 -0500 Subject: [PATCH 07/23] Remove mount_compendium method, move engine routes to config/routes.rb --- config/routes.rb | 8 ++++++++ lib/compendium/engine.rb | 13 ++++++++++--- lib/compendium/engine/mount.rb | 21 --------------------- 3 files changed, 18 insertions(+), 24 deletions(-) create mode 100644 config/routes.rb delete mode 100644 lib/compendium/engine/mount.rb diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..2c09745 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,8 @@ +Compendium::Engine.routes.draw do + scope controller: 'compendium/reports', as: 'compendium_reports' do + get ':report_name', action: :setup, constraints: { format: :html }, as: 'setup' + match ':report_name/export(/:query)', action: :export, as: 'export', via: [:get, :post] + match ':report_name(/:query)', action: :run, as: 'run', via: [:get, :post] + root action: :index, as: 'root' + end +end diff --git a/lib/compendium/engine.rb b/lib/compendium/engine.rb index 42c85c9..a1fe998 100644 --- a/lib/compendium/engine.rb +++ b/lib/compendium/engine.rb @@ -1,8 +1,15 @@ -require 'compendium/engine/mount' +require 'rails/engine' module Compendium - if defined?(Rails) - class Engine < ::Rails::Engine + class Engine < Rails::Engine + config.generators do |g| + g.test_framework :rspec + end + end + + class ExportRouter + def matches?(request) + request.params[:export].present? end end end diff --git a/lib/compendium/engine/mount.rb b/lib/compendium/engine/mount.rb deleted file mode 100644 index fe5343e..0000000 --- a/lib/compendium/engine/mount.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActionDispatch - module Routing - class Mapper - class ExportRouter - def matches?(request) - request.params[:export].present? - end - end - - def mount_compendium(options = {}) - scope options[:at], controller: options.fetch(:controller, 'compendium/reports'), as: 'compendium_reports' do - get ':report_name', action: :setup, constraints: { format: :html }, as: 'setup' - match ':report_name/export', action: :export, as: 'export', via: [:get, :post] - post ':report_name(/:query)', constraints: ExportRouter.new, action: :export, as: 'export_post' - match ':report_name(/:query)', action: :run, as: 'run', via: [:get, :post] - root action: :index, as: 'root' - end - end - end - end -end From 8e379871443d1ba9ef1745ffed0d89c1cc06158f Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Mon, 29 Jan 2018 12:04:00 -0500 Subject: [PATCH 08/23] Add missing require --- lib/compendium/param_types/dropdown.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/compendium/param_types/dropdown.rb b/lib/compendium/param_types/dropdown.rb index e57a075..a1414b4 100644 --- a/lib/compendium/param_types/dropdown.rb +++ b/lib/compendium/param_types/dropdown.rb @@ -1,3 +1,5 @@ +require 'compendium/param_types/with_choices' + module Compendium module ParamTypes class Dropdown < WithChoices From 38a1706c8bf365ea2aa0acf56164d64dd175c4dd Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Mon, 29 Jan 2018 12:04:55 -0500 Subject: [PATCH 09/23] Extract DSL into separate modules --- lib/compendium/dsl.rb | 147 ++----------- lib/compendium/dsl/exports.rb | 16 ++ lib/compendium/dsl/filter.rb | 15 ++ lib/compendium/dsl/metric.rb | 25 +++ lib/compendium/dsl/option.rb | 37 ++++ lib/compendium/dsl/query.rb | 60 ++++++ lib/compendium/dsl/table.rb | 13 ++ spec/compendium/dsl/exports_spec.rb | 30 +++ spec/compendium/dsl/filter_spec.rb | 40 ++++ spec/compendium/dsl/metric_spec.rb | 57 ++++++ spec/compendium/dsl/option_spec.rb | 49 +++++ spec/compendium/dsl/query_spec.rb | 150 ++++++++++++++ spec/compendium/dsl/table_spec.rb | 33 +++ spec/compendium/dsl_spec.rb | 307 ---------------------------- 14 files changed, 542 insertions(+), 437 deletions(-) create mode 100644 lib/compendium/dsl/exports.rb create mode 100644 lib/compendium/dsl/filter.rb create mode 100644 lib/compendium/dsl/metric.rb create mode 100644 lib/compendium/dsl/option.rb create mode 100644 lib/compendium/dsl/query.rb create mode 100644 lib/compendium/dsl/table.rb create mode 100644 spec/compendium/dsl/exports_spec.rb create mode 100644 spec/compendium/dsl/filter_spec.rb create mode 100644 spec/compendium/dsl/metric_spec.rb create mode 100644 spec/compendium/dsl/option_spec.rb create mode 100644 spec/compendium/dsl/query_spec.rb create mode 100644 spec/compendium/dsl/table_spec.rb delete mode 100644 spec/compendium/dsl_spec.rb diff --git a/lib/compendium/dsl.rb b/lib/compendium/dsl.rb index aad6916..f9133f9 100644 --- a/lib/compendium/dsl.rb +++ b/lib/compendium/dsl.rb @@ -1,98 +1,32 @@ require 'collection_of' require 'inheritable_attr' + +require 'compendium/dsl/exports' +require 'compendium/dsl/filter' +require 'compendium/dsl/metric' +require 'compendium/dsl/option' +require 'compendium/dsl/query' +require 'compendium/dsl/table' require 'compendium/option' require 'compendium/queries/query' + require 'active_support/core_ext/class/attribute' module Compendium - module DSL # rubocop:disable Metrics/ModuleLength + module DSL + include Exports + include Filter + include Metric + include Option + include Query + include Table + def self.extended(klass) klass.inheritable_attr :queries, default: ::Collection[Queries::Query] - klass.inheritable_attr :options, default: ::Collection[Option] + klass.inheritable_attr :options, default: ::Collection[Compendium::Option] klass.inheritable_attr :exporters, default: {} end - # Define a query - def query(name, opts = {}, &block) - define_query(name, opts, &block) - end - alias_method :chart, :query - alias_method :data, :query - - # Define a parameter for the report - def option(name, *args) - opts = args.extract_options! - type = args.shift - - add_params_validations(name, opts.delete(:validates)) - - if options[name] - options[name].type = type if type - options[name].default = opts.delete(:default) if opts.key?(:default) - options[name].merge!(opts) - else - options << Compendium::Option.new(opts.merge(name: name, type: type)) - end - end - - # Define a metric from a query or implicitly - # A metric is a derived statistic from a report, for instance a count of rows - def metric(name, *args, &block) - proc = args.first.is_a?(Proc) ? args.first : block - opts = args.extract_options! - - if opts.key?(:through) - [opts.delete(:through)].flatten.each do |query| - raise ArgumentError, "query #{query} is not defined" unless queries.key?(query) - queries[query].add_metric(name, proc, opts) - end - else - # Allow metrics to define queries implicitly - # ie. if you need a metric that counts a column, there's no need to explicitly create a query - # and just pass it into a metric - query = define_query("__metric_#{name}", {}, &block) - query.add_metric(name, -> (result) { result.first }, opts) - end - end - - # Define a filter to modify the results from specified query (in this case :deliveries) - # For example, this can be useful to translate columns prior to rendering, as it will apply - # for all render types (table, chart, JSON) - # Multiple queries can be set up with the same filter - def filter(*query_names, &block) - each_query(query_names) do |query| - query.add_filter(block) - end - end - - # Allow default table settings to be defined for a query. - # These settings are used when rendering a query to an HTML table or to CSV - def table(*query_names, &block) - each_query(query_names) do |query| - query.table_settings = block - end - end - - # Define any exports the report has - def exports(type, *opts) - exporters[type] = if opts.empty? - true - elsif opts.length == 1 - opts.first - else - opts - end - end - - # Each Report will have its own descendant of Params in order to safely add validations - def params_class - @params_class ||= Class.new(Params) - end - - def params_class=(klass) - @params_class = klass - end - # Allow defined queries to be redefined by name, eg: # query :main_query # main_query { collect_records_here } @@ -120,52 +54,5 @@ def each_query(query_names) yield queries[query_name] end end - - def define_query(name, opts, &block) - params = [name.to_sym, opts, block] - - if opts.key?(:through) - # Ensure each through query is defined - through = [opts[:through]].flatten - through.each { |q| raise ArgumentError, "query #{q} is not defined" unless queries.include?(q.to_sym) } - - params.insert(1, through) - elsif opts.fetch(:sum, false) - params.insert(1, opts[:sum]) - end - - query = query_type(opts).new(*params) - query.report = self - - metrics[name] = opts[:metric] if opts.key?(:metric) - - if queries[name] - raise Queries::CannotRedefineType unless queries[name].instance_of?(query.class) - queries.delete(name) - end - - queries << query - - query - end - - def query_type(opts) - if opts.key?(:collection) - Queries::Collection - elsif opts.key?(:through) - Queries::Through - elsif opts.fetch(:count, false) - Queries::Count - elsif opts.fetch(:sum, false) - Queries::Sum - else - Queries::Query - end - end - - def add_params_validations(name, validations) - return if validations.blank? - params_class.validates name, validations - end end end diff --git a/lib/compendium/dsl/exports.rb b/lib/compendium/dsl/exports.rb new file mode 100644 index 0000000..3911d7d --- /dev/null +++ b/lib/compendium/dsl/exports.rb @@ -0,0 +1,16 @@ +module Compendium + module DSL + module Exports + # Define any exports the report has + def exports(type, *opts) + exporters[type] = if opts.empty? + true + elsif opts.length == 1 + opts.first + else + opts + end + end + end + end +end diff --git a/lib/compendium/dsl/filter.rb b/lib/compendium/dsl/filter.rb new file mode 100644 index 0000000..7e6e969 --- /dev/null +++ b/lib/compendium/dsl/filter.rb @@ -0,0 +1,15 @@ +module Compendium + module DSL + module Filter + # Define a filter to modify the results from specified query (in this case :deliveries) + # For example, this can be useful to translate columns prior to rendering, as it will apply + # for all render types (table, chart, JSON) + # Multiple queries can be set up with the same filter + def filter(*query_names, &block) + each_query(query_names) do |query| + query.add_filter(block) + end + end + end + end +end diff --git a/lib/compendium/dsl/metric.rb b/lib/compendium/dsl/metric.rb new file mode 100644 index 0000000..ac32008 --- /dev/null +++ b/lib/compendium/dsl/metric.rb @@ -0,0 +1,25 @@ +module Compendium + module DSL + module Metric + # Define a metric from a query or implicitly + # A metric is a derived statistic from a report, for instance a count of rows + def metric(name, *args, &block) + proc = args.first.is_a?(Proc) ? args.first : block + opts = args.extract_options! + + if opts.key?(:through) + [opts.delete(:through)].flatten.each do |query| + raise ArgumentError, "query #{query} is not defined" unless queries.key?(query) + queries[query].add_metric(name, proc, opts) + end + else + # Allow metrics to define queries implicitly + # ie. if you need a metric that counts a column, there's no need to explicitly create a query + # and just pass it into a metric + query = define_query("__metric_#{name}", {}, &block) + query.add_metric(name, -> (result) { result.first }, opts) + end + end + end + end +end diff --git a/lib/compendium/dsl/option.rb b/lib/compendium/dsl/option.rb new file mode 100644 index 0000000..ff86a39 --- /dev/null +++ b/lib/compendium/dsl/option.rb @@ -0,0 +1,37 @@ +module Compendium + module DSL + module Option + # Define a parameter for the report + def option(name, *args) + opts = args.extract_options! + type = args.shift + + add_params_validations(name, opts.delete(:validates)) + + if options[name] + options[name].type = type if type + options[name].default = opts.delete(:default) if opts.key?(:default) + options[name].merge!(opts) + else + options << Compendium::Option.new(opts.merge(name: name, type: type)) + end + end + + # Each Report will have its own descendant of Params in order to safely add validations + def params_class + @params_class ||= Class.new(Params) + end + + def params_class=(klass) + @params_class = klass + end + + private + + def add_params_validations(name, validations) + return if validations.blank? + params_class.validates name, validations + end + end + end +end diff --git a/lib/compendium/dsl/query.rb b/lib/compendium/dsl/query.rb new file mode 100644 index 0000000..d2e18c3 --- /dev/null +++ b/lib/compendium/dsl/query.rb @@ -0,0 +1,60 @@ +module Compendium + module DSL + module Query + # Define a query + def query(name, opts = {}, &block) + define_query(name, opts, &block) + end + alias_method :chart, :query + alias_method :data, :query + end + + private + + def define_query(name, opts, &block) + params = [name.to_sym, opts, block] + + if opts.key?(:through) + # Ensure each through query is defined + through = [opts[:through]].flatten + through.each { |q| raise ArgumentError, "query #{q} is not defined" unless queries.include?(q.to_sym) } + + params.insert(1, through) + elsif opts.fetch(:sum, false) + params.insert(1, opts[:sum]) + end + + query = query_type(opts).new(*params) + query.report = self + + metrics[name] = opts[:metric] if opts.key?(:metric) + + if queries[name] + raise Queries::CannotRedefineType unless queries[name].instance_of?(query.class) + queries.delete(name) + end + + queries << query + + query + end + + def query_type(opts) + klass = if opts.key?(:collection) + Queries::Collection + elsif opts.key?(:through) + Queries::Through + elsif opts.fetch(:count, false) + Queries::Count + elsif opts.fetch(:sum, false) + Queries::Sum + else + opts.delete(:sum) + Queries::Query + end + + opts.delete(:count) + klass + end + end +end diff --git a/lib/compendium/dsl/table.rb b/lib/compendium/dsl/table.rb new file mode 100644 index 0000000..c992fe4 --- /dev/null +++ b/lib/compendium/dsl/table.rb @@ -0,0 +1,13 @@ +module Compendium + module DSL + module Table + # Allow default table settings to be defined for a query. + # These settings are used when rendering a query to an HTML table or to CSV + def table(*query_names, &block) + each_query(query_names) do |query| + query.table_settings = block + end + end + end + end +end diff --git a/spec/compendium/dsl/exports_spec.rb b/spec/compendium/dsl/exports_spec.rb new file mode 100644 index 0000000..fc548ac --- /dev/null +++ b/spec/compendium/dsl/exports_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' +require 'compendium' +require 'compendium/dsl' + +describe Compendium::DSL::Exports do + subject do + Class.new do + extend Compendium::DSL::Exports + inheritable_attr :exporters, default: {} + end + end + + describe '#exports' do + it 'should not have any exporters by default' do + expect(subject.exporters).to be_empty + end + + it 'should set the export to true if no options are given' do + subject.exports :csv + expect(subject.exporters[:csv]).to eq(true) + end + + it 'should save any given options' do + subject.exports :csv, :main_query + subject.exports :pdf, :foo, :bar + expect(subject.exporters[:csv]).to eq(:main_query) + expect(subject.exporters[:pdf]).to eq([:foo, :bar]) + end + end +end diff --git a/spec/compendium/dsl/filter_spec.rb b/spec/compendium/dsl/filter_spec.rb new file mode 100644 index 0000000..4d31a79 --- /dev/null +++ b/spec/compendium/dsl/filter_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +require 'compendium' +require 'compendium/dsl' + +describe Compendium::DSL::Filter do + subject do + Class.new do + extend Compendium::DSL + end + end + + describe '#filter' do + let(:filter_proc) { -> { :filter } } + + it 'should add a filter to the given query' do + subject.query :test + subject.filter :test, &filter_proc + expect(subject.queries[:test].filters).to include filter_proc + end + + it 'should raise an error if there is no query of the given name' do + expect { subject.filter :test, &filter_proc }.to raise_error(ArgumentError, 'query test is not defined') + end + + it 'should allow multiple filters to be defined for the same query' do + subject.query :test + subject.filter :test, &filter_proc + subject.filter :test, &-> { :another_filter } + expect(subject.queries[:test].filters.count).to eq(2) + end + + it 'should allow a filter to be applied to multiple queries at once' do + subject.query :query1 + subject.query :query2 + subject.filter :query1, :query2, &filter_proc + expect(subject.queries[:query1].filters).to include filter_proc + expect(subject.queries[:query2].filters).to include filter_proc + end + end +end diff --git a/spec/compendium/dsl/metric_spec.rb b/spec/compendium/dsl/metric_spec.rb new file mode 100644 index 0000000..678aa70 --- /dev/null +++ b/spec/compendium/dsl/metric_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' +require 'compendium' +require 'compendium/dsl' + +describe Compendium::DSL::Metric do + subject do + Class.new do + extend Compendium::DSL + end + end + + describe '#metric' do + let(:metric_proc) { -> { :metric } } + + before do + subject.query :test + subject.metric :test_metric, metric_proc, through: :test + end + + it 'should add a metric to the given query' do + expect(subject.queries[:test].metrics.first.name).to eq(:test_metric) + end + + it 'should set the metric command' do + expect(subject.queries[:test].metrics.first.command).to eq(metric_proc) + end + + context 'when through is specified' do + it 'should raise an error if specified for an invalid query' do + expect { subject.metric :test_metric, metric_proc, through: :fake }.to raise_error ArgumentError, 'query fake is not defined' + end + + it 'should allow metrics to be defined with a block' do + subject.metric :block_metric, through: :test do + 123 + end + + expect(subject.queries[:test].metrics[:block_metric].run(self, nil)).to eq(123) + end + + it 'should allow metrics to be defined with a lambda' do + subject.metric :block_metric, -> (*) { 123 }, through: :test + expect(subject.queries[:test].metrics[:block_metric].run(self, nil)).to eq(123) + end + end + + context 'when through is not specified' do + before { subject.metric(:no_through_metric) { |data| data } } + + specify { expect(subject.queries).to include :__metric_no_through_metric } + + it 'should return the result of the query as the result of the metric' do + expect(subject.queries[:__metric_no_through_metric].metrics[:no_through_metric].run(self, [123])).to eq(123) + end + end + end +end diff --git a/spec/compendium/dsl/option_spec.rb b/spec/compendium/dsl/option_spec.rb new file mode 100644 index 0000000..b279839 --- /dev/null +++ b/spec/compendium/dsl/option_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' +require 'compendium' +require 'compendium/dsl' +require 'compendium/option' + +describe Compendium::DSL::Option do + subject do + Class.new do + extend Compendium::DSL::Option + inheritable_attr :options, default: ::Collection[Compendium::Option] + end + end + + describe '#option' do + before { subject.option :starting_on, :date } + + specify { expect(subject.options).to include :starting_on } + specify { expect(subject.options[:starting_on]).to be_date } + + it 'should allow previously defined options to be redefined' do + subject.option :starting_on, :boolean + expect(subject.options[:starting_on]).to be_boolean + expect(subject.options[:starting_on]).not_to be_date + end + + it 'should allow overriding default value' do + proc = -> { Date.new(2013, 6, 1) } + subject.option :starting_on, :date, default: proc + expect(subject.options[:starting_on].default).to eq(proc) + end + + it 'should add validations' do + subject.option :foo, validates: { presence: true } + expect(subject.params_class.validators_on(:foo)).not_to be_empty + end + + it 'should not add validations if no validates option is given' do + expect(subject.params_class).not_to receive :validates + subject.option :foo + end + + it 'should not bleed overridden options into the superclass' do + r = Class.new(subject) + r.option :starting_on, :boolean + r.option :new, :date + expect(subject.options[:starting_on]).to be_date + end + end +end diff --git a/spec/compendium/dsl/query_spec.rb b/spec/compendium/dsl/query_spec.rb new file mode 100644 index 0000000..ce87974 --- /dev/null +++ b/spec/compendium/dsl/query_spec.rb @@ -0,0 +1,150 @@ +require 'spec_helper' +require 'compendium/dsl' + +describe Compendium::DSL::Query do + let(:proc1) { -> { :proc1 } } + let(:proc2) { -> { :proc2 } } + let(:report_class) do + proc1 = proc1 + + Class.new(Compendium::Report) do + query :test, &proc1 + end + end + + subject { report_class } + + describe '#query' do + specify { expect(subject.queries).to include :test } + + it 'should relate the new query back to the report instance' do + r = subject.new + expect(r.test.report).to eq(r) + end + + it 'should relate a query to the report class' do + expect(subject.test.report).to eq(subject) + end + + context 'when the query was previously defined' do + before { subject.query :test_query } + + it 'should allow previously defined queries to be redefined by name' do + subject.test_query foo: :bar + expect(subject.queries[:test_query].options).to eq(foo: :bar) + end + + it 'should allow previously defined queries to be accessed by name' do + expect(subject.test_query).to eq(subject.queries[:test_query]) + end + end + + context 'when overriding an existing query' do + before do + subject.query :test, &proc2 + subject.query :another_test, count: true + end + + it 'should delete the existing query' do + expect(subject.queries.count).to eq(2) + end + + it 'should only have one query with each name' do + expect(subject.queries.map(&:name)).to match_array([:test, :another_test]) + end + + it 'should use the new proc' do + expect(subject.test.proc).to eq(proc2) + end + + it 'should not allow replacing a query with a different type' do + expect { subject.query :test, count: true }.to raise_error(Compendium::Queries::CannotRedefineType) + expect(subject.test).to be_instance_of Compendium::Queries::Query + end + + it 'should allow replacing a query with the same type' do + subject.query :another_test, count: true, &proc2 + expect(subject.another_test.proc).to eq(proc2) + expect(subject.another_test).to be_instance_of Compendium::Queries::Count + end + end + + context 'when given a through option' do + before { report_class.query :through, through: :test } + subject { report_class.queries[:through] } + + specify { is_expected.to be_a Compendium::Queries::Through } + specify { expect(subject.through).to eq([:test]) } + end + + context 'when given a collection option' do + subject { report_class.queries[:collection] } + + context 'that is an enumerable' do + before { report_class.query :collection, collection: [] } + + it { is_expected.to be_a Compendium::Queries::Collection } + end + + context 'that is a symbol' do + let(:query) { double('Query') } + + before do + allow_any_instance_of(Compendium::Queries::Query).to receive(:get_associated_query).with(:query).and_return(query) + report_class.query :collection, collection: :query + end + + specify { expect(subject.collection).to eq(:query) } + end + + context 'that is a query' do + let(:query) { Compendium::Queries::Query.new(:query, {}, -> {}) } + before { report_class.query :collection, collection: query } + + specify { expect(subject.collection).to eq(query) } + end + end + + context 'when given a count option' do + subject { report_class.queries[:counted] } + + context 'set to true' do + before { report_class.query :counted, count: true } + it { is_expected.to be_a Compendium::Queries::Count } + end + + context 'set to false' do + before { report_class.query :counted, count: false } + it { is_expected.to be_a Compendium::Queries::Query } + it { is_expected.not_to be_a Compendium::Queries::Count } + end + end + + context 'when given a sum option' do + subject { report_class.queries[:summed] } + + context 'set to a truthy value' do + before { report_class.query :summed, sum: :assoc_count } + + it { is_expected.to be_a Compendium::Queries::Sum } + specify { expect(subject.column).to eq(:assoc_count) } + end + + context 'set to false' do + before { report_class.query :summed, sum: false } + it { is_expected.to be_a Compendium::Queries::Query } + it { is_expected.not_to be_a Compendium::Queries::Sum } + end + end + end + + describe '#chart' do + before { subject.chart(:chart) } + specify { expect(subject.queries).to include :chart } + end + + describe '#data' do + before { subject.data(:data) } + specify { expect(subject.queries).to include :data } + end +end diff --git a/spec/compendium/dsl/table_spec.rb b/spec/compendium/dsl/table_spec.rb new file mode 100644 index 0000000..3661e80 --- /dev/null +++ b/spec/compendium/dsl/table_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'compendium' +require 'compendium/dsl' + +describe Compendium::DSL::Table do + subject do + Class.new do + extend Compendium::DSL + end + end + + describe '#table' do + let(:table_proc) { -> { display_nil_as 'na' } } + + it 'should add table settings to the given query' do + subject.query :test + subject.table :test, &table_proc + expect(subject.queries[:test].table_settings).to eq(table_proc) + end + + it 'should raise an error if there is no query of the given name' do + expect { subject.table :test, &table_proc }.to raise_error(ArgumentError, 'query test is not defined') + end + + it 'should allow table settings to be applied to multiple queries at once' do + subject.query :query1 + subject.query :query2 + subject.table :query1, :query2, &table_proc + expect(subject.queries[:query1].table_settings).to eq(table_proc) + expect(subject.queries[:query2].table_settings).to eq(table_proc) + end + end +end diff --git a/spec/compendium/dsl_spec.rb b/spec/compendium/dsl_spec.rb deleted file mode 100644 index 26beccd..0000000 --- a/spec/compendium/dsl_spec.rb +++ /dev/null @@ -1,307 +0,0 @@ -require 'spec_helper' -require 'compendium' -require 'compendium/dsl' - -describe Compendium::DSL do - subject do - Class.new do - extend Compendium::DSL - end - end - - describe '#option' do - before { subject.option :starting_on, :date } - - specify { expect(subject.options).to include :starting_on } - specify { expect(subject.options[:starting_on]).to be_date } - - it 'should allow previously defined options to be redefined' do - subject.option :starting_on, :boolean - expect(subject.options[:starting_on]).to be_boolean - expect(subject.options[:starting_on]).not_to be_date - end - - it 'should allow overriding default value' do - proc = -> { Date.new(2013, 6, 1) } - subject.option :starting_on, :date, default: proc - expect(subject.options[:starting_on].default).to eq(proc) - end - - it 'should add validations' do - subject.option :foo, validates: { presence: true } - expect(subject.params_class.validators_on(:foo)).not_to be_empty - end - - it 'should not add validations if no validates option is given' do - expect(subject.params_class).not_to receive :validates - subject.option :foo - end - - it 'should not bleed overridden options into the superclass' do - r = Class.new(subject) - r.option :starting_on, :boolean - r.option :new, :date - expect(subject.options[:starting_on]).to be_date - end - end - - describe '#query' do - let(:proc1) { -> { :proc1 } } - let(:proc2) { -> { :proc2 } } - - let(:report_class) do - proc1 = proc1 - - Class.new(Compendium::Report) do - query :test, &proc1 - end - end - - subject { report_class } - - specify { expect(subject.queries).to include :test } - - it 'should relate the new query back to the report instance' do - r = subject.new - expect(r.test.report).to eq(r) - end - - it 'should relate a query to the report class' do - expect(subject.test.report).to eq(subject) - end - - context 'when overriding an existing query' do - before do - subject.query :test, &proc2 - subject.query :another_test, count: true - end - - it 'should delete the existing query' do - expect(subject.queries.count).to eq(2) - end - - it 'should only have one query with each name' do - expect(subject.queries.map(&:name)).to match_array([:test, :another_test]) - end - - it 'should use the new proc' do - expect(subject.test.proc).to eq(proc2) - end - - it 'should not allow replacing a query with a different type' do - expect { subject.query :test, count: true }.to raise_error(Compendium::Queries::CannotRedefineType) - expect(subject.test).to be_instance_of Compendium::Queries::Query - end - - it 'should allow replacing a query with the same type' do - subject.query :another_test, count: true, &proc2 - expect(subject.another_test.proc).to eq(proc2) - expect(subject.another_test).to be_instance_of Compendium::Queries::Count - end - end - - context 'when given a through option' do - before { report_class.query :through, through: :test } - subject { report_class.queries[:through] } - - specify { is_expected.to be_a Compendium::Queries::Through } - specify { expect(subject.through).to eq([:test]) } - end - - context 'when given a collection option' do - subject { report_class.queries[:collection] } - - context 'that is an enumerable' do - before { report_class.query :collection, collection: [] } - - it { is_expected.to be_a Compendium::Queries::Collection } - end - - context 'that is a symbol' do - let(:query) { double('Query') } - - before do - allow_any_instance_of(Compendium::Queries::Query).to receive(:get_associated_query).with(:query).and_return(query) - report_class.query :collection, collection: :query - end - - specify { expect(subject.collection).to eq(:query) } - end - - context 'that is a query' do - let(:query) { Compendium::Queries::Query.new(:query, {}, -> {}) } - before { report_class.query :collection, collection: query } - - specify { expect(subject.collection).to eq(query) } - end - end - - context 'when given a count option' do - subject { report_class.queries[:counted] } - - context 'set to true' do - before { report_class.query :counted, count: true } - it { is_expected.to be_a Compendium::Queries::Count } - end - - context 'set to false' do - before { report_class.query :counted, count: false } - it { is_expected.to be_a Compendium::Queries::Query } - it { is_expected.not_to be_a Compendium::Queries::Count } - end - end - - context 'when given a sum option' do - subject { report_class.queries[:summed] } - - context 'set to a truthy value' do - before { report_class.query :summed, sum: :assoc_count } - - it { is_expected.to be_a Compendium::Queries::Sum } - specify { expect(subject.column).to eq(:assoc_count) } - end - - context 'set to false' do - before { report_class.query :summed, sum: false } - it { is_expected.to be_a Compendium::Queries::Query } - it { is_expected.not_to be_a Compendium::Queries::Sum } - end - end - end - - describe '#chart' do - before { subject.chart(:chart) } - specify { expect(subject.queries).to include :chart } - end - - describe '#data' do - before { subject.data(:data) } - specify { expect(subject.queries).to include :data } - end - - describe '#metric' do - let(:metric_proc) { -> { :metric } } - - before do - subject.query :test - subject.metric :test_metric, metric_proc, through: :test - end - - it 'should add a metric to the given query' do - expect(subject.queries[:test].metrics.first.name).to eq(:test_metric) - end - - it 'should set the metric command' do - expect(subject.queries[:test].metrics.first.command).to eq(metric_proc) - end - - context 'when through is specified' do - it 'should raise an error if specified for an invalid query' do - expect { subject.metric :test_metric, metric_proc, through: :fake }.to raise_error ArgumentError, 'query fake is not defined' - end - - it 'should allow metrics to be defined with a block' do - subject.metric :block_metric, through: :test do - 123 - end - - expect(subject.queries[:test].metrics[:block_metric].run(self, nil)).to eq(123) - end - - it 'should allow metrics to be defined with a lambda' do - subject.metric :block_metric, -> (*) { 123 }, through: :test - expect(subject.queries[:test].metrics[:block_metric].run(self, nil)).to eq(123) - end - end - - context 'when through is not specified' do - before { subject.metric(:no_through_metric) { |data| data } } - - specify { expect(subject.queries).to include :__metric_no_through_metric } - - it 'should return the result of the query as the result of the metric' do - expect(subject.queries[:__metric_no_through_metric].metrics[:no_through_metric].run(self, [123])).to eq(123) - end - end - end - - describe '#filter' do - let(:filter_proc) { -> { :filter } } - - it 'should add a filter to the given query' do - subject.query :test - subject.filter :test, &filter_proc - expect(subject.queries[:test].filters).to include filter_proc - end - - it 'should raise an error if there is no query of the given name' do - expect { subject.filter :test, &filter_proc }.to raise_error(ArgumentError, 'query test is not defined') - end - - it 'should allow multiple filters to be defined for the same query' do - subject.query :test - subject.filter :test, &filter_proc - subject.filter :test, &-> { :another_filter } - expect(subject.queries[:test].filters.count).to eq(2) - end - - it 'should allow a filter to be applied to multiple queries at once' do - subject.query :query1 - subject.query :query2 - subject.filter :query1, :query2, &filter_proc - expect(subject.queries[:query1].filters).to include filter_proc - expect(subject.queries[:query2].filters).to include filter_proc - end - end - - describe '#table' do - let(:table_proc) { -> { display_nil_as 'na' } } - - it 'should add table settings to the given query' do - subject.query :test - subject.table :test, &table_proc - expect(subject.queries[:test].table_settings).to eq(table_proc) - end - - it 'should raise an error if there is no query of the given name' do - expect { subject.table :test, &table_proc }.to raise_error(ArgumentError, 'query test is not defined') - end - - it 'should allow table settings to be applied to multiple queries at once' do - subject.query :query1 - subject.query :query2 - subject.table :query1, :query2, &table_proc - expect(subject.queries[:query1].table_settings).to eq(table_proc) - expect(subject.queries[:query2].table_settings).to eq(table_proc) - end - end - - describe '#exports' do - it 'should not have any exporters by default' do - expect(subject.exporters).to be_empty - end - - it 'should set the export to true if no options are given' do - subject.exports :csv - expect(subject.exporters[:csv]).to eq(true) - end - - it 'should save any given options' do - subject.exports :csv, :main_query - subject.exports :pdf, :foo, :bar - expect(subject.exporters[:csv]).to eq(:main_query) - expect(subject.exporters[:pdf]).to eq([:foo, :bar]) - end - end - - it 'should allow previously defined queries to be redefined by name' do - subject.query :test_query - subject.test_query foo: :bar - expect(subject.queries[:test_query].options).to eq(foo: :bar) - end - - it 'should allow previously defined queries to be accessed by name' do - subject.query :test_query - expect(subject.test_query).to eq(subject.queries[:test_query]) - end -end From 8ca7146d8386408c23f02cab15faf34f703130bc Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Mon, 29 Jan 2018 12:11:10 -0500 Subject: [PATCH 10/23] Ensure only valid options are given for a query Replace collect option with execute_sql Rename execute_command to execute_sql_command --- README.md | 4 +-- lib/compendium/dsl/query.rb | 43 ++++++++++++++++--------- lib/compendium/queries/count.rb | 2 +- lib/compendium/queries/query.rb | 13 +++++--- lib/compendium/queries/sum.rb | 6 +++- lib/compendium/queries/through.rb | 4 +++ spec/compendium/queries/count_spec.rb | 2 +- spec/compendium/queries/query_spec.rb | 2 +- spec/compendium/queries/through_spec.rb | 2 +- 9 files changed, 51 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 648bc2e..7d14477 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ class MyReport < Compendium::Report end end - # Define a query which collects data by using AR directly - query :on_hand_inventory, collect: :active_record do |params| + # Define a query which doesn't execute SQL (for instance if you want to return AR models) + query :on_hand_inventory, execute_sql: false do |params| Items.where(in_stock: true) end diff --git a/lib/compendium/dsl/query.rb b/lib/compendium/dsl/query.rb index d2e18c3..cdab2fb 100644 --- a/lib/compendium/dsl/query.rb +++ b/lib/compendium/dsl/query.rb @@ -12,19 +12,11 @@ def query(name, opts = {}, &block) private def define_query(name, opts, &block) - params = [name.to_sym, opts, block] + klass = query_class(opts) + clean_opts(opts, klass) + params = build_params(name, opts, &block) - if opts.key?(:through) - # Ensure each through query is defined - through = [opts[:through]].flatten - through.each { |q| raise ArgumentError, "query #{q} is not defined" unless queries.include?(q.to_sym) } - - params.insert(1, through) - elsif opts.fetch(:sum, false) - params.insert(1, opts[:sum]) - end - - query = query_type(opts).new(*params) + query = klass.new(*params) query.report = self metrics[name] = opts[:metric] if opts.key?(:metric) @@ -39,8 +31,8 @@ def define_query(name, opts, &block) query end - def query_type(opts) - klass = if opts.key?(:collection) + def query_class(opts) + if opts.key?(:collection) Queries::Collection elsif opts.key?(:through) Queries::Through @@ -49,12 +41,31 @@ def query_type(opts) elsif opts.fetch(:sum, false) Queries::Sum else - opts.delete(:sum) Queries::Query end + end + + def build_params(name, opts, &block) + params = [name.to_sym, opts, block] + + if opts.key?(:through) + # Ensure each through query is defined + through = [opts[:through]].flatten + through.each { |q| raise ArgumentError, "query #{q} is not defined" unless queries.include?(q.to_sym) } + + params.insert(1, through) + elsif opts.fetch(:sum, false) + params.insert(1, opts[:sum]) + end + + params + end + def clean_opts(opts, query_class) opts.delete(:count) - klass + opts.delete(:collection) unless query_class == Queries::Collection + opts.delete(:through) unless query_class == Queries::Through + opts.delete(:sum) unless query_class == Queries::Sum end end end diff --git a/lib/compendium/queries/count.rb b/lib/compendium/queries/count.rb index 85a05c9..ad05073 100644 --- a/lib/compendium/queries/count.rb +++ b/lib/compendium/queries/count.rb @@ -14,7 +14,7 @@ def initialize(*args) private - def execute_command(command) + def execute_sql_command(command) return [] if command.nil? raise InvalidCommand unless command.respond_to?(:count) command.count diff --git a/lib/compendium/queries/query.rb b/lib/compendium/queries/query.rb index 25c4158..3e1661e 100644 --- a/lib/compendium/queries/query.rb +++ b/lib/compendium/queries/query.rb @@ -3,13 +3,12 @@ require 'compendium/metric' require 'compendium/presenters/chart' require 'compendium/presenters/table' +require 'compendium/queries/query/render' require 'collection_of' module Compendium module Queries class Query - autoload :Render, 'compendium/queries/query/render' - include Render attr_reader :name, :results, :metrics, :filters @@ -23,6 +22,8 @@ def initialize(*args) @name, @options, @proc = args @metrics = ::Collection[Compendium::Metric] @filters = ::Collection[Proc] + + options.assert_valid_keys(*valid_keys) end def initialize_clone(*) @@ -75,6 +76,10 @@ def empty? private + def valid_keys + %i(collection order reverse execute_sql totals) + end + def collect_results(context, *params) command = context.instance_exec(*params, &proc) if proc command = order_command(command) if options[:order] @@ -89,7 +94,7 @@ def collect_metrics(context) end def fetch_results(command) - options.fetch(:collect, nil) == :active_record ? command : execute_command(command) + options.fetch(:execute_sql, true) ? execute_sql_command(command) : command end def filter_results(results, params) @@ -120,7 +125,7 @@ def order_command(command) command end - def execute_command(command) + def execute_sql_command(command) return [] if command.nil? command = command.to_sql if command.respond_to?(:to_sql) execute_query(command) diff --git a/lib/compendium/queries/sum.rb b/lib/compendium/queries/sum.rb index 622cdec..3b24478 100644 --- a/lib/compendium/queries/sum.rb +++ b/lib/compendium/queries/sum.rb @@ -19,7 +19,11 @@ def initialize(*args) private - def execute_command(command) + def valid_keys + super.concat([:sum]) + end + + def execute_sql_command(command) return [] if command.nil? raise InvalidCommand unless command.respond_to?(:sum) command.sum(column) diff --git a/lib/compendium/queries/through.rb b/lib/compendium/queries/through.rb index b0f53be..df83b88 100644 --- a/lib/compendium/queries/through.rb +++ b/lib/compendium/queries/through.rb @@ -14,6 +14,10 @@ def initialize(*args) private + def valid_keys + super.concat([:through]) + end + def collect_results(context, params) results = collect_through_query_results(params, context) diff --git a/spec/compendium/queries/count_spec.rb b/spec/compendium/queries/count_spec.rb index f8e76f9..f2a6379 100644 --- a/spec/compendium/queries/count_spec.rb +++ b/spec/compendium/queries/count_spec.rb @@ -33,7 +33,7 @@ def count end describe Compendium::Queries::Count do - subject { described_class.new(:counted_query, { count: true }, -> (*) { @counter }) } + subject { described_class.new(:counted_query, {}, -> (*) { @counter }) } it 'should have a default order' do expect(subject.options[:order]).to eq('COUNT(*)') diff --git a/spec/compendium/queries/query_spec.rb b/spec/compendium/queries/query_spec.rb index 818d82b..024f0fc 100644 --- a/spec/compendium/queries/query_spec.rb +++ b/spec/compendium/queries/query_spec.rb @@ -3,7 +3,7 @@ describe Compendium::Queries::Query do describe '#initialize' do - let(:options) { double('Options') } + let(:options) { double('Options', assert_valid_keys: true) } let(:proc) { double('Proc') } context 'when supplying a report' do diff --git a/spec/compendium/queries/through_spec.rb b/spec/compendium/queries/through_spec.rb index a986267..b62f767 100644 --- a/spec/compendium/queries/through_spec.rb +++ b/spec/compendium/queries/through_spec.rb @@ -2,7 +2,7 @@ describe Compendium::Queries::Through do describe '#initialize' do - let(:options) { double('Options') } + let(:options) { double('Options', assert_valid_keys: true) } let(:proc) { double('Proc') } let(:through) { double('Query') } From 0db2f87a2fe437f09305c792879cf8389257cb1c Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Tue, 14 Aug 2018 13:17:31 -0400 Subject: [PATCH 11/23] Update rspec and rubocop --- .rspec | 1 + .rubocop.yml | 8 ++++---- compendium.gemspec | 11 ++++++----- lib/compendium/dsl.rb | 4 ++-- lib/compendium/presenters/option.rb | 2 -- lib/compendium/presenters/table.rb | 26 +++++++++++++------------- lib/compendium/report.rb | 16 ++++++++-------- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.rspec b/.rspec index 4e1e0d2..0912718 100644 --- a/.rspec +++ b/.rspec @@ -1 +1,2 @@ --color +--order random diff --git a/.rubocop.yml b/.rubocop.yml index baa0ec5..7bd11d1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -10,6 +10,9 @@ Layout/CaseIndentation: Layout/ElseAlignment: Enabled: false +Layout/EndAlignment: + EnforcedStyleAlignWith: variable + Layout/SpaceBeforeBlockBraces: EnforcedStyle: space EnforcedStyleForEmptyBraces: space @@ -25,9 +28,6 @@ Layout/SpaceInsideBlockBraces: EnforcedStyleForEmptyBraces: no_space SpaceBeforeBlockParameters: true -Lint/EndAlignment: - EnforcedStyleAlignWith: variable - Lint/UnusedMethodArgument: Exclude: - 'lib/compendium/abstract_chart_provider.rb' @@ -79,7 +79,7 @@ Style/IfUnlessModifier: Exclude: - 'Gemfile' -Style/MethodMissing: +Style/MethodMissingSuper: Exclude: - 'lib/compendium/presenters/settings/query.rb' diff --git a/compendium.gemspec b/compendium.gemspec index f9e1fef..deb2086 100644 --- a/compendium.gemspec +++ b/compendium.gemspec @@ -1,4 +1,4 @@ -lib = File.expand_path('../lib', __FILE__) +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'compendium/version' @@ -6,13 +6,13 @@ Gem::Specification.new do |gem| gem.name = 'compendium' gem.version = Compendium::VERSION gem.authors = ['Daniel Vandersluis'] - gem.email = ['dvandersluis@selfmgmt.com'] + gem.email = ['daniel.vandersluis@gmail.com'] gem.description = 'Ruby on Rails reporting framework' gem.summary = 'Ruby on Rails reporting framework' gem.homepage = 'https://github.com/dvandersluis/compendium' gem.license = 'MIT' - gem.files = `git ls-files`.split($/) # rubocop:disable Style/SpecialGlobalVars + gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ['lib'] @@ -22,7 +22,8 @@ Gem::Specification.new do |gem| gem.add_dependency 'inheritable_attr', '>= 1.0.0' gem.add_dependency 'rails', '>= 3.0.0', '< 4' gem.add_dependency 'sass-rails', '>= 3.0.0' + gem.add_development_dependency 'rake', '> 11.0.1', '< 12' - gem.add_development_dependency 'rspec', '~> 3.7.0' - gem.add_development_dependency 'rubocop', '0.52.1' + gem.add_development_dependency 'rspec', '~> 3.8.0' + gem.add_development_dependency 'rubocop', '~> 0.58' end diff --git a/lib/compendium/dsl.rb b/lib/compendium/dsl.rb index f9133f9..f320b22 100644 --- a/lib/compendium/dsl.rb +++ b/lib/compendium/dsl.rb @@ -31,7 +31,7 @@ def self.extended(klass) # query :main_query # main_query { collect_records_here } def method_missing(name, *args, &block) - if queries.keys.include?(name.to_sym) + if queries.key?(name.to_sym) query = queries[name.to_sym] query.proc = block if block_given? query.options = args.extract_options! @@ -42,7 +42,7 @@ def method_missing(name, *args, &block) end def respond_to_missing?(name, *args) - return true if queries.keys.include?(name) + return true if queries.key?(name) super end diff --git a/lib/compendium/presenters/option.rb b/lib/compendium/presenters/option.rb index 87ccfe2..55bd7a9 100644 --- a/lib/compendium/presenters/option.rb +++ b/lib/compendium/presenters/option.rb @@ -31,7 +31,6 @@ def input(ctx, form) raise ArgumentError, MISSING_CHOICES_ERROR if missing_choices? - # rubocop:disable Layout/EmptyLinesAroundArguments out << case option.type.to_sym when :scalar scalar_field(form) @@ -45,7 +44,6 @@ def input(ctx, form) when :boolean, :radio radio_fields(form) end - # rubocop:enable Layout/EmptyLinesAroundArguments end def hidden_field(form) diff --git a/lib/compendium/presenters/table.rb b/lib/compendium/presenters/table.rb index 53ca38a..989cc97 100644 --- a/lib/compendium/presenters/table.rb +++ b/lib/compendium/presenters/table.rb @@ -67,28 +67,28 @@ def build_row(row, row_class, cell_type = :td) end end - def formatted_heading(v) - v.is_a?(Symbol) ? translate(v) : v + def formatted_heading(heading) + heading.is_a?(Symbol) ? translate(heading) : heading end - def formatted_value(k, v) - if @settings.formatters[k] - @settings.formatters[k].call(v) - elsif v.numeric? - if v.zero? && @settings.display_zero_as? + def formatted_value(name, value) + if @settings.formatters[name] + @settings.formatters[name].call(value) + elsif value.numeric? + if value.zero? && @settings.display_zero_as? @settings.display_zero_as else - sprintf(@settings.number_format, v) + sprintf(@settings.number_format, value) end - elsif v.nil? + elsif value.nil? @settings.display_nil_as - end || v + end || value end - def translate(v, opts = {}) + def translate(value, opts = {}) opts.reverse_merge!(scope: settings.i18n_scope) if settings.i18n_scope? - opts[:default] = -> (*) { I18n.t(v, scope: 'compendium') } - I18n.t(v, opts) + opts[:default] = -> (*) { I18n.t(value, scope: 'compendium') } + I18n.t(value, opts) end def setup_totals diff --git a/lib/compendium/report.rb b/lib/compendium/report.rb index 37b8d9b..76a9d02 100644 --- a/lib/compendium/report.rb +++ b/lib/compendium/report.rb @@ -117,20 +117,20 @@ def exports?(type) def method_missing(name, *args, &block) prefix = name.to_s.sub(/(?:_results|\?)\Z/, '').to_sym - return queries[name] if queries.keys.include?(name) - return results[prefix] if name.to_s.end_with?('_results') && queries.keys.include?(prefix) - return params[name] if options.keys.include?(name) - return params[prefix] if name.to_s.end_with?('?') && options.keys.include?(prefix) + return queries[name] if queries.key?(name) + return results[prefix] if name.to_s.end_with?('_results') && queries.key?(prefix) + return params[name] if options.key?(name) + return params[prefix] if name.to_s.end_with?('?') && options.key?(prefix) super end def respond_to_missing?(name, include_private = false) prefix = name.to_s.sub(/(?:_results|\?)\Z/, '').to_sym - return true if queries.keys.include?(name) - return true if name.to_s.end_with?('_results') && queries.keys.include?(prefix) - return true if options.keys.include?(name) - return true if name.to_s.end_with?('?') && options.keys.include?(prefix) + return true if queries.key?(name) + return true if name.to_s.end_with?('_results') && queries.key?(prefix) + return true if options.key?(name) + return true if name.to_s.end_with?('?') && options.key?(prefix) super end end From 149f4c29e3ca57035c78cc1ffe61e6720f387f35 Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Tue, 14 Aug 2018 13:22:09 -0400 Subject: [PATCH 12/23] Add bin/console --- bin/console | 12 ++++++++++++ compendium.gemspec | 1 + 2 files changed, 13 insertions(+) create mode 100755 bin/console diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..604923a --- /dev/null +++ b/bin/console @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "compendium" + +begin + require "pry" + Pry.start +rescue LoadError + require "irb" + IRB.start(__FILE__) +end diff --git a/compendium.gemspec b/compendium.gemspec index deb2086..df1fe0e 100644 --- a/compendium.gemspec +++ b/compendium.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |gem| gem.add_dependency 'rails', '>= 3.0.0', '< 4' gem.add_dependency 'sass-rails', '>= 3.0.0' + gem.add_development_dependency 'pry' gem.add_development_dependency 'rake', '> 11.0.1', '< 12' gem.add_development_dependency 'rspec', '~> 3.8.0' gem.add_development_dependency 'rubocop', '~> 0.58' From 8d2e72b9e9ee106d360d0022162661110f479a6a Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Tue, 14 Aug 2018 13:40:12 -0400 Subject: [PATCH 13/23] Update travis targets --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 75b762a..281e6d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ language: ruby cache: - bundler rvm: - - 2.2.9 - - 2.3.6 - - 2.4.3 - - 2.5.0 + - 2.2.10 + - 2.3.7 + - 2.4.4 + - 2.5.1 before_install: gem install bundler -v 1.16.1 From 033db9c3815ebf1e321e2fae605641ebdd6db70a Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Tue, 14 Aug 2018 13:57:54 -0400 Subject: [PATCH 14/23] Update documentation --- CHANGELOG.md | 6 ++++++ UPGRADING.md | 12 ++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 UPGRADING.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 412e82c..cea5c23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## Future +* Removed support for Ruby < 2.2 +* Added support for Ruby 2.5 +* Changed `collect: :active_record` query option to `sql: true` +* Replaced `mount_compendium` with `mount Compendium::Engine => '/path'` + ## 1.2.2 * Allow report options to be hidden * Allow the collection in a CollectionQuery to be generated at report runtime using a proc diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000..ac3f5f7 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,12 @@ +# Upgrading Compendium + +## Upgrading to >= 2.0.0 + +### Queries +* The `collect` option has been replaced with `sql` (default `false`), in order to decouple queries from +active record. Replace `collect: :active_record` with `sql: false`. + +### Rails +#### Routes +* The `mount_compendium` routing monkey patch was removed in favour of actual routes. The actual routes + are slightly less configurable. Replace `mount_compendium` with `mount Compendium::Engine => '/path'` From 4ed10e548585d1b09487d1b7a09cc7dd714c44dd Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Tue, 14 Aug 2018 14:02:43 -0400 Subject: [PATCH 15/23] Update bin --- bin/console | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/console b/bin/console index 604923a..2a76fb3 100755 --- a/bin/console +++ b/bin/console @@ -1,12 +1,12 @@ #!/usr/bin/env ruby -require "bundler/setup" -require "compendium" +require 'bundler/setup' +require 'compendium' begin - require "pry" + require 'pry' Pry.start rescue LoadError - require "irb" + require 'irb' IRB.start(__FILE__) end From 4c03e44a0189f011b6926f8331a8bf878b0119f2 Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Tue, 14 Aug 2018 15:02:29 -0400 Subject: [PATCH 16/23] Add rubocop-rspec --- .gitignore | 1 + .rspec | 1 + .rubocop.yml | 34 ++++ .rubocop_todo.yml | 23 +-- compendium.gemspec | 1 + spec/compendium/context_wrapper_spec.rb | 16 +- spec/compendium/dsl/exports_spec.rb | 9 +- spec/compendium/dsl/filter_spec.rb | 11 +- spec/compendium/dsl/metric_spec.rb | 15 +- spec/compendium/dsl/option_spec.rb | 19 +- spec/compendium/dsl/query_spec.rb | 32 +-- spec/compendium/dsl/table_spec.rb | 9 +- spec/compendium/metric_spec.rb | 32 ++- spec/compendium/option_spec.rb | 6 +- spec/compendium/param_types/boolean_spec.rb | 27 ++- spec/compendium/param_types/date_spec.rb | 15 +- spec/compendium/param_types/dropdown_spec.rb | 11 +- spec/compendium/param_types/param_spec.rb | 18 +- spec/compendium/param_types/radio_spec.rb | 11 +- spec/compendium/param_types/scalar_spec.rb | 13 +- .../param_types/with_choices_spec.rb | 23 ++- spec/compendium/params_spec.rb | 22 +-- spec/compendium/presenters/base_spec.rb | 8 +- spec/compendium/presenters/chart_spec.rb | 14 +- spec/compendium/presenters/csv_spec.rb | 11 +- spec/compendium/presenters/option_spec.rb | 59 +++--- .../presenters/settings/query_spec.rb | 7 +- .../presenters/settings/table_spec.rb | 19 +- spec/compendium/presenters/table_spec.rb | 27 ++- spec/compendium/queries/collection_spec.rb | 21 +- spec/compendium/queries/count_spec.rb | 34 ++-- spec/compendium/queries/query_spec.rb | 56 +++--- spec/compendium/queries/sum_spec.rb | 34 ++-- spec/compendium/queries/through_spec.rb | 17 +- spec/compendium/report_spec.rb | 186 +++++++++--------- spec/compendium/result_set_spec.rb | 6 +- spec/spec_helper.rb | 16 +- 37 files changed, 463 insertions(+), 401 deletions(-) diff --git a/.gitignore b/.gitignore index 49f3cd2..9433be1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .bundle .config .idea +.rspec_status .yardoc Gemfile.lock InstalledFiles diff --git a/.rspec b/.rspec index 0912718..b18b4cc 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --color --order random +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml index 7bd11d1..da38d0e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,5 @@ inherit_from: .rubocop_todo.yml +require: rubocop-rspec Layout/AccessModifierIndentation: EnforcedStyle: outdent @@ -46,6 +47,39 @@ Metrics/ModuleLength: Metrics/LineLength: Max: 150 +RSpec/ContextWording: + Enabled: false + +RSpec/ExampleLength: + Max: 10 + +RSpec/ExpectChange: + EnforcedStyle: block + +RSpec/LeadingSubject: + Enabled: false + +RSpec/MessageSpies: + EnforcedStyle: receive + +RSpec/MultipleExpectations: + Enabled: false + +RSpec/NamedSubject: + Enabled: false + +RSpec/NestedGroups: + Max: 5 + +RSpec/NotToNot: + EnforcedStyle: to_not + +RSpec/SubjectStub: + Enabled: false + +RSpec/VerifiedDoubles: + Enabled: false + Style/Alias: EnforcedStyle: prefer_alias_method diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 640e9b0..6e79135 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,32 +1,33 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2018-01-26 13:11:40 -0500 using RuboCop version 0.52.1. +# on 2018-08-14 15:01:45 -0400 using RuboCop version 0.58.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 15 +# Offense count: 16 Metrics/AbcSize: Max: 33 -# Offense count: 4 +# Offense count: 3 Metrics/CyclomaticComplexity: Max: 7 # Offense count: 14 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 17 + Max: 15 -# Offense count: 3 +# Offense count: 2 Metrics/PerceivedComplexity: Max: 9 -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: braces, no_braces, context_dependent -Style/BracesAroundHashParameters: +# Offense count: 6 +RSpec/AnyInstance: Exclude: - - 'spec/compendium/presenters/option_spec.rb' + - 'spec/compendium/dsl/query_spec.rb' + - 'spec/compendium/presenters/chart_spec.rb' + - 'spec/compendium/queries/collection_spec.rb' + - 'spec/compendium/queries/query_spec.rb' + - 'spec/compendium/report_spec.rb' diff --git a/compendium.gemspec b/compendium.gemspec index df1fe0e..ecd3f3e 100644 --- a/compendium.gemspec +++ b/compendium.gemspec @@ -27,4 +27,5 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rake', '> 11.0.1', '< 12' gem.add_development_dependency 'rspec', '~> 3.8.0' gem.add_development_dependency 'rubocop', '~> 0.58' + gem.add_development_dependency 'rubocop-rspec', '~> 1.28' end diff --git a/spec/compendium/context_wrapper_spec.rb b/spec/compendium/context_wrapper_spec.rb index 90a3bf8..206ee01 100644 --- a/spec/compendium/context_wrapper_spec.rb +++ b/spec/compendium/context_wrapper_spec.rb @@ -24,7 +24,7 @@ def wrapper_num end end -describe Compendium::ContextWrapper do +RSpec.describe Compendium::ContextWrapper do describe '.wrap' do let(:w1) { Wrapper1.new } let(:w2) { Wrapper2.new } @@ -39,32 +39,34 @@ def wrapper_num specify { expect(subject.test_val).to eq(123) } specify { expect(subject.wrapped).to eq(true) } - it 'should not affect the original objects' do + it 'does not affect the original objects' do subject - expect(w1).not_to respond_to :wrapped - expect(w2).not_to respond_to :test_val + expect(w1).to_not respond_to :wrapped + expect(w2).to_not respond_to :test_val end - it 'should yield a block if given' do + it 'yields a block if given' do expect(described_class.wrap(w2, w1) { test_val }).to eq(123) end context 'overriding methods' do subject { described_class.wrap(w4, w3) } + specify { expect(subject.wrapper_num).to eq(4) } end context 'nested wrapping' do let(:inner) { described_class.wrap(w2, w1) } + subject { described_class.wrap(inner, w3) } it { is_expected.to respond_to :test_val } it { is_expected.to respond_to :wrapped } it { is_expected.to respond_to :wrapper_num } - it 'should not extend the inner wrap' do + it 'does not extend the inner wrap' do subject - expect(inner).not_to respond_to :wrapper_num + expect(inner).to_not respond_to :wrapper_num end end end diff --git a/spec/compendium/dsl/exports_spec.rb b/spec/compendium/dsl/exports_spec.rb index fc548ac..6cf71fd 100644 --- a/spec/compendium/dsl/exports_spec.rb +++ b/spec/compendium/dsl/exports_spec.rb @@ -1,8 +1,7 @@ -require 'spec_helper' require 'compendium' require 'compendium/dsl' -describe Compendium::DSL::Exports do +RSpec.describe Compendium::DSL::Exports do subject do Class.new do extend Compendium::DSL::Exports @@ -11,16 +10,16 @@ end describe '#exports' do - it 'should not have any exporters by default' do + it 'does not have any exporters by default' do expect(subject.exporters).to be_empty end - it 'should set the export to true if no options are given' do + it 'sets the export to true if no options are given' do subject.exports :csv expect(subject.exporters[:csv]).to eq(true) end - it 'should save any given options' do + it 'saves any given options' do subject.exports :csv, :main_query subject.exports :pdf, :foo, :bar expect(subject.exporters[:csv]).to eq(:main_query) diff --git a/spec/compendium/dsl/filter_spec.rb b/spec/compendium/dsl/filter_spec.rb index 4d31a79..d2837a8 100644 --- a/spec/compendium/dsl/filter_spec.rb +++ b/spec/compendium/dsl/filter_spec.rb @@ -1,8 +1,7 @@ -require 'spec_helper' require 'compendium' require 'compendium/dsl' -describe Compendium::DSL::Filter do +RSpec.describe Compendium::DSL::Filter do subject do Class.new do extend Compendium::DSL @@ -12,24 +11,24 @@ describe '#filter' do let(:filter_proc) { -> { :filter } } - it 'should add a filter to the given query' do + it 'adds a filter to the given query' do subject.query :test subject.filter :test, &filter_proc expect(subject.queries[:test].filters).to include filter_proc end - it 'should raise an error if there is no query of the given name' do + it 'raises an error if there is no query of the given name' do expect { subject.filter :test, &filter_proc }.to raise_error(ArgumentError, 'query test is not defined') end - it 'should allow multiple filters to be defined for the same query' do + it 'allows multiple filters to be defined for the same query' do subject.query :test subject.filter :test, &filter_proc subject.filter :test, &-> { :another_filter } expect(subject.queries[:test].filters.count).to eq(2) end - it 'should allow a filter to be applied to multiple queries at once' do + it 'allows a filter to be applied to multiple queries at once' do subject.query :query1 subject.query :query2 subject.filter :query1, :query2, &filter_proc diff --git a/spec/compendium/dsl/metric_spec.rb b/spec/compendium/dsl/metric_spec.rb index 678aa70..f118a3f 100644 --- a/spec/compendium/dsl/metric_spec.rb +++ b/spec/compendium/dsl/metric_spec.rb @@ -1,8 +1,7 @@ -require 'spec_helper' require 'compendium' require 'compendium/dsl' -describe Compendium::DSL::Metric do +RSpec.describe Compendium::DSL::Metric do subject do Class.new do extend Compendium::DSL @@ -17,20 +16,20 @@ subject.metric :test_metric, metric_proc, through: :test end - it 'should add a metric to the given query' do + it 'adds a metric to the given query' do expect(subject.queries[:test].metrics.first.name).to eq(:test_metric) end - it 'should set the metric command' do + it 'sets the metric command' do expect(subject.queries[:test].metrics.first.command).to eq(metric_proc) end context 'when through is specified' do - it 'should raise an error if specified for an invalid query' do + it 'raises an error if specified for an invalid query' do expect { subject.metric :test_metric, metric_proc, through: :fake }.to raise_error ArgumentError, 'query fake is not defined' end - it 'should allow metrics to be defined with a block' do + it 'allows metrics to be defined with a block' do subject.metric :block_metric, through: :test do 123 end @@ -38,7 +37,7 @@ expect(subject.queries[:test].metrics[:block_metric].run(self, nil)).to eq(123) end - it 'should allow metrics to be defined with a lambda' do + it 'allows metrics to be defined with a lambda' do subject.metric :block_metric, -> (*) { 123 }, through: :test expect(subject.queries[:test].metrics[:block_metric].run(self, nil)).to eq(123) end @@ -49,7 +48,7 @@ specify { expect(subject.queries).to include :__metric_no_through_metric } - it 'should return the result of the query as the result of the metric' do + it 'returns the result of the query as the result of the metric' do expect(subject.queries[:__metric_no_through_metric].metrics[:no_through_metric].run(self, [123])).to eq(123) end end diff --git a/spec/compendium/dsl/option_spec.rb b/spec/compendium/dsl/option_spec.rb index b279839..3636a67 100644 --- a/spec/compendium/dsl/option_spec.rb +++ b/spec/compendium/dsl/option_spec.rb @@ -1,9 +1,8 @@ -require 'spec_helper' require 'compendium' require 'compendium/dsl' require 'compendium/option' -describe Compendium::DSL::Option do +RSpec.describe Compendium::DSL::Option do subject do Class.new do extend Compendium::DSL::Option @@ -17,29 +16,29 @@ specify { expect(subject.options).to include :starting_on } specify { expect(subject.options[:starting_on]).to be_date } - it 'should allow previously defined options to be redefined' do + it 'allows previously defined options to be redefined' do subject.option :starting_on, :boolean expect(subject.options[:starting_on]).to be_boolean - expect(subject.options[:starting_on]).not_to be_date + expect(subject.options[:starting_on]).to_not be_date end - it 'should allow overriding default value' do + it 'allows overriding default value' do proc = -> { Date.new(2013, 6, 1) } subject.option :starting_on, :date, default: proc expect(subject.options[:starting_on].default).to eq(proc) end - it 'should add validations' do + it 'adds validations' do subject.option :foo, validates: { presence: true } - expect(subject.params_class.validators_on(:foo)).not_to be_empty + expect(subject.params_class.validators_on(:foo)).to_not be_empty end - it 'should not add validations if no validates option is given' do - expect(subject.params_class).not_to receive :validates + it 'does not add validations if no validates option is given' do + expect(subject.params_class).to_not receive :validates subject.option :foo end - it 'should not bleed overridden options into the superclass' do + it 'does not bleed overridden options into the superclass' do r = Class.new(subject) r.option :starting_on, :boolean r.option :new, :date diff --git a/spec/compendium/dsl/query_spec.rb b/spec/compendium/dsl/query_spec.rb index ce87974..935faa1 100644 --- a/spec/compendium/dsl/query_spec.rb +++ b/spec/compendium/dsl/query_spec.rb @@ -1,7 +1,6 @@ -require 'spec_helper' require 'compendium/dsl' -describe Compendium::DSL::Query do +RSpec.describe Compendium::DSL::Query do let(:proc1) { -> { :proc1 } } let(:proc2) { -> { :proc2 } } let(:report_class) do @@ -17,24 +16,24 @@ describe '#query' do specify { expect(subject.queries).to include :test } - it 'should relate the new query back to the report instance' do + it 'relates the new query back to the report instance' do r = subject.new expect(r.test.report).to eq(r) end - it 'should relate a query to the report class' do + it 'relates a query to the report class' do expect(subject.test.report).to eq(subject) end context 'when the query was previously defined' do before { subject.query :test_query } - it 'should allow previously defined queries to be redefined by name' do + it 'allows previously defined queries to be redefined by name' do subject.test_query foo: :bar expect(subject.queries[:test_query].options).to eq(foo: :bar) end - it 'should allow previously defined queries to be accessed by name' do + it 'allows previously defined queries to be accessed by name' do expect(subject.test_query).to eq(subject.queries[:test_query]) end end @@ -45,24 +44,24 @@ subject.query :another_test, count: true end - it 'should delete the existing query' do + it 'replaces the existing query' do expect(subject.queries.count).to eq(2) end - it 'should only have one query with each name' do + it 'only has one query with each name' do expect(subject.queries.map(&:name)).to match_array([:test, :another_test]) end - it 'should use the new proc' do + it 'uses the new proc' do expect(subject.test.proc).to eq(proc2) end - it 'should not allow replacing a query with a different type' do + it 'does not allow replacing a query with a different type' do expect { subject.query :test, count: true }.to raise_error(Compendium::Queries::CannotRedefineType) expect(subject.test).to be_instance_of Compendium::Queries::Query end - it 'should allow replacing a query with the same type' do + it 'allows replacing a query with the same type' do subject.query :another_test, count: true, &proc2 expect(subject.another_test.proc).to eq(proc2) expect(subject.another_test).to be_instance_of Compendium::Queries::Count @@ -71,6 +70,7 @@ context 'when given a through option' do before { report_class.query :through, through: :test } + subject { report_class.queries[:through] } specify { is_expected.to be_a Compendium::Queries::Through } @@ -99,6 +99,7 @@ context 'that is a query' do let(:query) { Compendium::Queries::Query.new(:query, {}, -> {}) } + before { report_class.query :collection, collection: query } specify { expect(subject.collection).to eq(query) } @@ -110,13 +111,15 @@ context 'set to true' do before { report_class.query :counted, count: true } + it { is_expected.to be_a Compendium::Queries::Count } end context 'set to false' do before { report_class.query :counted, count: false } + it { is_expected.to be_a Compendium::Queries::Query } - it { is_expected.not_to be_a Compendium::Queries::Count } + it { is_expected.to_not be_a Compendium::Queries::Count } end end @@ -132,19 +135,22 @@ context 'set to false' do before { report_class.query :summed, sum: false } + it { is_expected.to be_a Compendium::Queries::Query } - it { is_expected.not_to be_a Compendium::Queries::Sum } + it { is_expected.to_not be_a Compendium::Queries::Sum } end end end describe '#chart' do before { subject.chart(:chart) } + specify { expect(subject.queries).to include :chart } end describe '#data' do before { subject.data(:data) } + specify { expect(subject.queries).to include :data } end end diff --git a/spec/compendium/dsl/table_spec.rb b/spec/compendium/dsl/table_spec.rb index 3661e80..50a05ad 100644 --- a/spec/compendium/dsl/table_spec.rb +++ b/spec/compendium/dsl/table_spec.rb @@ -1,8 +1,7 @@ -require 'spec_helper' require 'compendium' require 'compendium/dsl' -describe Compendium::DSL::Table do +RSpec.describe Compendium::DSL::Table do subject do Class.new do extend Compendium::DSL @@ -12,17 +11,17 @@ describe '#table' do let(:table_proc) { -> { display_nil_as 'na' } } - it 'should add table settings to the given query' do + it 'adds table settings to the given query' do subject.query :test subject.table :test, &table_proc expect(subject.queries[:test].table_settings).to eq(table_proc) end - it 'should raise an error if there is no query of the given name' do + it 'raises an error if there is no query of the given name' do expect { subject.table :test, &table_proc }.to raise_error(ArgumentError, 'query test is not defined') end - it 'should allow table settings to be applied to multiple queries at once' do + it 'allows table settings to be applied to multiple queries at once' do subject.query :query1 subject.query :query2 subject.table :query1, :query2, &table_proc diff --git a/spec/compendium/metric_spec.rb b/spec/compendium/metric_spec.rb index aad5ac2..33b49a2 100644 --- a/spec/compendium/metric_spec.rb +++ b/spec/compendium/metric_spec.rb @@ -6,24 +6,24 @@ def calculate(data) end end -describe Compendium::Metric do +RSpec.describe Compendium::Metric do let(:ctx) { MetricContext.new } let(:data) { [[1, 2, 3], [4, 5, 6]] } subject { described_class.new(:test_metric, :query, nil) } describe '#run' do - it 'should delegate the command to the context when the command is a symbol' do + it 'delegates the command to the context when the command is a symbol' do subject.command = :calculate expect(subject.run(ctx, data)).to eq(1) end - it 'should call the command when it is a proc' do + it 'calls the command when it is a proc' do subject.command = -> (d) { d.flatten.inject(:+) } expect(subject.run(ctx, data)).to eq(21) end - it 'should allow procs that refer back to the context' do + it 'allows procs that refer back to the context' do subject.command = -> (d) { calculate(d) * 2 } expect(subject.run(ctx, data)).to eq(2) end @@ -31,54 +31,52 @@ def calculate(data) context 'when an if proc is given' do before { subject.command = -> (*) { 100 } } - it 'should calculate the metric if the proc evaluates to true' do + it 'calculates the metric if the proc evaluates to true' do subject.options[:if] = -> { true } expect(subject.run(ctx, data)).to eq(100) end - it 'should not calculate the metric if the proc evaluates to false' do + it 'does not calculate the metric if the proc evaluates to false' do subject.options[:if] = -> { false } expect(subject.run(ctx, data)).to be_nil end - it 'should clear the result if the proc evaluates to false' do + it 'sets the result to nil if the proc evaluates to false' do subject.options[:if] = -> { false } subject.result = 123 - subject.run(ctx, data) - expect(subject.result).to be_nil + expect { subject.run(ctx, data) }.to change { subject.result }.from(123).to(nil) end end context 'when an unless proc is given' do before { subject.command = -> (*) { 100 } } - it 'should calculate the metric if the proc evaluates to false' do + it 'calculates the metric if the proc evaluates to false' do subject.options[:unless] = -> { false } expect(subject.run(ctx, data)).to eq(100) end - it 'should not calculate the metric if the proc evaluates to true' do + it 'does not calculate the metric if the proc evaluates to true' do subject.options[:unless] = -> { true } expect(subject.run(ctx, data)).to be_nil end - it 'should clear the result if the proc evaluates to false' do + it 'sets the result to nil if the proc evaluates to true' do subject.options[:unless] = -> { true } subject.result = 123 - subject.run(ctx, data) - expect(subject.result).to be_nil + expect { subject.run(ctx, data) }.to change { subject.result }.from(123).to(nil) end end end describe '#ran?' do - it 'should return true if there are any results' do + it 'returns true if there are any results' do allow(subject).to receive_messages(result: 123) expect(subject).to have_ran end - it 'should return false if there are no results' do - expect(subject).not_to have_ran + it 'returns false if there are no results' do + expect(subject).to_not have_ran end end end diff --git a/spec/compendium/option_spec.rb b/spec/compendium/option_spec.rb index 147f0a9..402ac6a 100644 --- a/spec/compendium/option_spec.rb +++ b/spec/compendium/option_spec.rb @@ -1,11 +1,11 @@ require 'compendium/option' -describe Compendium::Option do - it 'should raise an ArgumentError if no name is given' do +RSpec.describe Compendium::Option do + it 'raises an ArgumentError if no name is given' do expect { described_class.new }.to raise_error ArgumentError, 'name must be provided' end - it 'should set up type predicates from the type option' do + it 'sets up type predicates from the type option' do o = described_class.new(name: :option, type: :date) expect(o).to be_date end diff --git a/spec/compendium/param_types/boolean_spec.rb b/spec/compendium/param_types/boolean_spec.rb index e9a3a6b..c9c872b 100644 --- a/spec/compendium/param_types/boolean_spec.rb +++ b/spec/compendium/param_types/boolean_spec.rb @@ -1,43 +1,42 @@ -require 'spec_helper' require 'compendium/param_types/boolean' -describe Compendium::ParamTypes::Boolean do +RSpec.describe Compendium::ParamTypes::Boolean do subject { described_class.new(true) } - it { is_expected.not_to be_scalar } + it { is_expected.to_not be_scalar } it { is_expected.to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } + it { is_expected.to_not be_date } + it { is_expected.to_not be_dropdown } + it { is_expected.to_not be_radio } - it 'should pass along 0 and 1' do + it 'passes along 0 and 1' do expect(described_class.new(0)).to eq(0) expect(described_class.new(1)).to eq(1) end - it 'should convert a numeric string to a number' do + it 'converts a numeric string to a number' do expect(described_class.new('0')).to eq(0) expect(described_class.new('1')).to eq(1) end - it 'should return 0 for a truthy value' do + it 'returns 0 for a truthy value' do expect(described_class.new(true)).to eq(0) expect(described_class.new(:abc)).to eq(0) end - it 'should return 1 for a falsey value' do + it 'returns 1 for a falsey value' do expect(described_class.new(false)).to eq(1) expect(described_class.new(nil)).to eq(1) end describe '#value' do - it 'should return true for a truthy value' do + it 'returns true for a truthy value' do expect(described_class.new(true).value).to eq(true) expect(described_class.new(:abc).value).to eq(true) expect(described_class.new(0).value).to eq(true) end - it 'should return false for a falsey value' do + it 'returns false for a falsey value' do expect(described_class.new(false).value).to eq(false) expect(described_class.new(nil).value).to eq(false) expect(described_class.new(1).value).to eq(false) @@ -45,11 +44,11 @@ end describe '#!' do - it 'should return false if the boolean is true' do + it 'returns false if the boolean is true' do expect(!described_class.new(true)).to eq(false) end - it 'should return true if the boolean is false' do + it 'returns true if the boolean is false' do expect(!described_class.new(false)).to eq(true) end end diff --git a/spec/compendium/param_types/date_spec.rb b/spec/compendium/param_types/date_spec.rb index d9e5724..3d34d50 100644 --- a/spec/compendium/param_types/date_spec.rb +++ b/spec/compendium/param_types/date_spec.rb @@ -1,21 +1,20 @@ -require 'spec_helper' require 'compendium/param_types/date' -describe Compendium::ParamTypes::Date do +RSpec.describe Compendium::ParamTypes::Date do subject { described_class.new(Date.today) } - it { is_expected.not_to be_scalar } - it { is_expected.not_to be_boolean } + it { is_expected.to_not be_scalar } + it { is_expected.to_not be_boolean } it { is_expected.to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } + it { is_expected.to_not be_dropdown } + it { is_expected.to_not be_radio } - it 'should convert date strings to date objects' do + it 'converts date strings to date objects' do p = described_class.new('2010-05-20') expect(p).to eq(Date.new(2010, 5, 20)) end - it 'should convert other date/time formats to date objects' do + it 'converts other date/time formats to date objects' do expect(described_class.new(DateTime.new(2010, 5, 20, 10, 30, 59))).to eq(Date.new(2010, 5, 20)) expect(described_class.new(Time.new(2010, 5, 20, 10, 30, 59))).to eq(Date.new(2010, 5, 20)) expect(described_class.new(Date.new(2010, 5, 20))).to eq(Date.new(2010, 5, 20)) diff --git a/spec/compendium/param_types/dropdown_spec.rb b/spec/compendium/param_types/dropdown_spec.rb index ee053af..4caf3cb 100644 --- a/spec/compendium/param_types/dropdown_spec.rb +++ b/spec/compendium/param_types/dropdown_spec.rb @@ -1,12 +1,11 @@ -require 'spec_helper' require 'compendium/param_types/dropdown' -describe Compendium::ParamTypes::Dropdown do +RSpec.describe Compendium::ParamTypes::Dropdown do subject { described_class.new(0, %w(a b c)) } - it { is_expected.not_to be_scalar } - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } + it { is_expected.to_not be_scalar } + it { is_expected.to_not be_boolean } + it { is_expected.to_not be_date } it { is_expected.to be_dropdown } - it { is_expected.not_to be_radio } + it { is_expected.to_not be_radio } end diff --git a/spec/compendium/param_types/param_spec.rb b/spec/compendium/param_types/param_spec.rb index cdfff5f..10dc05d 100644 --- a/spec/compendium/param_types/param_spec.rb +++ b/spec/compendium/param_types/param_spec.rb @@ -1,19 +1,17 @@ -require 'spec_helper' require 'compendium/param_types/param' -describe Compendium::ParamTypes::Param do +RSpec.describe Compendium::ParamTypes::Param do subject { described_class.new(:test) } - it { is_expected.not_to be_scalar } - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } + it { is_expected.to_not be_scalar } + it { is_expected.to_not be_boolean } + it { is_expected.to_not be_date } + it { is_expected.to_not be_dropdown } + it { is_expected.to_not be_radio } describe '#==' do - it "should compare to the param's value" do - allow(subject).to receive_messages(value: :test_value) - expect(subject).to eq(:test_value) + it "compares to the param's value" do + expect(subject).to eq(:test) end end end diff --git a/spec/compendium/param_types/radio_spec.rb b/spec/compendium/param_types/radio_spec.rb index d74d916..f65dc68 100644 --- a/spec/compendium/param_types/radio_spec.rb +++ b/spec/compendium/param_types/radio_spec.rb @@ -1,12 +1,11 @@ -require 'spec_helper' require 'compendium/param_types/radio' -describe Compendium::ParamTypes::Radio do +RSpec.describe Compendium::ParamTypes::Radio do subject { described_class.new(0, %w(a b c)) } - it { is_expected.not_to be_scalar } - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } + it { is_expected.to_not be_scalar } + it { is_expected.to_not be_boolean } + it { is_expected.to_not be_date } + it { is_expected.to_not be_dropdown } it { is_expected.to be_radio } end diff --git a/spec/compendium/param_types/scalar_spec.rb b/spec/compendium/param_types/scalar_spec.rb index 01d0d83..1e39251 100644 --- a/spec/compendium/param_types/scalar_spec.rb +++ b/spec/compendium/param_types/scalar_spec.rb @@ -1,16 +1,15 @@ -require 'spec_helper' require 'compendium/param_types/scalar' -describe Compendium::ParamTypes::Scalar do +RSpec.describe Compendium::ParamTypes::Scalar do subject { described_class.new(123) } it { is_expected.to be_scalar } - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } + it { is_expected.to_not be_boolean } + it { is_expected.to_not be_date } + it { is_expected.to_not be_dropdown } + it { is_expected.to_not be_radio } - it 'should not change values' do + it 'does not change values' do expect(subject).to eq(123) end end diff --git a/spec/compendium/param_types/with_choices_spec.rb b/spec/compendium/param_types/with_choices_spec.rb index 8bb66cc..444b19d 100644 --- a/spec/compendium/param_types/with_choices_spec.rb +++ b/spec/compendium/param_types/with_choices_spec.rb @@ -1,39 +1,38 @@ -require 'spec_helper' require 'compendium/param_types/with_choices' -describe Compendium::ParamTypes::WithChoices do +RSpec.describe Compendium::ParamTypes::WithChoices do subject { described_class.new(0, %w(a b c)) } - it { is_expected.not_to be_boolean } - it { is_expected.not_to be_date } - it { is_expected.not_to be_dropdown } - it { is_expected.not_to be_radio } + it { is_expected.to_not be_boolean } + it { is_expected.to_not be_date } + it { is_expected.to_not be_dropdown } + it { is_expected.to_not be_radio } - it 'should return the index when given an index' do + it 'returns the index when given an index' do p = described_class.new(1, %i(foo bar baz)) expect(p).to eq(1) end - it 'should return the index when given a value' do + it 'returns the index when given a value' do p = described_class.new(:foo, %i(foo bar baz)) expect(p).to eq(0) end - it 'should return the index when given a string value' do + it 'returns the index when given a string value' do p = described_class.new('2', %i(foo bar baz)) expect(p).to eq(2) end - it 'should raise an error if given an invalid index' do + it 'raises an error if given an invalid index' do expect { described_class.new(3, %i(foo bar baz)) }.to raise_error IndexError end - it 'should raise an error if given a value that is not included in the choices' do + it 'raises an error if given a value that is not included in the choices' do expect { described_class.new(:quux, %i(foo bar baz)) }.to raise_error IndexError end describe '#value' do - it 'should return the value of the given choice' do + it 'returns the value of the given choice' do p = described_class.new(2, %i(foo bar baz)) expect(p.value).to eq(:baz) end diff --git a/spec/compendium/params_spec.rb b/spec/compendium/params_spec.rb index 57786ba..275220d 100644 --- a/spec/compendium/params_spec.rb +++ b/spec/compendium/params_spec.rb @@ -1,6 +1,7 @@ require 'compendium/params' -describe Compendium::Params do +RSpec.describe Compendium::Params do + let(:params) { {} } let(:options) do opts = Collection[Compendium::Option] opts << Compendium::Option.new(name: :starting_on, type: :date, default: -> { Date.today }) @@ -12,20 +13,18 @@ opts end - subject { described_class.new(@params, options) } + subject { described_class.new(params, options) } - it 'should only allow keys that are given as options' do - @params = { starting_on: '2013-10-15', foo: :bar } - expect(subject.keys).not_to include :foo + it 'only allows keys that are given as options' do + params.merge!(starting_on: '2013-10-15', foo: :bar) + expect(subject.keys).to_not include :foo end - it 'should set missing options to their default value' do - @params = {} + it 'sets missing options to their default value' do expect(subject.starting_on).to eq(Date.today) end - it 'should set missing options to nil if there is no default value' do - @params = {} + it 'sets missing options to nil if there is no default value' do expect(subject.ending_on).to be_nil end @@ -40,18 +39,19 @@ subject.valid? end - it { is_expected.not_to be_valid } + it { is_expected.to_not be_valid } specify { expect(subject.errors.keys).to include :ending_on } end context 'numericality' do subject { report_class.new({ number: 'abcd' }, options) } + before do report_class.validates :number, numericality: true subject.valid? end - it { is_expected.not_to be_valid } + it { is_expected.to_not be_valid } specify { expect(subject.errors.keys).to include :number } end end diff --git a/spec/compendium/presenters/base_spec.rb b/spec/compendium/presenters/base_spec.rb index 4a57241..48b5c09 100644 --- a/spec/compendium/presenters/base_spec.rb +++ b/spec/compendium/presenters/base_spec.rb @@ -1,19 +1,19 @@ -require 'spec_helper' require 'compendium/presenters/base' TestPresenter = Class.new(Compendium::Presenters::Base) do presents :test_obj end -describe Compendium::Presenters::Base do +RSpec.describe Compendium::Presenters::Base do let(:template) { double('Template', delegated?: true) } + subject { TestPresenter.new(template, :test) } - it 'should allow the object name to be overridden' do + it 'allows the object name to be overridden' do expect(subject.test_obj).to eq(:test) end - it 'should delegate missing methods to the template object' do + it 'delegates missing methods to the template object' do expect(template).to receive(:delegated?) expect(subject).to be_delegated end diff --git a/spec/compendium/presenters/chart_spec.rb b/spec/compendium/presenters/chart_spec.rb index 148cd20..8bf4c59 100644 --- a/spec/compendium/presenters/chart_spec.rb +++ b/spec/compendium/presenters/chart_spec.rb @@ -1,7 +1,6 @@ -require 'spec_helper' require 'compendium/presenters/chart' -describe Compendium::Presenters::Chart do +RSpec.describe Compendium::Presenters::Chart do let(:template) do double( 'Template', @@ -34,7 +33,8 @@ end context 'when options are given' do - before { allow(results).to receive(:records) { { one: [] } } } + before { allow(results).to receive(:records).and_return(one: []) } + subject { described_class.new(template, query, :pie, index: :one) } specify { expect(subject.data).to eq(results.records[:one]) } @@ -62,17 +62,17 @@ end describe '#remote?' do - it 'should be true if options[:remote] is set to true' do + it 'returns true if options[:remote] is set to true' do expect(described_class.new(template, query, :pie, remote: true)).to be_remote end - it 'should be true if the query has not been run yet' do + it 'returns true if the query has not been run yet' do allow(query).to receive_messages(run?: false) described_class.new(template, query, :pie).should_be_remote end - it 'should be false otherwise' do - expect(described_class.new(template, query, :pie)).not_to be_remote + it 'returns false otherwise' do + expect(described_class.new(template, query, :pie)).to_not be_remote end end end diff --git a/spec/compendium/presenters/csv_spec.rb b/spec/compendium/presenters/csv_spec.rb index efa4f16..ffa3d8c 100644 --- a/spec/compendium/presenters/csv_spec.rb +++ b/spec/compendium/presenters/csv_spec.rb @@ -1,27 +1,26 @@ require 'compendium/presenters/csv' -describe Compendium::Presenters::CSV do +RSpec.describe Compendium::Presenters::CSV do let(:results) { double('Results', records: [{ group: 'A', one: 1, two: 2 }, { group: 'B', one: 3, two: 4 }], keys: %i(group one two)) } let(:query) { double('Query', results: results, options: {}, table_settings: nil) } let(:presenter) { described_class.new(query) } before do allow(query).to receive(:pos) { raise caller.join("\n") } + allow(I18n).to receive(:t) { |key| key } end - before { allow(I18n).to receive(:t) { |key| key } } - describe '#render' do - it 'should return a CSV of the results' do + it 'returns the results as CSV' do expect(presenter.render).to eq("group,one,two\nA,1.00,2.00\nB,3.00,4.00\n") end - it "should use the query's table settings" do + it "uses the query's table settings" do allow(query).to receive(:table_settings).and_return(-> (*) { number_format '%0.0f' }) expect(presenter.render).to eq("group,one,two\nA,1,2\nB,3,4\n") end - it 'should output a total row if the query has totals' do + it 'outputs a total row if the query has totals' do query.results.records << { group: '', one: 4, two: 6 } query.options[:totals] = true expect(presenter.render).to eq("group,one,two\nA,1.00,2.00\nB,3.00,4.00\ntotal,4.00,6.00\n") diff --git a/spec/compendium/presenters/option_spec.rb b/spec/compendium/presenters/option_spec.rb index f597088..35223fe 100644 --- a/spec/compendium/presenters/option_spec.rb +++ b/spec/compendium/presenters/option_spec.rb @@ -1,8 +1,7 @@ -require 'spec_helper' require 'compendium/presenters/option' require 'compendium/option' -describe Compendium::Presenters::Option do +RSpec.describe Compendium::Presenters::Option do let(:template) do t = double('Template') allow(t).to receive(:t) { |key| key } # Stub I18n.t to just return the given value @@ -16,7 +15,7 @@ subject { described_class.new(template, option) } describe '#name' do - it 'should pass the name through I18n' do + it 'passes the name through I18n' do expect(template).to receive(:t).with('options.test_option', anything) subject.name end @@ -25,23 +24,27 @@ describe '#note' do before { allow(template).to receive(:content_tag) } - it 'should return nil if no note is specified' do + it 'returns nil if no note is specified' do expect(subject.note).to be_nil end - it 'should pass to I18n if the note option is set to true' do - option[:note] = true - expect(template).to receive(:t).with(:test_option_note) - subject.note + context 'given note: true' do + it 'retrieves the default key from I18n' do + option[:note] = true + expect(template).to receive(:t).with(:test_option_note) + subject.note + end end - it 'should pass to I18n if the note option is set' do - option[:note] = :the_note - expect(template).to receive(:t).with(:the_note) - subject.note + context 'given note: something' do + it 'retrieves the given key from I18n' do + option[:note] = :the_note + expect(template).to receive(:t).with(:the_note) + subject.note + end end - it 'should create the note within a div with class option-note' do + it 'creates the note within a div with class option-note' do option[:note] = true expect(template).to receive(:content_tag).with(:div, anything, class: 'option-note') subject.note @@ -61,33 +64,33 @@ context 'when AccessibleTooltip is present' do before do stub_const('AccessibleTooltip', Object.new) - expect(template).to receive(:accessible_tooltip).and_yield + allow(template).to receive(:accessible_tooltip).and_yield end - it 'should return a label with the tooltip' do + it 'returns a label with the tooltip' do expect(form).to receive(:label).with(:test_option, 'options.test') subject.label(form) end end - it 'should translate the note' do + it 'translates the note' do expect(template).to receive(:t).with('options.test', anything) subject.label(form) end - it 'should translate the option name if no specific note is given' do + it 'translates the option name if no specific note is given' do option[:note] = true expect(template).to receive(:t).with('options.test_option_note', anything) subject.label(form) end - it 'should render the note' do + it 'renders the note' do expect(template).to receive(:content_tag).with(:div, 'options.test', class: 'option-note') subject.label(form) end end - it 'should render the label' do + it 'renders the label' do expect(template).to receive(:content_tag).with(:span, 'options.test_option', class: 'option-label') subject.label(form) end @@ -105,7 +108,7 @@ context 'with a scalar option' do before { option.type = :scalar } - it 'should render an text field' do + it 'renders an text field' do expect(form).to receive(:text_field).with(:test_option) subject.input(ctx, form) end @@ -114,12 +117,12 @@ context 'with a date option' do before { option.type = :date } - it 'should render a text field' do + it 'renders a text field' do expect(form).to receive(:text_field).with(:test_option) subject.input(ctx, form) end - it 'should render a calendar date select if defined' do + it 'renders a calendar date select if defined' do stub_const('CalendarDateSelect', Object.new) expect(form).to receive(:calendar_date_select).with(:test_option, anything) subject.input(ctx, form) @@ -129,12 +132,12 @@ context 'with a dropdown option' do before { option.type = :dropdown } - it 'should render a select field' do - expect(form).to receive(:select).with(:test_option, [1, 2, 3], { foo: :bar }) + it 'renders a select field' do + expect(form).to receive(:select).with(:test_option, [1, 2, 3], foo: :bar) subject.input(ctx, form) end - it 'should raise if there are no choices' do + it 'raises if there are no choices' do option.choices = nil expect { subject.input(ctx, form) }.to raise_error ArgumentError end @@ -143,7 +146,7 @@ context 'with a boolean option' do before { option.type = :boolean } - it 'should render radio buttons and labels for true and false' do + it 'renders radio buttons and labels for true and false' do expect(form).to receive(:radio_button).with(:test_option, 0) expect(form).to receive(:label).with(:test_option, 'true', value: 0) expect(form).to receive(:radio_button).with(:test_option, 1) @@ -155,7 +158,7 @@ context 'with a radio option' do before { option.type = :radio } - it 'should render radio buttons and labels for each option' do + it 'renders radio buttons and labels for each option' do expect(form).to receive(:radio_button).with(:test_option, 0) expect(form).to receive(:label).with(:test_option, 1, value: 0) expect(form).to receive(:radio_button).with(:test_option, 1) @@ -165,7 +168,7 @@ subject.input(ctx, form) end - it 'should raise if there are no choices' do + it 'raises if there are no choices' do option.choices = nil expect { subject.input(ctx, form) }.to raise_error ArgumentError end diff --git a/spec/compendium/presenters/settings/query_spec.rb b/spec/compendium/presenters/settings/query_spec.rb index b409096..c39be2f 100644 --- a/spec/compendium/presenters/settings/query_spec.rb +++ b/spec/compendium/presenters/settings/query_spec.rb @@ -1,13 +1,12 @@ -require 'spec_helper' require 'compendium/presenters/settings/query' -describe Compendium::Presenters::Settings::Query do +RSpec.describe Compendium::Presenters::Settings::Query do subject { described_class.new } describe '#update' do before { subject.foo = :bar } - it 'should override previous settings' do + it 'overrides previous settings' do subject.update do |s| s.foo :quux end @@ -15,7 +14,7 @@ expect(subject.foo).to eq(:quux) end - it 'should allow the block parameter to be skipped' do + it 'allows the block parameter to be skipped' do subject.update do foo :quux end diff --git a/spec/compendium/presenters/settings/table_spec.rb b/spec/compendium/presenters/settings/table_spec.rb index ffd49a2..07e8bbb 100644 --- a/spec/compendium/presenters/settings/table_spec.rb +++ b/spec/compendium/presenters/settings/table_spec.rb @@ -1,7 +1,6 @@ -require 'spec_helper' require 'compendium/presenters/table' -describe Compendium::Presenters::Settings::Table do +RSpec.describe Compendium::Presenters::Settings::Table do let(:results) { double('Results', records: [{ one: 1, two: 2 }, { one: 3, two: 4 }], keys: [:one, :two]) } let(:query) { double('Query', results: results, options: {}, table_settings: nil) } let(:table) { Compendium::Presenters::Table.new(nil, query) } @@ -9,7 +8,7 @@ subject { table.settings } context 'default settings' do - it 'should return default values' do + it 'returns the default values' do expect(subject.number_format).to eq '%0.2f' expect(subject.table_class).to eq('results') expect(subject.header_class).to eq('headings') @@ -30,7 +29,7 @@ end end - it 'should have overriden settings' do + it 'has overriden settings' do expect(subject.number_format).to eq('%0.1f') expect(subject.table_class).to eq('report_table') expect(subject.header_class).to eq('report_heading') @@ -39,7 +38,7 @@ end describe '#update' do - it 'should override previous settings' do + it 'overrides previous settings' do subject.update do |s| s.number_format '%0.3f' end @@ -49,30 +48,30 @@ end describe '#skip_total_for' do - it 'should add columns to the setting' do + it 'adds columns to the setting' do subject.skip_total_for :foo, :bar expect(subject.skipped_total_cols).to eq([:foo, :bar]) end - it 'should be callable multiple times' do + it 'is callable multiple times' do subject.skip_total_for :foo, :bar subject.skip_total_for :quux expect(subject.skipped_total_cols).to eq(%i(foo bar quux)) end - it 'should not care about type' do + it 'does not care about type' do subject.skip_total_for 'foo' expect(subject.skipped_total_cols).to eq([:foo]) end end describe '#override_heading' do - it 'should override a given heading' do + it 'overrides the given heading' do subject.override_heading :one, 'First Column' expect(subject.headings).to eq('one' => 'First Column', 'two' => :two) end - it 'should override multiple headings with a block' do + it 'overrides multiple headings with a block' do subject.override_heading do |col| col.to_s * 2 end diff --git a/spec/compendium/presenters/table_spec.rb b/spec/compendium/presenters/table_spec.rb index 0cf2e4c..34d7603 100644 --- a/spec/compendium/presenters/table_spec.rb +++ b/spec/compendium/presenters/table_spec.rb @@ -1,7 +1,6 @@ -require 'spec_helper' require 'compendium/presenters/table' -describe Compendium::Presenters::Table do +RSpec.describe Compendium::Presenters::Table do let(:template) { double('Template') } let(:results) { double('Results', records: [{ one: 1, two: 2 }, { one: 3, two: 4 }], keys: [:one, :two]) } let(:query) { double('Query', results: results, options: {}, table_settings: nil) } @@ -13,66 +12,66 @@ allow(I18n).to receive(:t) { |key| key } end - it 'should use the table class given in settings' do + it 'uses the table class given in settings' do table.settings.table_class 'report_table' expect(template).to receive(:content_tag).with(:table, class: 'report_table') table.render end - it 'should use the default table class if not overridden' do + it 'uses the default table class if not specified' do expect(template).to receive(:content_tag).with(:table, class: 'results') table.render end - it 'should build the heading row' do + it 'builds the heading row' do expect(template).to receive(:content_tag).with(:tr, class: 'headings') expect(template).to receive(:content_tag).with(:th, :one) expect(template).to receive(:content_tag).with(:th, :two) table.render end - it 'should use the overridden heading class if given' do + it 'uses the overridden heading class if given' do table.settings.header_class 'report_header' expect(template).to receive(:content_tag).with(:tr, class: 'report_header') table.render end - it 'should build data rows' do + it 'builds data rows' do expect(template).to receive(:content_tag).with(:tr, class: 'data').twice table.render end - it 'should use the overridden row class if given' do + it 'uses the overridden row class if given' do table.settings.row_class 'report_row' expect(template).to receive(:content_tag).with(:tr, class: 'report_row').twice table.render end - it 'should add a totals row if the query has totals: true set' do + it 'adds a totals row if the query has totals: true set' do query.options[:totals] = true expect(template).to receive(:content_tag).with(:tr, class: 'totals') table.render end - it 'should not add a totals row if the query has totals: false set' do + it 'does not add a totals row if the query has totals: false set' do query.options[:totals] = false - expect(template).not_to receive(:content_tag).with(:tr, class: 'totals') + expect(template).to_not receive(:content_tag).with(:tr, class: 'totals') table.render end - it 'should not add a totals row if the query does not have :totals set' do + it 'does not add a totals row if the query does not have :totals set' do query.options.delete(:totals) - expect(template).not_to receive(:content_tag).with(:tr, class: 'totals') + expect(template).to_not receive(:content_tag).with(:tr, class: 'totals') table.render end - it 'should use the totals class if that setting is overridden' do + it 'uses the totals class if that setting is overridden' do query.options[:totals] = true table.settings.totals_class 'report_totals' diff --git a/spec/compendium/queries/collection_spec.rb b/spec/compendium/queries/collection_spec.rb index 89dd31f..866e452 100644 --- a/spec/compendium/queries/collection_spec.rb +++ b/spec/compendium/queries/collection_spec.rb @@ -1,14 +1,14 @@ -require 'spec_helper' require 'compendium/queries/collection' -describe Compendium::Queries::Collection do +RSpec.describe Compendium::Queries::Collection do let(:collection) { { one: 1, two: 2, three: 3 } } + subject { described_class.new(:collection_query, { collection: collection }, -> (_, _key, item) { [item * 2] }) } before { allow_any_instance_of(Compendium::Queries::Query).to receive(:execute_query) { |_instance, cmd| cmd } } describe '#run' do - context do + describe 'results' do before { subject.run(nil) } specify { expect(subject.results).to be_a Compendium::ResultSet } @@ -22,7 +22,7 @@ end end - it 'should not collect empty results' do + it 'does not collect empty results' do subject.proc = -> (_, _key, item) { [item] if item > 2 } subject.run(nil) expect(subject.results).to eq(three: [3]) @@ -30,31 +30,34 @@ context 'when given another query' do let(:q) { Compendium::Queries::Query.new(:q, {}, -> (*) { { one: 1, two: 2, three: 3 } }) } + subject { described_class.new(:collection, { collection: q }, -> (_, _key, item) { [item * 2] }) } before { subject.run(nil) if RSpec.current_example.metadata.fetch(:run_query, true) } specify { expect(subject.results).to eq(one: [2], two: [4], three: [6]) } - it 'should not re-run the query if it has already ran', run_query: false do + it 'does not re-run the query if it has already ran', run_query: false do q.run(nil) - expect(q).not_to receive(:run) + expect(q).to_not receive(:run) subject.run(nil) end end context 'when given a proc' do let(:proc) { -> (*) { [1, 2, 3] } } + subject { described_class.new(:collection, { collection: proc }, -> (_, _key, item) { [item * 2] }) } - it 'should use the collection from the proc' do + it 'uses the collection from the proc' do subject.run(nil) expect(subject.results).to eq(1 => [2], 2 => [4], 3 => [6]) end - context do + context 'when the query has not run' do let(:proc) { -> (*) { raise ArgumentError } } - it 'should not run the proc until runtime' do + + it 'does not run the proc until runtime' do expect { subject }.to_not raise_error end end diff --git a/spec/compendium/queries/count_spec.rb b/spec/compendium/queries/count_spec.rb index f2a6379..0110d25 100644 --- a/spec/compendium/queries/count_spec.rb +++ b/spec/compendium/queries/count_spec.rb @@ -1,4 +1,3 @@ -require 'spec_helper' require 'compendium' require 'compendium/queries/count' @@ -32,46 +31,49 @@ def count end end -describe Compendium::Queries::Count do - subject { described_class.new(:counted_query, {}, -> (*) { @counter }) } +RSpec.describe Compendium::Queries::Count do + subject { described_class.new(:counted_query, {}, -> (*) { counter }) } - it 'should have a default order' do + it 'has a default order' do expect(subject.options[:order]).to eq('COUNT(*)') expect(subject.options[:reverse]).to eq(true) end describe '#run' do - it 'should call count on the proc result' do - @counter = SingleCounter.new - expect(@counter).to receive(:count).and_return(1234) + let(:counter) { SingleCounter.new } + + it 'calls count on the proc result' do + expect(counter).to receive(:count).and_return(1234) subject.run(nil, self) end - it 'should return the count' do - @counter = SingleCounter.new + it 'returns the count' do expect(subject.run(nil, self)).to eq([1792]) end context 'when given a hash' do - before { @counter = MultipleCounter.new } + let(:counter) { MultipleCounter.new } - it 'should return a hash' do + it 'returns a hash' do expect(subject.run(nil, self)).to eq(3 => 983, 1 => 340, 2 => 204) end - it 'should be ordered in descending order' do + it 'is ordered in descending order' do expect(subject.run(nil, self).keys).to eq([3, 1, 2]) end - it 'should use the given options' do + it 'uses the given options' do subject.options[:reverse] = false expect(subject.run(nil, self).keys).to eq([2, 1, 3]) end end - it 'should raise an error if the proc does not respond to count' do - @counter = Class.new - expect { subject.run(nil, self) }.to raise_error Compendium::Queries::InvalidCommand + context 'when the proc does not respond to count' do + let(:counter) { Class.new } + + it 'raises an error if the proc does not respond to count' do + expect { subject.run(nil, self) }.to raise_error Compendium::Queries::InvalidCommand + end end end end diff --git a/spec/compendium/queries/query_spec.rb b/spec/compendium/queries/query_spec.rb index 024f0fc..1ea358f 100644 --- a/spec/compendium/queries/query_spec.rb +++ b/spec/compendium/queries/query_spec.rb @@ -1,13 +1,13 @@ -require 'spec_helper' require 'compendium/queries/query' -describe Compendium::Queries::Query do +RSpec.describe Compendium::Queries::Query do describe '#initialize' do let(:options) { double('Options', assert_valid_keys: true) } let(:proc) { double('Proc') } context 'when supplying a report' do let(:r) { Compendium::Report.new } + subject { described_class.new(r, :test, options, proc) } specify { expect(subject.report).to eq(r) } @@ -36,40 +36,40 @@ allow(query).to receive(:fetch_results) { |c| c } end - it 'should return the result of the query' do + it 'returns the result of the query' do results = query.run(nil) expect(results).to be_a Compendium::ResultSet expect(results.to_a).to eq([{ 'value' => 1 }, { 'value' => 2 }]) end - it 'should mark the query as having ran' do + it 'marks the query as having ran' do query.run(nil) expect(query).to have_run end - it 'should not affect any cloned queries' do + it 'does not affect any cloned queries' do q2 = query.clone query.run(nil) - expect(q2).not_to have_run + expect(q2).to_not have_run end - it 'should return an empty result set if running an query with no proc' do + it 'returns an empty result set if running an query with no proc' do query = described_class.new(:blank, {}, nil) expect(query.run(nil)).to be_empty end - it 'should filter the result set if a filter is provided' do + it 'filters the result set if a filter is provided' do query.add_filter(-> (data) { data.reject { |d| d[:value].odd? } }) expect(query.run(nil).to_a).to eq([{ 'value' => 2 }]) end - it 'should run multiple filters if given' do + it 'runs multiple filters if given' do query.add_filter(-> (data) { data.reject { |d| d[:value].odd? } }) query.add_filter(-> (data) { data.reject { |d| d[:value].even? } }) expect(query.run(nil)).to be_empty end - it 'should allow the result set to be a single hash when filters are present' do + it 'allows the result set to be a single hash when filters are present' do query = described_class.new(:test, {}, -> (*) { { value1: 1, value2: 2, value3: 3 } }) allow(query).to receive(:fetch_results) { |c| c } @@ -91,17 +91,17 @@ before { query.options[:order] = 'col1' } - it 'should order the query' do + it 'orders the query' do expect(cmd).to receive(:order) query.run(nil) end - it 'should not reverse the order by default' do - expect(cmd).not_to receive(:reverse_order) + it 'does not reverse the order by default' do + expect(cmd).to_not receive(:reverse_order) query.run(nil) end - it 'should reverse order if the query is given reverse: true' do + it 'reverses the order if the query is given reverse: true' do query.options[:reverse] = true expect(cmd).to receive(:reverse_order) query.run(nil) @@ -119,16 +119,16 @@ before { allow_any_instance_of(described_class).to receive(:fetch_results) { |_instance, c| c } } - it 'should return its results' do + it 'returns its results' do expect(subject.run(nil)).to eq([1, 2, 3]) end - it 'should not affect the report' do + it 'does not affect the report' do subject.run(nil) expect(report.queries[:test].results).to be_nil end - it 'should not affect future instances of the report' do + it 'does not affect future instances of the report' do subject.run(nil) expect(report.new.queries[:test].results).to be_nil end @@ -136,26 +136,27 @@ end describe '#nil?' do - it "should return true if the query's proc is nil" do - expect(Compendium::Queries::Query.new(:test, {}, nil)).to be_nil + it "returns true if the query's proc is nil" do + expect(described_class.new(:test, {}, nil)).to be_nil end - it "should return false if the query's proc is not nil" do - expect(Compendium::Queries::Query.new(:test, {}, -> {})).not_to be_nil + it "returns false if the query's proc is not nil" do + expect(described_class.new(:test, {}, -> {})).to_not be_nil end end describe '#render_chart' do let(:template) { double('Template') } + subject { described_class.new(:test, {}, -> (*) {}) } - it 'should initialize a new Chart presenter if the query has no results' do + it 'initializes a new Chart presenter if the query has no results' do allow(subject).to receive_messages(empty?: true) expect(Compendium::Presenters::Chart).to receive(:new).with(template, subject).and_return(double('Presenter').as_null_object) subject.render_chart(template) end - it 'should initialize a new Chart presenter if the query has results' do + it 'initializes a new Chart presenter if the query has results' do allow(subject).to receive_messages(empty?: false) expect(Compendium::Presenters::Chart).to receive(:new).with(template, subject).and_return(double('Presenter').as_null_object) subject.render_chart(template) @@ -164,14 +165,15 @@ describe '#render_table' do let(:template) { double('Template') } + subject { described_class.new(:test, {}, -> (*) {}) } - it 'should return nil if the query has no results' do + it 'returns nil if the query has no results' do allow(subject).to receive_messages(empty?: true) expect(subject.render_table(template)).to be_nil end - it 'should initialize a new Table presenter if the query has results' do + it 'initializes a new Table presenter if the query has results' do allow(subject).to receive_messages(empty?: false) expect(Compendium::Presenters::Table).to receive(:new).with(template, subject).and_return(double('Presenter').as_null_object) subject.render_table(template) @@ -180,10 +182,12 @@ describe '#url' do let(:report) { double('Report') } + subject { described_class.new(:test, {}, -> {}) } + before { subject.report = report } - it "should build a URL using its report's URL" do + it "builds a URL using its report's URL" do expect(report).to receive(:url).with(query: :test) subject.url end diff --git a/spec/compendium/queries/sum_spec.rb b/spec/compendium/queries/sum_spec.rb index d7ae728..76b67df 100644 --- a/spec/compendium/queries/sum_spec.rb +++ b/spec/compendium/queries/sum_spec.rb @@ -1,4 +1,3 @@ -require 'spec_helper' require 'compendium/queries/sum' require 'compendium/report' @@ -32,46 +31,49 @@ def sum(*) end end -describe Compendium::Queries::Sum do - subject { described_class.new(:counted_query, :col, { sum: :col }, -> (*) { @counter }) } +RSpec.describe Compendium::Queries::Sum do + subject { described_class.new(:counted_query, :col, { sum: :col }, -> (*) { summer }) } - it 'should have a default order' do + it 'has a default order' do expect(subject.options[:order]).to eq('SUM(col)') expect(subject.options[:reverse]).to eq(true) end describe '#run' do - it 'should call sum on the proc result' do - @counter = SingleSummer.new - expect(@counter).to receive(:sum).with(:col).and_return(1234) + let(:summer) { SingleSummer.new } + + it 'calls sum on the proc result' do + expect(summer).to receive(:sum).with(:col).and_return(1234) subject.run(nil, self) end - it 'should return the sum' do - @counter = SingleSummer.new + it 'returns the sum' do expect(subject.run(nil, self)).to eq([1792]) end context 'when given a hash' do - before { @counter = MultipleSummer.new } + let(:summer) { MultipleSummer.new } - it 'should return a hash if given' do + it 'returns a hash if given' do expect(subject.run(nil, self)).to eq(3 => 983, 1 => 340, 2 => 204) end - it 'should be ordered in descending order' do + it 'is ordered in descending order' do expect(subject.run(nil, self).keys).to eq([3, 1, 2]) end - it 'should use the given options' do + it 'uses the given options' do subject.options[:reverse] = false expect(subject.run(nil, self).keys).to eq([2, 1, 3]) end end - it 'should raise an error if the proc does not respond to sum' do - @counter = Class.new - expect { subject.run(nil, self) }.to raise_error Compendium::Queries::InvalidCommand + context 'when the proc does not respond to sum' do + let(:summer) { Class.new } + + it 'raises an error if the proc does not respond to sum' do + expect { subject.run(nil, self) }.to raise_error Compendium::Queries::InvalidCommand + end end end end diff --git a/spec/compendium/queries/through_spec.rb b/spec/compendium/queries/through_spec.rb index b62f767..6ad1fff 100644 --- a/spec/compendium/queries/through_spec.rb +++ b/spec/compendium/queries/through_spec.rb @@ -1,6 +1,6 @@ require 'compendium/queries/through' -describe Compendium::Queries::Through do +RSpec.describe Compendium::Queries::Through do describe '#initialize' do let(:options) { double('Options', assert_valid_keys: true) } let(:proc) { double('Proc') } @@ -8,6 +8,7 @@ context 'when supplying a report' do let(:r) { Compendium::Report.new } + subject { described_class.new(r, :test, through, options, proc) } specify { expect(subject.report).to eq(r) } @@ -35,25 +36,25 @@ before { allow(parent3).to receive(:execute_query) { |cmd| cmd } } - it 'should pass along the params if the proc collects it' do + it 'passes along the params if the proc collects it' do params = { one: 1, two: 2 } q = described_class.new(:through, parent3, {}, -> (_r, p) { p }) expect(q.run(params)).to eq(params) end - it 'should pass along the params if the proc has a splat argument' do + it 'passes along the params if the proc has a splat argument' do params = { one: 1, two: 2 } q = described_class.new(:through, parent3, {}, -> (*args) { args }) expect(q.run(params)).to eq([[[1, 2, 3]], params.with_indifferent_access]) end - it "should not pass along the params if the proc doesn't collects it" do + it "does not pass along the params if the proc doesn't collects it" do params = { one: 1, two: 2 } q = described_class.new(:through, parent3, {}, -> (r) { r }) expect(q.run(params)).to eq([[1, 2, 3]]) end - it 'should not affect its parent query' do + it 'does not affect its parent query' do q = described_class.new(:through, parent3, {}, -> (r) { r.map! { |i| i * 2 } }) expect(q.run(nil)).to eq([[1, 2, 3, 1, 2, 3]]) expect(parent3.results).to eq([[1, 2, 3]]) @@ -62,7 +63,7 @@ context 'with a single parent' do subject { described_class.new(:sub, parent1, {}, -> (r) { r.first }) } - it 'should not try to run a through query if the parent query has no results' do + it 'does not try to run a through query if the parent query has no results' do expect { subject.run(nil) }.to_not raise_error expect(subject.results).to be_empty end @@ -71,12 +72,12 @@ context 'with multiple parents' do subject { described_class.new(:sub, [parent1, parent2], {}, -> (r) { r.first }) } - it 'should not try to run a through query with multiple parents all of which have no results' do + it 'does not try to run a through query with multiple parents all of which have no results' do expect { subject.run(nil) }.to_not raise_error expect(subject.results).to be_empty end - it 'should allow non blank queries' do + it 'allows non blank queries' do subject.through = parent3 subject.run(nil) expect(subject.results).to eq([1, 2, 3]) diff --git a/spec/compendium/report_spec.rb b/spec/compendium/report_spec.rb index 7f10afc..5b9000a 100644 --- a/spec/compendium/report_spec.rb +++ b/spec/compendium/report_spec.rb @@ -1,13 +1,12 @@ -require 'spec_helper' require 'compendium/queries' -describe Compendium::Report do +RSpec.describe Compendium::Report do subject { described_class } specify { expect(subject.queries).to be_empty } specify { expect(subject.options).to be_empty } - it 'should not do anything when run' do + it 'does not do anything when run' do report = subject.new report.run expect(report.results).to be_empty @@ -20,9 +19,9 @@ metric :test_metric, -> {}, through: :test end end + let(:report2) { report_class.new } subject { report_class.new } - let(:report2) { report_class.new } specify { expect(subject.queries).to_not equal report2.queries } specify { expect(subject.queries).to_not equal report_class.queries } @@ -31,6 +30,7 @@ describe '.report_name' do subject { TestReport = Class.new(described_class) } + specify { expect(subject.report_name).to eq(:test) } end @@ -50,9 +50,9 @@ metric(:implicit_metric) { [1, 2, 3].count } end end + let!(:report2) { report_class.new } subject { report_class.new(first: '2010-10-10', second: '2011-11-11') } - let!(:report2) { report_class.new } before do allow_any_instance_of(Compendium::Queries::Query).to receive(:fetch_results) { |_instance, c| c } @@ -61,28 +61,28 @@ specify { expect(subject.test_results.records).to eq [Date.new(2010, 10, 10), Date.new(2011, 11, 11)] } - it 'should allow metric results to be accessed through a query' do + it 'allows metric results to be accessed through a query' do expect(subject.test.metrics[:lambda_metric].result).to eq(Date.new(2011, 11, 11)) end - it 'should run its metrics defined as a lambda' do + it 'runs its metrics defined as a lambda' do expect(subject.metrics[:lambda_metric].result).to eq(Date.new(2011, 11, 11)) end - it 'should run its metrics defined as a block' do + it 'runs its metrics defined as a block' do expect(subject.metrics[:block_metric].result).to eq(Date.new(2011, 11, 11)) end - it 'should run its implicit metrics' do + it 'runs its implicit metrics' do expect(subject.metrics[:implicit_metric].result).to eq(3) end - it 'should not affect other instances of the report class' do + it 'does not affect other instances of the report class' do expect(report2.test.results).to be_nil expect(report2.metrics[:lambda_metric].result).to be_nil end - it 'should not affect the class collections' do + it 'does not affect the class collections' do expect(report_class.test.results).to be_nil end @@ -99,11 +99,11 @@ specify { expect(subject.through.results).to eq([100]) } - it "should not mark other instances' queries as ran" do - expect(report2.test).not_to have_run + it "does not mark other instances' queries as ran" do + expect(report2.test).to_not have_run end - it 'should not affect other instances' do + it 'does not affect other instances' do report2.queries.each { |q| allow(q).to receive(:fetch_results) { |c| c } } report2.run expect(report2.through.results).to eq([1600]) @@ -121,73 +121,77 @@ subject { report_class.new } - it 'should raise an error if given :only and :except options' do + it 'raises an error if given both :only and :except options' do expect { subject.run(nil, only: :first, except: :second) }.to raise_error(ArgumentError) end - it 'should raise an error if given an invalid query name' do + it 'raises an error if given an invalid query name' do expect { subject.run(nil, only: :foo) }.to raise_error(ArgumentError) end - it 'should run all queries if nothing is specified' do + it 'runs all queries if nothing is specified' do subject.run(nil) expect(subject.first).to have_run expect(subject.second).to have_run end - it 'should only run queries specified by :only' do - subject.run(nil, only: :first) - expect(subject.first).to have_run - expect(subject.second).not_to have_run - end + context 'when :only is given' do + it 'only run specified queries' do + subject.run(nil, only: :first) + expect(subject.first).to have_run + expect(subject.second).to_not have_run + end - it 'should allow multiple queries to be specified by :only' do - report_class.query(:third) {} - subject.run(nil, only: [:first, :third]) - expect(subject.first).to have_run - expect(subject.second).not_to have_run - expect(subject.third).to have_run - end + it 'allows multiple queries to be specified' do + report_class.query(:third) {} + subject.run(nil, only: [:first, :third]) + expect(subject.first).to have_run + expect(subject.second).to_not have_run + expect(subject.third).to have_run + end - it 'should not run through queries related to a query specified by only if not also specified' do - report_class.query(:through, through: :first) {} - subject.run(nil, only: :first) - expect(subject.through).not_to have_run - end + it 'does not run through queries related to a query specified by only if not also specified' do + report_class.query(:through, through: :first) {} + subject.run(nil, only: :first) + expect(subject.through).to_not have_run + end - it 'should run through queries related to a query specified by only if also specified' do - report_class.query(:through, through: :first) {} - subject.run(nil, only: [:first, :through]) - expect(subject.through).to have_run + it 'runs through queries related to a query specified by only if also specified' do + report_class.query(:through, through: :first) {} + subject.run(nil, only: [:first, :through]) + expect(subject.through).to have_run + end end - it 'should not run queries specified by :except' do - subject.run(nil, except: :first) - expect(subject.first).not_to have_run - expect(subject.second).to have_run - end + context 'when :except is given' do + it 'does run queries specified by :except' do + subject.run(nil, except: :first) + expect(subject.first).to_not have_run + expect(subject.second).to have_run + end - it 'should allow multiple queries to be specified by :except' do - report_class.query(:third) {} - subject.run(nil, except: [:first, :third]) - expect(subject.first).not_to have_run - expect(subject.second).to have_run - expect(subject.third).not_to have_run - end + it 'allows multiple queries to be specified by :except' do + report_class.query(:third) {} + subject.run(nil, except: [:first, :third]) + expect(subject.first).to_not have_run + expect(subject.second).to have_run + expect(subject.third).to_not have_run + end - it 'should not run through queries excepted related to a query even if the main query is not excepted' do - report_class.query(:through, through: :first) {} - subject.run(nil, except: :through) - expect(subject.through).not_to have_run - expect(subject.first).to have_run + it 'does not run through queries related to a skipped query even if the main query is not excepted' do + report_class.query(:through, through: :first) {} + subject.run(nil, except: :through) + expect(subject.through).to_not have_run + expect(subject.first).to have_run + end end end end context 'class name predicates' do before do - OneReport = Class.new(Compendium::Report) - TwoReport = Class.new(Compendium::Report) + OneReport = Class.new(described_class) + TwoReport = Class.new(described_class) ThreeReport = Class.new end @@ -199,10 +203,10 @@ it { is_expected.to respond_to(:one?) } it { is_expected.to respond_to(:two?) } - it { is_expected.not_to respond_to(:three?) } + it { is_expected.to_not respond_to(:three?) } - it { is_expected.not_to be_one } - it { is_expected.not_to be_two } + it { is_expected.to_not be_one } + it { is_expected.to_not be_two } specify { expect(OneReport).to be_one } specify { expect(TwoReport).to be_two } @@ -212,13 +216,13 @@ let(:report_class) { Class.new(subject) } let(:report_class2) { Class.new(report_class) } - it 'should include ancestors params' do + it 'includes ancestors params' do expect(report_class.params_class.ancestors).to include subject.params_class end - it 'should inherit validations' do + it 'inherits validations' do report_class.params_class.validates :foo, presence: true - expect(report_class2.params_class.validators_on(:foo)).not_to be_nil + expect(report_class2.params_class.validators_on(:foo)).to_not be_nil end end @@ -230,14 +234,14 @@ end end - it 'should return true if there are no validation failures' do + it 'returns true if there are no validation failures' do r = report_class.new(id: 5) expect(r).to be_valid end - it 'should return false if there are validation failures' do + it 'returns false if there are validation failures' do r = report_class.new(id: nil) - expect(r).not_to be_valid + expect(r).to_not be_valid expect(r.errors.keys).to include :id end end @@ -253,14 +257,14 @@ end end - it 'should return true if there are no validation failures' do + it 'returns true if there are no validation failures' do r = report_class.new(number: 4) expect(r).to be_valid end - it 'should return false if there are validation failures' do + it 'returns false if there are validation failures' do r = report_class.new(number: 5) - expect(r).not_to be_valid + expect(r).to_not be_valid expect(r.errors.keys).to include :number end end @@ -284,15 +288,15 @@ let(:subclass2) { Class.new(report_class) } let(:subclass3) { Class.new(subclass1) } - it 'should add filters to the specified query' do + it 'adds filters to the specified query' do expect(subclass1.main_query.filters).to include filter_proc end - it 'should add filters by inheritence' do - expect(subclass3.main_query.filters).not_to be_empty + it 'adds filters by inheritence' do + expect(subclass3.main_query.filters).to_not be_empty end - it 'should not bleed filters from a subclass into other subclasses' do + it 'does not bleed filters from a subclass into other subclasses' do subclass1 expect(subclass2.main_query.filters).to be_empty end @@ -308,16 +312,16 @@ subject { report_class.new } - it 'should return true if there is an export for the given type' do - expect(subject.exports?(:csv)).to be_truthy + it 'returns true if there is an export for the given type' do + expect(subject).to be_exports(:csv) end - it 'should return false if there is no export for the given type explicitly' do - expect(subject.exports?(:pdf)).to be_falsey + it 'returns false if there is no export for the given type explicitly' do + expect(subject).to_not be_exports(:pdf) end - it 'should return false if there is no export for the given type implicitly' do - expect(subject.exports?(:xls)).to be_falsey + it 'returns false if there is no export for the given type implicitly' do + expect(subject).to_not be_exports(:xls) end end @@ -332,22 +336,22 @@ subject { report_class.new(foo: 123) } - it 'should return a query if given a query name' do + it 'returns a query if given a query name' do expect(subject.my_query).to eq(subject.queries[:my_query]) end - it 'should return query results if given a query_results' do + it 'returns query results if given a query_results' do subject.run expect(subject.my_query_results).to eql(subject.results[:my_query]) end - it 'should return the param value if given a param name' do + it 'returns the param value if given a param name' do expect(subject.foo).to eq(123) end - it 'should return the param truthiness if given a param predicate' do - expect(subject.foo?).to be_truthy - expect(subject.bar?).to be_falsey + it 'returns the param truthiness if given a param predicate' do + expect(subject).to be_foo + expect(subject).to_not be_bar end end @@ -361,27 +365,27 @@ subject { report_class.new } - it 'should accept the name of a query' do + it 'accepts the name of a query' do expect(subject).to respond_to :my_query end - it 'should accept the name of a query with _results' do + it 'accepts the name of a query with _results' do expect(subject).to respond_to :my_query_results end - it 'should accept the name of an option' do + it 'accepts the name of an option' do expect(subject).to respond_to :foo end - it 'should accept the name of an option as a predicate' do + it 'accepts the name of an option as a predicate' do expect(subject).to respond_to :foo? end - it 'should not accept the name of an option with _results' do + it 'does not accept the name of an option with _results' do expect(subject).to_not respond_to :foo_results end - it 'should not accept the name of a query as a predicate' do + it 'does not accept the name of a query as a predicate' do expect(subject).to_not respond_to :my_query? end end diff --git a/spec/compendium/result_set_spec.rb b/spec/compendium/result_set_spec.rb index 3e85789..fbd44ef 100644 --- a/spec/compendium/result_set_spec.rb +++ b/spec/compendium/result_set_spec.rb @@ -1,27 +1,31 @@ require 'compendium/result_set' -describe Compendium::ResultSet do +RSpec.describe Compendium::ResultSet do describe '#initialize' do subject { described_class.new(results).records } context 'when given an array' do let(:results) { [1, 2, 3] } + it { is_expected.to eq([1, 2, 3]) } end context 'when given an array of hashes' do let(:results) { [{ one: 1 }, { two: 2 }] } + it { is_expected.to eq([{ 'one' => 1 }, { 'two' => 2 }]) } specify { expect(subject.first).to be_a ActiveSupport::HashWithIndifferentAccess } end context 'when given a hash' do let(:results) { { one: 1, two: 2 } } + it { is_expected.to eq(one: 1, two: 2) } end context 'when given a scalar' do let(:results) { 3 } + it { is_expected.to eq([3]) } end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 02ca232..23180bd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,18 @@ -RSpec.configure do |rspec| - rspec.mock_with :rspec do |mocks| +require 'bundler/setup' +require 'compendium' +require 'pry' + +RSpec.configure do |config| + config.filter_run_when_matching(:focus) + config.disable_monkey_patching! + config.example_status_persistence_file_path = '.rspec_status' + + config.order = :random + Kernel.srand(config.seed) + + config.mock_with :rspec do |mocks| mocks.yield_receiver_to_any_instance_implementation_blocks = true + mocks.verify_partial_doubles = true end end From 97a7b4140dc8a2e49114aad849a8a269b7abbbef Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Wed, 15 Aug 2018 09:11:59 -0400 Subject: [PATCH 17/23] Update tests and docs for collection queries --- README.md | 10 ++++- spec/compendium/queries/collection_spec.rb | 46 ++++++++++++++-------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7d14477..31fe489 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ If validation is set up on any options, calling `valid?` on the report will vali ```ruby class MyReport < Compendium::Report - options :starting_on, :date, validates: { presence: true } + option :starting_on, :date, validates: { presence: true } end r = MyReport.new @@ -144,7 +144,13 @@ end #### Collection Queries -Sometimes you'll want to run a collection over a collection of data; for this, you can use a **collection query**. A collection query will perform the same query for each element of a hash or array, or for each result of a query. A collection is specified via `collection: [...]`, `collection: { ... }` or `collection: query` (note not a symbol but an actual query object). +Sometimes you'll want to run the same query over a collection of data; for this, you can use a **collection query**. A collection query will perform the same query for each element of a hash or array, or for each result of a query. A collection is specified via `collection:`, where the value is an array, hash, proc, or `Queries::Query`. + +```ruby +query :popular_orders, collection: popular_items do |params, id| + Orders.where(item_id: id) +end +``` ### Tying into your Rails application diff --git a/spec/compendium/queries/collection_spec.rb b/spec/compendium/queries/collection_spec.rb index 866e452..5ec3383 100644 --- a/spec/compendium/queries/collection_spec.rb +++ b/spec/compendium/queries/collection_spec.rb @@ -1,37 +1,49 @@ require 'compendium/queries/collection' RSpec.describe Compendium::Queries::Collection do - let(:collection) { { one: 1, two: 2, three: 3 } } - subject { described_class.new(:collection_query, { collection: collection }, -> (_, _key, item) { [item * 2] }) } before { allow_any_instance_of(Compendium::Queries::Query).to receive(:execute_query) { |_instance, cmd| cmd } } describe '#run' do - describe 'results' do - before { subject.run(nil) } - - specify { expect(subject.results).to be_a Compendium::ResultSet } - specify { expect(subject.results).to eq(one: [2], two: [4], three: [6]) } + context 'when given a hash' do + let(:collection) { { one: 1, two: 2, three: 3 } } - context 'when given an array instead of a hash' do - let(:collection) { [1, 2, 3] } + context 'when there are results for each element' do + before { subject.run(nil) } specify { expect(subject.results).to be_a Compendium::ResultSet } - specify { expect(subject.results).to eq(1 => [2], 2 => [4], 3 => [6]) } + specify { expect(subject.results).to eq(one: [2], two: [4], three: [6]) } + + context 'when given an array instead of a hash' do + let(:collection) { [1, 2, 3] } + + specify { expect(subject.results).to be_a Compendium::ResultSet } + specify { expect(subject.results).to eq(1 => [2], 2 => [4], 3 => [6]) } + end + end + + context 'when there are empty results' do + it 'does not collect empty results' do + subject.proc = -> (_, _key, item) { [item] if item > 2 } + subject.run(nil) + expect(subject.results).to eq(three: [3]) + end end end - it 'does not collect empty results' do - subject.proc = -> (_, _key, item) { [item] if item > 2 } - subject.run(nil) - expect(subject.results).to eq(three: [3]) + context 'when given an array' do + let(:collection) { [1, 2, 3] } + + it 'returns results' do + subject.run(nil) + expect(subject.results).to eq(1 => [2], 2 => [4], 3 => [6]) + end end context 'when given another query' do let(:q) { Compendium::Queries::Query.new(:q, {}, -> (*) { { one: 1, two: 2, three: 3 } }) } - - subject { described_class.new(:collection, { collection: q }, -> (_, _key, item) { [item * 2] }) } + let(:collection) { q } before { subject.run(nil) if RSpec.current_example.metadata.fetch(:run_query, true) } @@ -57,7 +69,7 @@ context 'when the query has not run' do let(:proc) { -> (*) { raise ArgumentError } } - it 'does not run the proc until runtime' do + it 'does not run the proc until execution time' do expect { subject }.to_not raise_error end end From 984f939cde3d1d74d2809e10dae2d41fad1e4f93 Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Wed, 15 Aug 2018 09:26:31 -0400 Subject: [PATCH 18/23] Make type required for options --- lib/compendium/dsl/option.rb | 13 +++++-------- lib/compendium/option.rb | 13 ++++++------- spec/compendium/dsl/option_spec.rb | 13 ++++++++----- spec/compendium/option_spec.rb | 15 ++++++++++++--- spec/compendium/presenters/option_spec.rb | 2 +- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/lib/compendium/dsl/option.rb b/lib/compendium/dsl/option.rb index ff86a39..eb12a3e 100644 --- a/lib/compendium/dsl/option.rb +++ b/lib/compendium/dsl/option.rb @@ -2,18 +2,15 @@ module Compendium module DSL module Option # Define a parameter for the report - def option(name, *args) - opts = args.extract_options! - type = args.shift - - add_params_validations(name, opts.delete(:validates)) + def option(name, type, default: nil, validates: nil, **opts) + add_params_validations(name, validates) if options[name] - options[name].type = type if type - options[name].default = opts.delete(:default) if opts.key?(:default) + options[name].type = type + options[name].default = default if default options[name].merge!(opts) else - options << Compendium::Option.new(opts.merge(name: name, type: type)) + options << Compendium::Option.new(opts.merge(name: name, type: type, default: default)) end end diff --git a/lib/compendium/option.rb b/lib/compendium/option.rb index 543f80c..22b878d 100644 --- a/lib/compendium/option.rb +++ b/lib/compendium/option.rb @@ -11,14 +11,13 @@ class Option delegate :boolean?, :date?, :dropdown?, :radio?, :scalar?, to: :type delegate :merge, :merge!, :[], :[]=, to: :@options - def initialize(hash = {}) - raise ArgumentError, 'name must be provided' unless hash.key?(:name) + def initialize(name:, type:, default: nil, choices: nil, **options) + @name = name.to_sym + @default = default + @choices = choices + @options = options.with_indifferent_access - @name = hash.delete(:name).to_sym - @default = hash.delete(:default) - @choices = hash.delete(:choices) - self.type = hash.delete(:type) - @options = hash.with_indifferent_access + self.type = type end def type=(type) diff --git a/spec/compendium/dsl/option_spec.rb b/spec/compendium/dsl/option_spec.rb index 3636a67..d284202 100644 --- a/spec/compendium/dsl/option_spec.rb +++ b/spec/compendium/dsl/option_spec.rb @@ -17,9 +17,8 @@ specify { expect(subject.options[:starting_on]).to be_date } it 'allows previously defined options to be redefined' do - subject.option :starting_on, :boolean - expect(subject.options[:starting_on]).to be_boolean - expect(subject.options[:starting_on]).to_not be_date + expect { subject.option :starting_on, :boolean }.to change { subject.options[:starting_on].type }. + from('date').to('boolean') end it 'allows overriding default value' do @@ -29,13 +28,13 @@ end it 'adds validations' do - subject.option :foo, validates: { presence: true } + subject.option :foo, :scalar, validates: { presence: true } expect(subject.params_class.validators_on(:foo)).to_not be_empty end it 'does not add validations if no validates option is given' do expect(subject.params_class).to_not receive :validates - subject.option :foo + subject.option :foo, :scalar end it 'does not bleed overridden options into the superclass' do @@ -44,5 +43,9 @@ r.option :new, :date expect(subject.options[:starting_on]).to be_date end + + it 'requires a type be given' do + expect { subject.option :foo }.to raise_error(ArgumentError) + end end end diff --git a/spec/compendium/option_spec.rb b/spec/compendium/option_spec.rb index 402ac6a..6bfc3fd 100644 --- a/spec/compendium/option_spec.rb +++ b/spec/compendium/option_spec.rb @@ -2,11 +2,20 @@ RSpec.describe Compendium::Option do it 'raises an ArgumentError if no name is given' do - expect { described_class.new }.to raise_error ArgumentError, 'name must be provided' + expect { described_class.new }.to raise_error ArgumentError + end + + it 'raises an ArgumentError if no type is given' do + expect { described_class.new(name: 'foo').type }.to raise_error ArgumentError end it 'sets up type predicates from the type option' do - o = described_class.new(name: :option, type: :date) - expect(o).to be_date + option = described_class.new(name: :option, type: :date) + expect(option).to be_date + end + + it 'sets options if given' do + option = described_class.new(name: :option, type: :scalar, foo: 1, bar: 2, baz: 3) + expect(option.options).to match(foo: 1, bar: 2, baz: 3) end end diff --git a/spec/compendium/presenters/option_spec.rb b/spec/compendium/presenters/option_spec.rb index 35223fe..17595fb 100644 --- a/spec/compendium/presenters/option_spec.rb +++ b/spec/compendium/presenters/option_spec.rb @@ -10,7 +10,7 @@ let(:form) { double('Form') } let(:ctx) { double('Context') } - let(:option) { Compendium::Option.new(name: :test_option) } + let(:option) { Compendium::Option.new(name: :test_option, type: :scalar) } subject { described_class.new(template, option) } From 5eb3b105642a6e45e869b60a7a04cfa4550976ea Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Thu, 16 Aug 2018 12:32:05 -0400 Subject: [PATCH 19/23] Use rubocop_defaults --- .rubocop.yml | 112 ++++----------------------------------------------- Gemfile | 4 ++ 2 files changed, 12 insertions(+), 104 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index da38d0e..9368567 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,114 +1,32 @@ -inherit_from: .rubocop_todo.yml -require: rubocop-rspec - -Layout/AccessModifierIndentation: - EnforcedStyle: outdent - -Layout/CaseIndentation: - EnforcedStyle: end - IndentOneStep: true - -Layout/ElseAlignment: - Enabled: false +AllCops: + TargetRubyVersion: 2.4 -Layout/EndAlignment: - EnforcedStyleAlignWith: variable +inherit_gem: + rubocop_defaults: .rubocop.yml -Layout/SpaceBeforeBlockBraces: - EnforcedStyle: space - EnforcedStyleForEmptyBraces: space - -Layout/SpaceInLambdaLiteral: - EnforcedStyle: require_space - -Layout/SpaceInsideArrayLiteralBrackets: - EnforcedStyle: no_space +inherit_from: .rubocop_todo.yml -Layout/SpaceInsideBlockBraces: - EnforcedStyle: space - EnforcedStyleForEmptyBraces: no_space - SpaceBeforeBlockParameters: true +inherit_mode: + merge: + - Exclude Lint/UnusedMethodArgument: Exclude: - 'lib/compendium/abstract_chart_provider.rb' -Metrics/BlockLength: - Exclude: - - spec/**/* - Max: 25 - -Metrics/ClassLength: - Max: 100 - -Metrics/ModuleLength: - Max: 100 - -Metrics/LineLength: - Max: 150 - -RSpec/ContextWording: - Enabled: false - RSpec/ExampleLength: Max: 10 -RSpec/ExpectChange: - EnforcedStyle: block - -RSpec/LeadingSubject: - Enabled: false - -RSpec/MessageSpies: - EnforcedStyle: receive - -RSpec/MultipleExpectations: - Enabled: false - -RSpec/NamedSubject: - Enabled: false - -RSpec/NestedGroups: - Max: 5 - -RSpec/NotToNot: - EnforcedStyle: to_not - -RSpec/SubjectStub: - Enabled: false - -RSpec/VerifiedDoubles: - Enabled: false - -Style/Alias: - EnforcedStyle: prefer_alias_method - -Style/BarePercentLiterals: - EnforcedStyle: percent_q - -Style/BlockDelimiters: - EnforcedStyle: line_count_based - -Style/BracesAroundHashParameters: - EnforcedStyle: context_dependent - Style/DateTime: Exclude: - 'spec/compendium/param_types/date_spec.rb' -Style/Documentation: - Enabled: false - Style/FormatString: EnforcedStyle: sprintf Style/FormatStringToken: EnforcedStyle: unannotated -Style/HashSyntax: - Exclude: - - 'Rakefile' - Style/IfUnlessModifier: Exclude: - 'Gemfile' @@ -117,22 +35,8 @@ Style/MethodMissingSuper: Exclude: - 'lib/compendium/presenters/settings/query.rb' -Style/PercentLiteralDelimiters: - PreferredDelimiters: - default: '()' - '%i': '()' - '%I': '()' - '%w': '()' - '%W': '()' - -Style/RegexpLiteral: - EnforcedStyle: mixed - Style/RescueModifier: Enabled: false -Style/StabbyLambdaParentheses: - EnforcedStyle: require_parentheses - Style/SymbolArray: MinSize: 3 diff --git a/Gemfile b/Gemfile index 550dd53..904b7a8 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,10 @@ source 'https://rubygems.org' # Specify your gem's dependencies in compendium.gemspec gemspec +group :development do + gem 'rubocop_defaults', git: 'https://github.com/dvandersluis/rubocop_defaults.git' +end + if RUBY_VERSION >= '2.4' gem 'json', github: 'flori/json', branch: 'v1.8' end From 984bab97aa58ec6596ce635472eecefb59f03214 Mon Sep 17 00:00:00 2001 From: jesster2k10 Date: Fri, 16 Aug 2019 10:39:25 +0100 Subject: [PATCH 20/23] Remove alias_method_chain for rails 5+ support --- .../rails/active_record/connection_adapters/quoting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/rails/active_record/connection_adapters/quoting.rb b/config/initializers/rails/active_record/connection_adapters/quoting.rb index dbaabfe..2eb0658 100644 --- a/config/initializers/rails/active_record/connection_adapters/quoting.rb +++ b/config/initializers/rails/active_record/connection_adapters/quoting.rb @@ -12,7 +12,7 @@ def quote_with_simple_delegator(value, column = nil) quote_without_simple_delegator(value, column) end - alias_method_chain :quote, :simple_delegator + prepend :quote, :simple_delegator end end end From 930b2c182bd6c4edd3da032daf53c7e71075e303 Mon Sep 17 00:00:00 2001 From: jesster2k10 Date: Fri, 16 Aug 2019 10:41:11 +0100 Subject: [PATCH 21/23] Update compendium.gemspec --- compendium.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compendium.gemspec b/compendium.gemspec index ecd3f3e..e8925ab 100644 --- a/compendium.gemspec +++ b/compendium.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |gem| gem.add_dependency 'collection_of', '1.0.6' gem.add_dependency 'compass-rails', '>= 1.0.0' gem.add_dependency 'inheritable_attr', '>= 1.0.0' - gem.add_dependency 'rails', '>= 3.0.0', '< 4' + gem.add_dependency 'rails', '>= 5.1.0' gem.add_dependency 'sass-rails', '>= 3.0.0' gem.add_development_dependency 'pry' From fbcbef309305c627325a489d53476ff376f780a8 Mon Sep 17 00:00:00 2001 From: jesster2k10 Date: Fri, 16 Aug 2019 10:48:18 +0100 Subject: [PATCH 22/23] Rails 5 support --- .../active_record/connection_adapters/quoting.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/config/initializers/rails/active_record/connection_adapters/quoting.rb b/config/initializers/rails/active_record/connection_adapters/quoting.rb index 2eb0658..46f228c 100644 --- a/config/initializers/rails/active_record/connection_adapters/quoting.rb +++ b/config/initializers/rails/active_record/connection_adapters/quoting.rb @@ -3,16 +3,18 @@ # crash. # Override AR::ConnectionAdapters::Quoting to forward a SimpleDelegator's object to be quoted. +module QuoteWithSimpleDelegator + def quote(value, column = nil) + return value.quoted_id if value.respond_to?(:quoted_id) + value = value.__getobj__ if value.is_a?(SimpleDelegator) + super + end +end + module ActiveRecord module ConnectionAdapters module Quoting - def quote_with_simple_delegator(value, column = nil) - return value.quoted_id if value.respond_to?(:quoted_id) - value = value.__getobj__ if value.is_a?(SimpleDelegator) - quote_without_simple_delegator(value, column) - end - - prepend :quote, :simple_delegator + prepend QuoteWithSimpleDelegator end end end From 5dd8fcb583db23ce82eca5c9a59634375e3ea578 Mon Sep 17 00:00:00 2001 From: Jesse Onolememen Date: Fri, 16 Aug 2019 19:53:31 +0100 Subject: [PATCH 23/23] Fix rails 5+ error --- compendium.gemspec | 2 +- lib/compendium/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compendium.gemspec b/compendium.gemspec index e8925ab..d97e37a 100644 --- a/compendium.gemspec +++ b/compendium.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |gem| gem.add_dependency 'sass-rails', '>= 3.0.0' gem.add_development_dependency 'pry' - gem.add_development_dependency 'rake', '> 11.0.1', '< 12' + gem.add_development_dependency 'rake', '> 11.0.1', '< 12.3.3' gem.add_development_dependency 'rspec', '~> 3.8.0' gem.add_development_dependency 'rubocop', '~> 0.58' gem.add_development_dependency 'rubocop-rspec', '~> 1.28' diff --git a/lib/compendium/version.rb b/lib/compendium/version.rb index 1e24088..cabce79 100644 --- a/lib/compendium/version.rb +++ b/lib/compendium/version.rb @@ -1,3 +1,3 @@ module Compendium - VERSION = '1.2.2'.freeze + VERSION = '1.3.0'.freeze end