by_star 0.2.5 → 0.3.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/.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