by_star 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,2 +1,4 @@
1
1
  tmp
2
2
  spec/fixtures/database.yml
3
+ pkg
4
+ by_star.sqlite3
data/README.markdown CHANGED
@@ -24,6 +24,16 @@ It also allows you to do nested finds on the records returned which I personally
24
24
 
25
25
  If you're not using the standard `created_at` field: don't worry! I've covered that scenario too.
26
26
 
27
+ ## count_by* methods
28
+
29
+ `count_by` methods can be scoped to only count those records which have a specific field set, and you do this by specifying the symbol version of the name of the field, e.g;
30
+
31
+ Invoice.count_by_year(:value)
32
+
33
+ If you want to specify further arguments but do not care about the scoped field:
34
+
35
+ Invoice.count_by_year(:all, 2009)
36
+
27
37
 
28
38
  ## By Year (`by_year`)
29
39
 
@@ -37,6 +47,41 @@ This will return all posts in 2009, whereas:
37
47
 
38
48
  will return all the posts in the year 1999.
39
49
 
50
+ You can also specify the full year:
51
+
52
+ Post.by_year(2009)
53
+ Post.by_year(1999)
54
+
55
+ When you specify a year *less than* 1902 and *greater than* 2039 using specific versions of Ruby (i.e. 1.8.6p114) an `ArgumentError` will be raised. We recommend you upgrade Ruby to *at least* 1.8.7 to stop this problem occuring.
56
+
57
+ ## Sum By Year (`sum_by_year`)
58
+
59
+ To sum records for the current year based on a field:
60
+
61
+ Invoice.sum_by_year(:value)
62
+
63
+ To sum records for a year based on a field:
64
+
65
+ Invoice.sum_by_year(:value, 09)
66
+
67
+ You can also pass it a full year:
68
+
69
+ Invoice.sum_by_year(:value, 2009)
70
+
71
+ ## Count By Year (`count_by_year`)
72
+
73
+ To count the records in the current year regardless of field:
74
+
75
+ Invoice.count_by_year
76
+
77
+ To count records in the current year where only a specific field is set:
78
+
79
+ Invoice.count_by_year(:value)
80
+
81
+ To count records in a different year regardless of field:
82
+
83
+ Invoice.count_by_year(:all, :year => 2009)
84
+
40
85
  ## By Month (`by_month`)
41
86
 
42
87
  If you know the number of the month you want:
@@ -67,6 +112,49 @@ If you have a Time object you can use it to find the posts:
67
112
 
68
113
  This will find all the posts in November 2008.
69
114
 
115
+ When you specify a year *less than* 1902 and *greater than* 2039 using specific versions of Ruby (i.e. 1.8.6p114) an `ArgumentError` will be raised. We recommend you upgrade Ruby to *at least* 1.8.7 to stop this problem occuring.
116
+
117
+
118
+ ## Sum By Month (`sum_by_month`)
119
+
120
+ To sum records for the current month:
121
+
122
+ Invoice.sum_by_month
123
+
124
+ To sum records for a numbered month based on a field:
125
+
126
+ Invoice.sum_by_month(:value, 9)
127
+
128
+ You can also specify the name of the month:
129
+
130
+ Invoice.sum_by_month(:value, "September")
131
+
132
+ You can also lookup on a different year:
133
+
134
+ Invoice.sum_by_year(:value, 9, :year => "2009")
135
+
136
+ ## Count By Month (`count_by_month`)
137
+
138
+ To count records for the current month regardless of field:
139
+
140
+ Invoice.count_by_month
141
+
142
+ To count records for the current month where only a specific field is set:
143
+
144
+ Invoice.count_by_month(:value)
145
+
146
+ To count records for a different month regardless of field:
147
+
148
+ Invoice.count_by_month(:all, 9)
149
+
150
+ To count records for a different month in the current year:
151
+
152
+ Invoice.count_by_month(:number, 9)
153
+
154
+ To count records for a different month in a different year:
155
+
156
+ Invoice.count_by_month(:number, 9, :year => 2008)
157
+
70
158
  ## By Fortnight (`by_fortnight`)
71
159
 
72
160
  Fortnight numbering starts at 0. The beginning of a fortnight is Monday, 12am.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.5
1
+ 0.3.0
data/by_star.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{by_star}
8
- s.version = "0.2.5"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ryan Bigg", "Mislav Marohni\304\207"]
12
- s.date = %q{2009-10-15}
12
+ s.date = %q{2009-10-17}
13
13
  s.description = %q{ActiveRecord extension for easier date scopes and time ranges}
14
14
  s.email = %q{radarlistener@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -22,9 +22,18 @@ Gem::Specification.new do |s|
22
22
  "Rakefile",
23
23
  "VERSION",
24
24
  "by_star.gemspec",
25
+ "by_star.sqlite3",
25
26
  "lib/by_star.rb",
27
+ "lib/calculations.rb",
28
+ "lib/calculations/count.rb",
29
+ "lib/calculations/sum.rb",
30
+ "lib/range_calculations.rb",
31
+ "lib/shared.rb",
32
+ "lib/time_ext.rb",
33
+ "lib/vanilla.rb",
26
34
  "rails/init.rb",
27
35
  "spec/by_star_spec.rb",
36
+ "spec/database.yml",
28
37
  "spec/fixtures/models.rb",
29
38
  "spec/fixtures/schema.rb",
30
39
  "spec/spec_helper.rb",
data/lib/by_star.rb CHANGED
@@ -1,4 +1,10 @@
1
1
  require 'chronic'
2
+ require 'shared'
3
+ require 'range_calculations'
4
+ require 'time_ext'
5
+ require 'vanilla'
6
+ Dir[File.dirname(__FILE__) + '/calculations/*.rb'].each { |file| require file }
7
+ require 'calculations'
2
8
  module ByStar
3
9
 
4
10
  def self.included(base)
@@ -6,311 +12,12 @@ module ByStar
6
12
  end
7
13
 
8
14
  module ClassMethods
9
- # Examples:
10
- # by_year(2010)
11
- # # 2-digit year:
12
- # by_year(10)
13
- # # Time or Date object:
14
- # by_year(time)
15
- # # String:
16
- # by_year("2010")
17
- def by_year(time=Time.zone.now.year, options={}, &block)
18
- year = work_out_year(time)
19
-
20
- start_time = Time.utc(year, 1, 1)
21
- end_time = start_time.end_of_year
22
- by_star(start_time, end_time, options, &block)
23
- rescue ArgumentError
24
- raise ParseError, "Invalid arguments detected, year may possibly be outside of valid range (1902-2039). This is no longer a problem on Ruby versions > 1.8.7, so we recommend you upgrade to at least 1.8.7."
25
- end
26
-
27
- # Examples:
28
- # by_month(1)
29
- # by_month("January")
30
- # by_month("January", :year => 2008)
31
- # by_month(time)
32
- def by_month(time=Time.zone.now.month, options={}, &block)
33
- time = Time.zone.now.month if time.nil?
34
- year = options[:year] ||= Time.zone.now.year
35
- # Work out what actual month is.
36
- month = if time.is_a?(Numeric) && (1..12).include?(time)
37
- time
38
- elsif valid_time_or_date?(time)
39
- year = time.year
40
- time.month
41
- elsif time.is_a?(String) && Date::MONTHNAMES.include?(time)
42
- Date::MONTHNAMES.index(time)
43
- else
44
- raise ParseError, "Value is not an integer (between 1 and 12), time object or string (make sure you typed the name right)."
45
- end
46
-
47
- start_time = Time.utc(year, month, 1)
48
- end_time = start_time.end_of_month
49
-
50
- by_star(start_time, end_time, options, &block)
51
- end
52
-
53
- # Examples:
54
- # # 18th fortnight of 2004
55
- # Post.by_fortnight(18, :year => 2004)
56
- def by_fortnight(time=Time.zone.now, options = {}, &block)
57
- time = parse(time)
58
-
59
- # If options[:year] is passed in, use that year regardless.
60
- year = work_out_year(options[:year]) if options[:year]
61
- # If the first argument is a date or time, ask it for the year
62
- year ||= time.year unless time.is_a?(Numeric)
63
- # If the first argument is a fixnum, assume this year.
64
- year ||= Time.zone.now.year
65
-
66
- # Dodgy!
67
- # Surely there's a method in Rails to do this.
68
- start_time = if valid_time_or_date?(time)
69
- time.beginning_of_year + (time.strftime("%U").to_i).weeks
70
- elsif time.is_a?(Numeric) && time <= 26
71
- Time.utc(year, 1, 1) + ((time.to_i) * 2).weeks
72
- else
73
- raise ParseError, "by_fortnight takes only a Time or Date object, a Fixnum (less than or equal to 26) or a Chronicable string."
74
- end
75
- start_time = start_time.beginning_of_week
76
- end_time = start_time + 2.weeks
77
- by_star(start_time, end_time, options, &block)
78
- end
79
-
80
- # Examples:
81
- # # 36th week
82
- # Post.by_week(36)
83
- # Post.by_week(36.54)
84
- # Post.by_week(36, :year => 2004)
85
- # Post.by_week(<Time object>)
86
- # Post.by_week(<Date object>)
87
- # Post.by_week("next tuesday")
88
- def by_week(time=Time.zone.now, options = {}, &block)
89
- time = parse(time)
90
-
91
- # If options[:year] is passed in, use that year regardless.
92
- year = work_out_year(options[:year]) if options[:year]
93
- # If the first argument is a date or time, ask it for the year
94
- year ||= time.year unless time.is_a?(Numeric)
95
- # If the first argument is a fixnum, assume this year.
96
- year ||= Time.now.year
97
-
98
- # Dodgy!
99
- # Surely there's a method in Rails to do this.
100
- start_time = if valid_time_or_date?(time)
101
- weeks = time.strftime("%U").to_i
102
- time.beginning_of_year
103
- elsif time.is_a?(Numeric) && time < 53
104
- weeks = time.to_i
105
- Time.utc(year, 1, 1)
106
- else
107
- raise ParseError, "by_week takes only a Time or Date object, a Fixnum (less than or equal to 53) or a Chronicable string."
108
- end
109
- start_time += weeks.weeks
110
- end_time = start_time + 1.week
111
- by_star(start_time, end_time, options, &block)
112
- end
113
-
114
-
115
- # Examples:
116
- # Post.by_weekend
117
- # Post.by_weekend(Time.now + 5.days)
118
- # Post.by_weekend(Date.today + 5)
119
- # Post.by_weekend("next tuesday")
120
- def by_weekend(time=Time.zone.now, options = {}, &block)
121
- time = parse(time)
122
- start_time = time.beginning_of_weekend
123
- by_star(start_time, (start_time + 1.day).end_of_day, options, &block)
124
- end
125
-
126
-
127
- # Examples:
128
- # Post.by_current_weekend
129
- def by_current_weekend(options = {}, &block)
130
- time = Time.zone.now
131
- # Friday, 3pm
132
- start_time = time.beginning_of_weekend
133
- # Monday, 3am
134
- end_time = time.end_of_weekend
135
- by_star(start_time, end_time, options, &block)
136
- end
137
-
138
- # Examples:
139
- # Post.by_current_work_week
140
- def by_current_work_week(options = {}, &block)
141
- time = Time.zone.now
142
- # Monday, 3am
143
- time = time + 1.week if time.wday == 6 || time.wday == 0
144
- start_time = time.beginning_of_week + 3.hours
145
- # Friday, 3pm
146
- end_time = time.beginning_of_weekend
147
- by_star(start_time, end_time, options, &block)
148
- end
149
-
150
-
151
- # Examples:
152
- # Post.by_day
153
- # Post.by_day(Time.yesterday)
154
- # Post.by_day("next tuesday")
155
- def by_day(time = Time.zone.now, options = {}, &block)
156
- time = parse(time)
157
- by_star(time.beginning_of_day, time.end_of_day, options, &block)
158
- end
159
- alias_method :today, :by_day
160
-
161
- # Examples:
162
- # Post.yesterday
163
- # # 2 days ago:
164
- # Post.yesterday(Time.yesterday)
165
- # # day before next tuesday
166
- # Post.yesterday("next tuesday")
167
- def yesterday(time = Time.zone.now, options = {}, &block)
168
- time = parse(time)
169
- by_day(time.advance(:days => -1), options, &block)
170
- end
171
-
172
- # Examples:
173
- # Post.tomorrow
174
- # # 2 days from now:
175
- # Post.tomorrow(Time.tomorrow)
176
- # # day after next tuesday
177
- # Post.tomorrow("next tuesday")
178
- def tomorrow(time = Time.zone.now, options = {}, &block)
179
- time = parse(time)
180
- by_day(time.advance(:days => 1), options, &block)
181
- end
182
-
183
- # Scopes to records older than current or given time
184
- # Post.past
185
- # Post.past()
186
- def past(time = Time.zone.now, options = {}, &block)
187
- time = parse(time)
188
- by_direction("<", time, options, &block)
189
- end
190
-
191
- # Scopes to records newer than current or given time
192
- def future(time = Time.zone.now, options = {}, &block)
193
- time = parse(time)
194
- by_direction(">", time, options, &block)
195
- end
196
-
197
- private
198
-
199
- def by_direction(condition, time, options = {}, &block)
200
- field = connection.quote_table_name(table_name)
201
- field << "." << connection.quote_column_name(options[:field] || "created_at")
202
- with_scope(:find => { :conditions => ["#{field} #{condition} ?", time.utc] }) do
203
- if block_given?
204
- with_scope(:find => block.call) do
205
- find(:all)
206
- end
207
- else
208
- find(:all)
209
- end
210
- end
211
- end
212
-
213
- # scopes results between start_time and end_time
214
- def by_star(start_time, end_time, options = {}, &block)
215
- start_time = parse(start_time)
216
- end_time = parse(end_time)
217
-
218
- raise ParseError, "End time is before start time, searching like this will return no results." if end_time < start_time
219
-
220
- field = options[:field] || "created_at"
221
- with_scope(:find => { :conditions => { field => start_time.utc..end_time.utc } }) do
222
- if block_given?
223
- with_scope(:find => block.call) do
224
- find(:all)
225
- end
226
- else
227
- find(:all)
228
- end
229
- end
230
- end
231
-
232
- alias :between :by_star
233
- public :between
234
-
235
- # This will work for the next 30 years (written in 2009)
236
- def work_out_year(value)
237
- case value
238
- when 0..39
239
- 2000 + value
240
- when 40..99
241
- 1900 + value
242
- when nil
243
- Time.zone.now.year
244
- else
245
- # We may be passed something that's not a straight out integer
246
- # These things include: BigDecimals, Floats and Strings.
247
- value.to_i
248
- end
249
- end
250
-
251
- # Checks if the object is a Time, Date or TimeWithZone object.
252
- def valid_time_or_date?(value)
253
- value.is_a?(Time) || value.is_a?(Date) || value.is_a?(ActiveSupport::TimeWithZone)
254
- end
255
-
256
- def parse(object)
257
- object = case object.class.to_s
258
- when "NilClass"
259
- o = Time.zone.now
260
- when "String"
261
- o = object
262
- Chronic.parse(object, :now => Time.zone.now)
263
- when "Date"
264
- object.to_time(:utc)
265
- else
266
- object
267
- end
268
- raise ParseError, "Chronic couldn't work out #{o.inspect}; please be more precise." if object.nil?
269
- object
270
- end
271
-
272
- def method_missing(method, *args)
273
- if method.to_s =~ /^(as_of|up_to)_(.+)$/
274
- method = $1
275
- expr = $2.humanize
276
- unless time = parse(expr)
277
- raise ParseError, "Chronic couldn't work out #{expr.inspect}; please be more precise."
278
- end
279
-
280
- reference = args.first || Time.now
281
-
282
- if "as_of" == method
283
- between(time, reference)
284
- else
285
- between(reference, time)
286
- end
287
- else
288
- super
289
- end
290
- end
15
+ include RangeCalculations
16
+ include Shared
17
+ include Vanilla
18
+ include Calculations
291
19
  end
292
20
 
293
21
  class ParseError < Exception; end
294
22
  class MonthNotFound < Exception; end
295
23
  end
296
-
297
- class Time
298
- def beginning_of_weekend
299
- friday = case self.wday
300
- when 0
301
- self.end_of_week.beginning_of_day.advance(:days => -2)
302
- when 5
303
- self.beginning_of_day
304
- else
305
- self.beginning_of_week.advance(:days => 4)
306
- end
307
- # 3pm, Friday.
308
- (friday + 15.hours)
309
- end
310
-
311
- def end_of_weekend
312
- # 3am, Monday.
313
- # LOL I CHEATED.
314
- beginning_of_weekend + 3.days - 12.hours
315
- end
316
- end