timeframeable 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -19,21 +19,52 @@ Or install it yourself as:
19
19
 
20
20
  ## Usage
21
21
 
22
- Inject dates from ```params[:start]``` and ```params[:end]``` or their default values into ```@timeframe.start``` and ```@timeframe.end```:
22
+ ### Controller module
23
23
 
24
- timeframeable :defaults => [:beginning_of_month, :now]
24
+ Typical usage:
25
25
 
26
- Inject dates from ```params[:s]``` and ```params[:e]``` or their default values into ```@timeframe.start``` and ```@timeframe.end```:
26
+ ```ruby
27
+ class FrontController < ApplicationController
28
+ include Timeframeable::Controller
29
+ timeframeable
30
+ ```
27
31
 
28
- timeframeable :defaults => [:beginning_of_month, :now], :start_key => :s, :end_key => :e
32
+ This will inject dates from `params[:start]` and `params[:end]` into `@timeframe.start` and `@timeframe.end`. `nil` will be used if parameter was not found. You can specify default values to avoid this:
29
33
 
30
- Inject dates from ```params[:start]``` and ```params[:end]``` or their default values into ```@timeframe.start``` and ```@timeframe.end```, and inject dates from ```params[:s]``` and ```params[:e]``` or their default values into ```@tf.start``` and ```@tf.end```:
34
+ ```ruby
35
+ timeframeable :defaults => [:beginning_of_month, :now]
36
+ ```
31
37
 
32
- timeframeable :defaults => [:beginning_of_month, :now]
33
- timeframeable :defaults => [:beginning_of_month, :now], :start_key => :s, :end_key => :e, :variable => :'@tf'
38
+ You can specify keys to seek at params hash:
39
+
40
+ ```ruby
41
+ timeframeable :defaults => [:beginning_of_month, :now],
42
+ :start_key => :s,
43
+ :end_key => :e
44
+ ```
45
+
46
+ Or even specify instance variable to fill:
47
+
48
+ ```ruby
49
+ timeframeable :defaults => [:beginning_of_month, :now],
50
+ :start_key => :s,
51
+ :end_key => :e,
52
+ :variable => :tf
53
+ ```
54
+
55
+ ### ActiveRecord module
56
+
57
+ ```ruby
58
+ class Model < ActiveRecord::Base
59
+ include Timeframeable::Scope
60
+ end
61
+
62
+ Model.timeframe(:created_at, @timeframe).count
63
+ ```
34
64
 
35
65
  ## TODO
36
- If there will be such a need, it may be usefull to add ```:start_value``` and ```:end_value``` lambda options, which will extract appropriate values from ```params``` bypassing ```:start_key``` and ```:end_key```.
66
+
67
+ It might be usefull to add `:start_value` and `:end_value` lambda options, which will extract appropriate values from `params` bypassing `:start_key` and `:end_key`.
37
68
 
38
69
  ## Contributing
39
70
 
@@ -6,40 +6,13 @@ require 'active_support/core_ext/date/conversions'
6
6
  require 'active_support/core_ext/time/calculations'
7
7
  require 'active_support/core_ext/date_time/calculations'
8
8
  require 'active_support/core_ext/date_time/conversions'
9
+ require 'timeframeable/controller'
10
+ require 'timeframeable/scope'
9
11
 
10
12
  module Timeframeable
11
- extend ActiveSupport::Concern
12
13
 
13
14
  Timeframe = Struct.new(:start, :end)
14
15
 
15
- module ClassMethods
16
- def timeframeable(options={})
17
- raise 'Timeframe defaults not defined' if options[:defaults].blank?
18
- raise 'Invalid Timeframe defaults' unless options[:defaults].size == 2
19
-
20
- options = options.dup
21
-
22
- options[:start_key] ||= :start
23
- options[:end_key] ||= :end
24
- options[:variable] ||= :'@timeframe'
25
- options[:defaults] = options[:defaults].map do |x|
26
- if x.is_a? Symbol
27
- if x == :now
28
- DateTime.now
29
- else
30
- DateTime.now.send(x)
31
- end
32
- else
33
- x
34
- end
35
- end
36
-
37
- before_filter do
38
- set_timeframe options
39
- end
40
- end
41
- end
42
-
43
16
  def self.parse_date(param, extrapolate=nil)
44
17
  return unless param
45
18
 
@@ -72,21 +45,13 @@ module Timeframeable
72
45
  else
73
46
  end
74
47
 
75
- result
48
+ result.utc
76
49
  else
77
50
  begin
78
- DateTime.parse(param.to_s)
51
+ DateTime.parse(param.to_s).utc
79
52
  rescue
80
53
  return
81
54
  end
82
55
  end
83
56
  end
84
-
85
- private
86
-
87
- def set_timeframe(options)
88
- start_date = Timeframeable.parse_date(params[options[:start_key]]) || options[:defaults][0].to_datetime
89
- end_date = Timeframeable.parse_date(params[options[:end_key]], :end) || options[:defaults][1].to_datetime
90
- instance_variable_set options[:variable], Timeframe.new(start_date.utc, end_date.utc)
91
- end
92
- end
57
+ end
@@ -0,0 +1,38 @@
1
+ module Timeframeable
2
+ module Controller
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def timeframeable(options={})
7
+ options = options.dup
8
+ options[:start_key] ||= :start
9
+ options[:end_key] ||= :end
10
+ options[:variable] ||= :timeframe
11
+ options[:defaults] ||= []
12
+ options[:defaults] = options[:defaults].map do |x|
13
+ if x.is_a? Symbol
14
+ if x == :now
15
+ DateTime.now.utc
16
+ else
17
+ DateTime.now.send(x).utc
18
+ end
19
+ else
20
+ x && x.to_datetime.utc
21
+ end
22
+ end
23
+
24
+ before_filter do
25
+ set_timeframe options
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def set_timeframe(options)
33
+ start_date = Timeframeable.parse_date(params[options[:start_key]]) || options[:defaults][0]
34
+ end_date = Timeframeable.parse_date(params[options[:end_key]], :end) || options[:defaults][1]
35
+ instance_variable_set :"@#{options[:variable]}", Timeframe.new(start_date, end_date)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ module Timeframeable
2
+ module Scope
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_eval do
7
+ scope :timeframe, lambda {|field, timeframe|
8
+ scope = scoped
9
+ scope = scope.where(arel_table[field].gteq(timeframe.start)) if timeframe.start
10
+ scope = scope.where(arel_table[field].lteq(timeframe.end)) if timeframe.end
11
+ scope
12
+ }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module Timeframeable
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,145 @@
1
+ require_relative '../../spec_helper'
2
+ require 'timeframeable'
3
+ require 'timecop'
4
+
5
+ describe Timeframeable::Controller do
6
+
7
+ def assert_utc(date)
8
+ case date
9
+ when DateTime
10
+ date.utc?.should be_true
11
+ when Struct
12
+ date.start.utc?.should be_true if date.start
13
+ date.end.utc?.should be_true if date.end
14
+ else
15
+ raise "Unknown object: #{date}"
16
+ end
17
+ date
18
+ end
19
+
20
+ it 'has dedicated data structure' do
21
+ date = Timeframeable::Timeframe.new(1, 2)
22
+ date.start.should == 1
23
+ date.end.should == 2
24
+ end
25
+
26
+ describe 'Timeframeable.parse_date' do
27
+
28
+ it 'uses DateTime.parse for strings' do
29
+ date = DateTime.now.change(:usec => 0)
30
+ assert_utc(Timeframeable.parse_date(date.to_s)).should == date
31
+ end
32
+
33
+ it 'suppress exceptions on invalid strings' do
34
+ Timeframeable.parse_date('trololo').should be_nil
35
+ end
36
+
37
+ it 'accepts composite param' do
38
+ assert_utc(Timeframeable.parse_date({:year => 2013, :month => 1, :day => 15})).should ==
39
+ DateTime.new(2013, 1, 15)
40
+ end
41
+
42
+ it 'accepts incomplete composite param' do
43
+ assert_utc(Timeframeable.parse_date({:year => 2013, :month => 1})).should ==
44
+ DateTime.new(2013, 1, 1)
45
+ assert_utc(Timeframeable.parse_date({:month => 1})).should ==
46
+ DateTime.new(Date.today.year, 1, 1)
47
+ end
48
+
49
+ it 'extrapolates range to its end' do
50
+ assert_utc(Timeframeable.parse_date({:year => 2013, :month => 1, :day => 15}, :end)).should ==
51
+ DateTime.new(2013, 1, 15).end_of_day
52
+ assert_utc(Timeframeable.parse_date({:year => 2013, :month => 1}, :end)).should ==
53
+ DateTime.new(2013, 1, 1).end_of_month.end_of_day
54
+ end
55
+ end
56
+
57
+ context 'controller' do
58
+ let(:controller) { Class.new.new }
59
+ let(:default_options) {
60
+ {
61
+ :start_key => :start,
62
+ :end_key => :end,
63
+ :variable => :timeframe,
64
+ :defaults => [DateTime.now.beginning_of_month, DateTime.now]
65
+ }
66
+ }
67
+
68
+ before do
69
+ controller.class.class_eval do
70
+ include Timeframeable::Controller
71
+ end
72
+ mock(controller.class).before_filter().at_least(1) {|block| controller.instance_eval(&block) }
73
+ end
74
+
75
+ describe 'Controller.timeframeable' do
76
+
77
+ it 'timeframes without options' do
78
+ default_options[:defaults] = []
79
+ mock(controller).set_timeframe(default_options)
80
+ controller.class.timeframeable
81
+ end
82
+
83
+ it 'timeframes with default options' do
84
+ options_given = {:defaults => default_options[:defaults]}
85
+ options_got = default_options
86
+ mock(controller).set_timeframe(options_got)
87
+ controller.class.timeframeable(options_given)
88
+ end
89
+
90
+ it 'timeframes with symbol options' do
91
+ Timecop.freeze(DateTime.now) do
92
+ options_given = {:defaults => [:beginning_of_month, :now]}
93
+ options_got = default_options.merge(:defaults => [DateTime.now.beginning_of_month, DateTime.now])
94
+ mock(controller).set_timeframe(options_got)
95
+ controller.class.timeframeable(options_given)
96
+ end
97
+ end
98
+
99
+ it 'timeframes with custom options' do
100
+ Timecop.freeze(DateTime.now) do
101
+ options_given = {:defaults => [:beginning_of_month, :now], :start_key => :s, :end_key => :e, :variable => :'@tf'}
102
+ options_got = default_options.merge(options_given).merge(:defaults => [DateTime.now.beginning_of_month, DateTime.now])
103
+ mock(controller).set_timeframe(options_got)
104
+ controller.class.timeframeable(options_given)
105
+ end
106
+ end
107
+ end
108
+
109
+ describe 'Controller#set_timeframe' do
110
+ it 'falls back to nils' do
111
+ stub(controller).params{ {} }
112
+ default_options[:defaults] = []
113
+ controller.class.timeframeable default_options
114
+ assert_utc(controller.instance_variable_get(:"@#{default_options[:variable]}")).should ==
115
+ Timeframeable::Timeframe.new(nil, nil)
116
+ end
117
+
118
+ it 'falls back to default values' do
119
+ stub(controller).params{ {} }
120
+ controller.class.timeframeable default_options
121
+ assert_utc(controller.instance_variable_get(:"@#{default_options[:variable]}")).should ==
122
+ Timeframeable::Timeframe.new(*default_options[:defaults])
123
+ end
124
+
125
+ it 'uses actual params' do
126
+ dates = default_options[:defaults].map{|d| d.prev_month.utc.change(:usec => 0) }
127
+ stub(controller).params{ Hash[[:start, :end].zip(dates.map(&:to_s))] }
128
+ controller.class.timeframeable default_options
129
+ assert_utc(controller.instance_variable_get(:"@#{default_options[:variable]}")).should ==
130
+ Timeframeable::Timeframe.new(*dates)
131
+ end
132
+
133
+ it 'allows multiple timeframes to be set' do
134
+ stub(controller).params{ {} }
135
+ other_options = default_options.merge(:variable => :test, :defaults => default_options[:defaults].map(&:prev_month))
136
+ controller.class.timeframeable default_options
137
+ controller.class.timeframeable other_options
138
+ assert_utc(controller.instance_variable_get(:"@#{default_options[:variable]}")).should ==
139
+ Timeframeable::Timeframe.new(*default_options[:defaults])
140
+ assert_utc(controller.instance_variable_get(:"@#{other_options[:variable]}")).should ==
141
+ Timeframeable::Timeframe.new(*other_options[:defaults])
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,35 @@
1
+ require_relative '../../spec_helper'
2
+ require 'timeframeable'
3
+ require 'active_record'
4
+
5
+ describe Timeframeable::Scope do
6
+
7
+ before :all do
8
+ module App end
9
+ ActiveRecord::Base.establish_connection(
10
+ :adapter => 'sqlite3',
11
+ :database => ':memory:'
12
+ )
13
+ end
14
+
15
+ before do
16
+ App.send(:remove_const, :Model) if App.const_defined?(:Model)
17
+ class App::Model < ActiveRecord::Base
18
+ include Timeframeable::Scope
19
+ end
20
+ end
21
+
22
+ it 'has scope that accepts timeframe' do
23
+ timeframe = Timeframeable::Timeframe.new(DateTime.parse('2013-01-01 00:00:00'), DateTime.parse('2013-01-31 23:59:59'))
24
+ timeframe_r = timeframe.clone
25
+ timeframe_r.start = nil
26
+ timeframe_l = timeframe.clone
27
+ timeframe_l.end = nil
28
+ App::Model.timeframe(:date, timeframe).to_sql.should ==
29
+ %(SELECT "models".* FROM "models" WHERE ("models"."date" >= '2013-01-01 00:00:00') AND ("models"."date" <= '2013-01-31 23:59:59'))
30
+ App::Model.timeframe(:date, timeframe_r).to_sql.should ==
31
+ %(SELECT "models".* FROM "models" WHERE ("models"."date" <= '2013-01-31 23:59:59'))
32
+ App::Model.timeframe(:date, timeframe_l).to_sql.should ==
33
+ %(SELECT "models".* FROM "models" WHERE ("models"."date" >= '2013-01-01 00:00:00'))
34
+ end
35
+ end
@@ -18,6 +18,8 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ['lib']
19
19
 
20
20
  gem.add_dependency 'activesupport'
21
+ gem.add_development_dependency 'activerecord'
22
+ gem.add_development_dependency 'sqlite3'
21
23
  gem.add_development_dependency 'rspec'
22
24
  gem.add_development_dependency 'rr'
23
25
  gem.add_development_dependency 'timecop'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timeframeable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -27,6 +27,38 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activerecord
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: sqlite3
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
30
62
  - !ruby/object:Gem::Dependency
31
63
  name: rspec
32
64
  requirement: !ruby/object:Gem::Requirement
@@ -88,8 +120,11 @@ files:
88
120
  - README.md
89
121
  - Rakefile
90
122
  - lib/timeframeable.rb
123
+ - lib/timeframeable/controller.rb
124
+ - lib/timeframeable/scope.rb
91
125
  - lib/timeframeable/version.rb
92
- - spec/lib/timeframeable_spec.rb
126
+ - spec/lib/timeframeable/controller_spec.rb
127
+ - spec/lib/timeframeable/scope_spec.rb
93
128
  - spec/spec_helper.rb
94
129
  - timeframeable.gemspec
95
130
  homepage: ''
@@ -117,6 +152,7 @@ signing_key:
117
152
  specification_version: 3
118
153
  summary: datetime range extractor
119
154
  test_files:
120
- - spec/lib/timeframeable_spec.rb
155
+ - spec/lib/timeframeable/controller_spec.rb
156
+ - spec/lib/timeframeable/scope_spec.rb
121
157
  - spec/spec_helper.rb
122
158
  has_rdoc:
@@ -1,111 +0,0 @@
1
- require_relative '../spec_helper'
2
- require 'timeframeable'
3
- require 'timecop'
4
-
5
- describe Timeframeable do
6
-
7
- it 'has dedicated data structure' do
8
- data = Timeframeable::Timeframe.new(1, 2)
9
- data.start.should == 1
10
- data.end.should == 2
11
- end
12
-
13
- describe 'Timeframeable.parse_date' do
14
-
15
- it 'uses DateTime.parse for strings' do
16
- data = DateTime.now.change(:usec => 0)
17
- Timeframeable.parse_date(data.to_s).should == data
18
- end
19
-
20
- it 'suppress exceptions on invalid strings' do
21
- Timeframeable.parse_date('trololo').should be_nil
22
- end
23
-
24
- it 'accepts composite param' do
25
- Timeframeable.parse_date({:year => 2013, :month => 1, :day => 15}).should == DateTime.new(2013, 1, 15)
26
- end
27
-
28
- it 'extrapolates range to its end' do
29
- Timeframeable.parse_date({:year => 2013, :month => 1, :day => 15}, :end).should == DateTime.new(2013, 1, 15).end_of_day
30
- Timeframeable.parse_date({:year => 2013, :month => 1}, :end).should == DateTime.new(2013, 1, 1).end_of_month.end_of_day
31
- end
32
- end
33
-
34
- context 'controller' do
35
- let(:controller) { Class.new.new }
36
- let(:default_options) {
37
- {
38
- :start_key => :start,
39
- :end_key => :end,
40
- :variable => :'@timeframe',
41
- :defaults => [DateTime.now.beginning_of_month, DateTime.now]
42
- }
43
- }
44
-
45
- before do
46
- controller.class.class_eval do
47
- include Timeframeable
48
- end
49
- end
50
-
51
- describe 'Controller.timeframeable' do
52
-
53
- before do
54
- mock(controller.class).before_filter() {|block| controller.instance_eval(&block) }
55
- end
56
-
57
- it 'timeframes with default options' do
58
- options_given = {:defaults => default_options[:defaults]}
59
- options_got = default_options
60
- mock(controller).set_timeframe(options_got)
61
- controller.class.timeframeable(options_given)
62
- end
63
-
64
- it 'timeframes with symbol options' do
65
- Timecop.freeze(DateTime.now) do
66
- options_given = {:defaults => [:beginning_of_month, :now]}
67
- options_got = default_options.merge(:defaults => [DateTime.now.beginning_of_month, DateTime.now])
68
- mock(controller).set_timeframe(options_got)
69
- controller.class.timeframeable(options_given)
70
- end
71
- end
72
-
73
- it 'timeframes with custom options' do
74
- Timecop.freeze(DateTime.now) do
75
- options_given = {:defaults => [:beginning_of_month, :now], :start_key => :s, :end_key => :e, :variable => :'@tf'}
76
- options_got = default_options.merge(options_given).merge(:defaults => [DateTime.now.beginning_of_month, DateTime.now])
77
- mock(controller).set_timeframe(options_got)
78
- controller.class.timeframeable(options_given)
79
- end
80
- end
81
- end
82
-
83
- describe 'Controller#set_timeframe' do
84
- it 'falls back to default values' do
85
- stub(controller).params{ {} }
86
- controller.send :set_timeframe, default_options
87
- controller.instance_variable_get(default_options[:variable]).should ==
88
- Timeframeable::Timeframe.new(*default_options[:defaults])
89
- end
90
-
91
- it 'uses actual params' do
92
- dates = default_options[:defaults].map{|d| d.prev_month.utc.change(:usec => 0) }
93
- stub(controller).params{ Hash[[:start, :end].zip(dates.map(&:to_s))] }
94
- controller.send :set_timeframe, default_options
95
- controller.instance_variable_get(default_options[:variable]).should ==
96
- Timeframeable::Timeframe.new(*dates)
97
- end
98
-
99
- it 'allows multiple timeframes to be set' do
100
- stub(controller).params{ {} }
101
- other_options = default_options.merge(:variable => :'@test', :defaults => default_options[:defaults].map(&:prev_month))
102
- controller.send :set_timeframe, default_options
103
- controller.send :set_timeframe, other_options
104
- controller.instance_variable_get(default_options[:variable]).should ==
105
- Timeframeable::Timeframe.new(*default_options[:defaults])
106
- controller.instance_variable_get(other_options[:variable]).should ==
107
- Timeframeable::Timeframe.new(*other_options[:defaults])
108
- end
109
- end
110
- end
111
- end