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 +2 -0
- data/README.markdown +88 -0
- data/VERSION +1 -1
- data/by_star.gemspec +11 -2
- data/lib/by_star.rb +10 -303
- data/lib/calculations.rb +23 -0
- data/lib/calculations/count.rb +15 -0
- data/lib/calculations/sum.rb +14 -0
- data/lib/range_calculations.rb +23 -0
- data/lib/shared.rb +6 -0
- data/lib/time_ext.rb +20 -0
- data/lib/vanilla.rb +273 -0
- data/spec/by_star_spec.rb +410 -357
- data/spec/database.yml +9 -0
- data/spec/fixtures/models.rb +18 -0
- data/spec/fixtures/schema.rb +6 -0
- data/spec/spec_helper.rb +5 -6
- metadata +11 -2
data/.gitignore
CHANGED
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.
|
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.
|
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-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|