Commit 234cdc2c144326330b3a9f4e8ab319a7f4f91fce

Authored by Charles Maresh
Committed by Andrew Kane
1 parent 166605c3

Allow custom model calculation methods (#150)

* Initial implementation of custom calculation methods

* Use `#klass` to get the model

* Move test to be database specific

Custom model methods doesn't work specifically on Array objects

* Simplify test case and example

* Clean up methods to remove usage of `try`

* Avoid floats by using a count calculation

* Refactor checking of calculation methods

Break into a separate class to avoid conflicting with existing
ActiveRecord methods via `method_missing?`

* Simplify test case by removing distinct

* Add tests for alternate scenarios

- calc method not white listed
- calc method white listed but not defined

* Specify a RuntimeError for Rails 4.0 tests
Showing 2 changed files with 67 additions and 4 deletions   Show diff stats
lib/groupdate/series.rb
@@ -5,14 +5,14 @@ module Groupdate @@ -5,14 +5,14 @@ module Groupdate
5 def initialize(magic, relation) 5 def initialize(magic, relation)
6 @magic = magic 6 @magic = magic
7 @relation = relation 7 @relation = relation
  8 + @calculations = Groupdate::Calculations.new(relation)
8 end 9 end
9 10
10 # clone to prevent modifying original variables 11 # clone to prevent modifying original variables
11 def method_missing(method, *args, &block) 12 def method_missing(method, *args, &block)
12 - # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb  
13 - if ActiveRecord::Calculations.method_defined?(method) 13 + if @calculations.include?(method)
14 magic.perform(relation, method, *args, &block) 14 magic.perform(relation, method, *args, &block)
15 - elsif @relation.respond_to?(method, true) 15 + elsif relation.respond_to?(method, true)
16 Groupdate::Series.new(magic, relation.send(method, *args, &block)) 16 Groupdate::Series.new(magic, relation.send(method, *args, &block))
17 else 17 else
18 super 18 super
@@ -20,11 +20,36 @@ module Groupdate @@ -20,11 +20,36 @@ module Groupdate
20 end 20 end
21 21
22 def respond_to?(method, include_all = false) 22 def respond_to?(method, include_all = false)
23 - ActiveRecord::Calculations.method_defined?(method) || relation.respond_to?(method) || super 23 + @calculations.include?(method) || relation.respond_to?(method) || super
24 end 24 end
25 25
26 def reverse_order_value 26 def reverse_order_value
27 nil 27 nil
28 end 28 end
29 end 29 end
  30 +
  31 + class Calculations
  32 + attr_reader :relation
  33 +
  34 + def initialize(relation)
  35 + @relation = relation
  36 + end
  37 +
  38 + def include?(method)
  39 + # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb
  40 + ActiveRecord::Calculations.method_defined?(method) || custom_calculations.include?(method)
  41 + end
  42 +
  43 + def custom_calculations
  44 + return [] if !model.respond_to?(:groupdate_calculation_methods)
  45 + model.groupdate_calculation_methods
  46 + end
  47 +
  48 + private
  49 +
  50 + def model
  51 + return if !relation.respond_to?(:klass)
  52 + relation.klass
  53 + end
  54 + end
30 end 55 end
test/test_helper.rb
@@ -18,6 +18,18 @@ ActiveRecord::Base.time_zone_aware_attributes = true @@ -18,6 +18,18 @@ ActiveRecord::Base.time_zone_aware_attributes = true
18 18
19 class User < ActiveRecord::Base 19 class User < ActiveRecord::Base
20 has_many :posts 20 has_many :posts
  21 +
  22 + def self.groupdate_calculation_methods
  23 + [:custom_count, :undefined_calculation]
  24 + end
  25 +
  26 + def self.custom_count
  27 + count
  28 + end
  29 +
  30 + def self.unlisted_calculation
  31 + count
  32 + end
21 end 33 end
22 34
23 class Post < ActiveRecord::Base 35 class Post < ActiveRecord::Base
@@ -346,6 +358,32 @@ module TestDatabase @@ -346,6 +358,32 @@ module TestDatabase
346 assert_raises(ArgumentError) { User.group_by_day.first } 358 assert_raises(ArgumentError) { User.group_by_day.first }
347 end 359 end
348 360
  361 + # custom model calculation methods
  362 +
  363 + def test_custom_model_calculation_method
  364 + create_user "2014-05-01", 1
  365 + create_user "2014-05-01", 2
  366 + create_user "2014-05-03", 3
  367 +
  368 + expected = {
  369 + Date.parse("2014-05-01") => 2,
  370 + Date.parse("2014-05-02") => 0,
  371 + Date.parse("2014-05-03") => 1
  372 + }
  373 +
  374 + assert_equal expected, User.group_by_day(:created_at).custom_count
  375 + end
  376 +
  377 + def test_using_unlisted_calculation_method_returns_new_series_instance
  378 + assert_instance_of Groupdate::Series, User.group_by_day(:created_at).unlisted_calculation
  379 + end
  380 +
  381 + def test_using_listed_but_undefined_custom_calculation_method_raises_error
  382 + assert_raises(RuntimeError) do
  383 + User.group_by_day(:created_at).undefined_calculation
  384 + end
  385 + end
  386 +
349 private 387 private
350 388
351 def call_method(method, field, options) 389 def call_method(method, field, options)