timeframeable 0.0.1 → 0.1.0

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