by_star 4.0.0 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/mysql.yml +92 -92
  3. data/.github/workflows/postgresql.yml +99 -99
  4. data/.gitignore +6 -6
  5. data/.travis.yml +92 -92
  6. data/CHANGELOG.md +63 -59
  7. data/Gemfile +18 -18
  8. data/MIT-LICENSE +20 -20
  9. data/README.md +616 -616
  10. data/Rakefile +18 -18
  11. data/UPGRADING +4 -4
  12. data/by_star.gemspec +34 -34
  13. data/cleaner.rb +24 -24
  14. data/lib/by_star/base.rb +69 -69
  15. data/lib/by_star/between.rb +185 -185
  16. data/lib/by_star/directional.rb +35 -35
  17. data/lib/by_star/kernel/date.rb +41 -41
  18. data/lib/by_star/kernel/in_time_zone.rb +20 -20
  19. data/lib/by_star/kernel/time.rb +41 -41
  20. data/lib/by_star/normalization.rb +156 -156
  21. data/lib/by_star/orm/active_record/by_star.rb +75 -75
  22. data/lib/by_star/orm/mongoid/by_star.rb +90 -90
  23. data/lib/by_star/orm/mongoid/reorder.rb +23 -23
  24. data/lib/by_star/version.rb +3 -3
  25. data/lib/by_star.rb +18 -18
  26. data/spec/database.yml +15 -15
  27. data/spec/fixtures/active_record/models.rb +12 -12
  28. data/spec/fixtures/active_record/schema.rb +19 -19
  29. data/spec/fixtures/mongoid/models.rb +31 -31
  30. data/spec/fixtures/shared/seeds.rb +36 -36
  31. data/spec/gemfiles/Gemfile.rails +5 -5
  32. data/spec/gemfiles/Gemfile.rails32 +7 -7
  33. data/spec/gemfiles/Gemfile.rails40 +7 -7
  34. data/spec/gemfiles/Gemfile.rails41 +7 -7
  35. data/spec/gemfiles/Gemfile.rails42 +7 -7
  36. data/spec/gemfiles/Gemfile.rails50 +7 -7
  37. data/spec/gemfiles/Gemfile.rails51 +7 -7
  38. data/spec/gemfiles/Gemfile.rails52 +7 -7
  39. data/spec/gemfiles/Gemfile.rails60 +7 -7
  40. data/spec/gemfiles/Gemfile.rails61 +7 -7
  41. data/spec/integration/active_record/active_record_spec.rb +41 -41
  42. data/spec/integration/mongoid/mongoid_spec.rb +39 -39
  43. data/spec/integration/shared/at_time.rb +53 -53
  44. data/spec/integration/shared/between_dates.rb +99 -99
  45. data/spec/integration/shared/between_times.rb +99 -99
  46. data/spec/integration/shared/by_calendar_month.rb +55 -55
  47. data/spec/integration/shared/by_cweek.rb +54 -54
  48. data/spec/integration/shared/by_day.rb +120 -120
  49. data/spec/integration/shared/by_direction.rb +126 -126
  50. data/spec/integration/shared/by_fortnight.rb +48 -48
  51. data/spec/integration/shared/by_month.rb +50 -50
  52. data/spec/integration/shared/by_quarter.rb +49 -49
  53. data/spec/integration/shared/by_week.rb +54 -54
  54. data/spec/integration/shared/by_weekend.rb +49 -49
  55. data/spec/integration/shared/by_year.rb +48 -48
  56. data/spec/integration/shared/index_scope_parameter.rb +111 -111
  57. data/spec/integration/shared/offset_parameter.rb +32 -32
  58. data/spec/integration/shared/order_parameter.rb +36 -36
  59. data/spec/integration/shared/relative.rb +174 -174
  60. data/spec/spec_helper.rb +33 -33
  61. data/spec/unit/kernel_date_spec.rb +113 -113
  62. data/spec/unit/kernel_time_spec.rb +57 -57
  63. data/spec/unit/normalization_spec.rb +384 -384
  64. data/tmp/.gitignore +1 -1
  65. metadata +3 -3
data/README.md CHANGED
@@ -1,616 +1,616 @@
1
- # ByStar
2
-
3
- [![Build Status](https://travis-ci.org/radar/by_star.svg)](https://travis-ci.org/radar/by_star)
4
- [![Code Climate](https://codeclimate.com/github/radar/by_star.svg)](https://codeclimate.com/github/radar/by_star)
5
-
6
- ByStar (by_*) allows you easily and reliably query ActiveRecord and Mongoid objects based on time.
7
-
8
- ### Examples
9
-
10
- ```ruby
11
- Post.by_year(2013) # all posts in 2013
12
- Post.before(Date.today) # all posts for before today
13
- Post.yesterday # all posts for yesterday
14
- Post.between_times(Time.zone.now - 3.hours, # all posts in last 3 hours
15
- Time.zone.now)
16
- @post.next # next post after a given post
17
- ```
18
-
19
- ## Installation
20
-
21
- Install this gem by adding this to your Gemfile:
22
-
23
- ```ruby
24
- gem 'by_star', git: 'https://github.com/radar/by_star'
25
- ```
26
-
27
- Then run `bundle install`
28
-
29
- If you are using ActiveRecord, you're done!
30
-
31
- Mongoid users, please include the Mongoid::ByStar module for each model you wish to use the functionality.
32
- This is the convention among Mongoid plugins.
33
-
34
- ```ruby
35
- class MyModel
36
- include Mongoid::Document
37
- include Mongoid::ByStar
38
- ```
39
-
40
- ## Finder Methods
41
-
42
- ### Base Scopes
43
-
44
- ByStar adds the following finder scopes (class methods) to your model to query time ranges.
45
- These accept a `Date`, `Time`, or `DateTime` object as an argument, which defaults to `Time.zone.now` if not specified:
46
-
47
- | Scope | Meaning |
48
- | --- | --- |
49
- | `between_times(start_time, end_time)` | Finds all records occurring between two given times. |
50
- | `between_dates(start_date, end_date)` | Finds all records occurring between two given dates, from beginning of start_date until end of end_date. |
51
- | `before(end_time)` | Finds all records occurring before the given time. |
52
- | `after(start_time)` | Finds all records occurring after the given time. |
53
- | `at_time(time)` | Finds all records occurring exactly at the given time, or which overlap the time in the case of "timespan"-type object (see below) |
54
-
55
- `between_times` and `between_dates` supports alternate argument forms:
56
- * `between_times(Range)`
57
- * `between_times(Array)`
58
- * `between_times(start_time, nil)` - same as `after(start_time)`
59
- * `between_times(nil, end_time)` - same as `before(end_time)`
60
-
61
- ### Time Range Scopes
62
-
63
- ByStar adds additional shortcut scopes based on commonly used time ranges.
64
- See sections below for detailed argument usage of each:
65
-
66
- | Scope | Meaning |
67
- | --- | --- |
68
- | `by_day` | Query by a given date. |
69
- | `by_week` | Allows zero-based week value from 0 to 52. |
70
- | `by_cweek` | Allows one-based week value from 1 to 53. |
71
- | `by_weekend` | Saturday and Sunday only of the given week. |
72
- | `by_fortnight` | A two-week period, with the first fortnight of the year beginning on 1st January. |
73
- | `by_month` | Query by month. Allows integer arg, e.g. `11` for November. |
74
- | `by_calendar_month` | Month as it appears on a calendar; days form previous/following months which are part of the first/last weeks of the given month. |
75
- | `by_quarter` | 3-month intervals of the year. |
76
- | `by_year` | Query by year. Allows integer arg, e.g. `2017`. |
77
-
78
- ### Relative Scopes
79
-
80
- ByStar also adds scopes which are relative to the current time.
81
- Note the `past_*` and `next_*` methods represent a time distance from current time (`Time.zone.now`),
82
- and do not strictly end/begin evenly on a calendar week/month/year (unlike `by_*` methods which do.)
83
-
84
- | Scope | Meaning |
85
- | --- | --- |
86
- | `today` | Finds all occurrences on today's date. |
87
- | `yesterday` | Finds all occurrences on yesterday's date. |
88
- | `tomorrow` | Finds all occurrences on tomorrow's date. |
89
- | `past_day` | Prior 24-hour period from current time. |
90
- | `past_week` | Prior 7-day period from current time. |
91
- | `past_fortnight` | Prior 14-day period from current time. |
92
- | `past_month` | Prior 30-day period from current time. |
93
- | `past_year` | Prior 365-day period from current time. |
94
- | `next_day` | Subsequent 24-hour period from current time. |
95
- | `next_week` | Subsequent 7-day period from current time. |
96
- | `next_fortnight` | Subsequent 14-day period from current time. |
97
- | `next_month` | Subsequent 30-day period from current time. |
98
- | `next_year` | Subsequent 365-day period from current time. |
99
-
100
- ### Superlative Finders
101
-
102
- Find the oldest or newest records. Returns an object instance (not a relation):
103
-
104
- * `newest`
105
- * `oldest`
106
-
107
- ### Instance Methods
108
-
109
- In addition, ByStar adds instance methods to return the next / previous record in the timewise sequence.
110
- Returns an object instance (not a relation):
111
-
112
- * `object.next`
113
- * `object.previous`
114
-
115
- ### Kernel Extensions
116
-
117
- ByStar extends the kernel `Date`, `Time`, and `DateTime` objects with the following instance methods,
118
- which mirror the ActiveSupport methods `beginning_of_day`, `end_of_week`, etc:
119
-
120
- * `beginning_of_weekend`
121
- * `end_of_weekend`
122
- * `beginning_of_fortnight`
123
- * `end_of_fortnight`
124
- * `beginning_of_calendar_month`
125
- * `end_of_calendar_month`
126
-
127
- Lastly, ByStar aliases Rails 3 `Date#to_time_in_current_zone` to the Rails 4 syntax `#in_time_zone`, if it has not already been defined.
128
-
129
- ## Usage
130
-
131
- ### Setting the Query Field
132
-
133
- By default, ByStar assumes you will use the `created_at` field to query objects by time.
134
- You may specify an alternate field on all query methods as follows:
135
-
136
- ```ruby
137
- Post.by_month("January", field: :updated_at)
138
- ```
139
-
140
- Alternatively, you may set a default in your model using the `by_star_field` macro:
141
-
142
- ```ruby
143
- class Post < ActiveRecord::Base
144
- by_star_field :updated_at
145
- end
146
- ```
147
-
148
- ### Scoping the Query
149
-
150
- All ByStar methods (except `oldest`, `newest`, `previous`, `next`) return an `ActiveRecord::Relation`
151
- (or `Mongoid::Criteria`) which can be daisy-chained with other scopes/finder methods:
152
-
153
- ```ruby
154
- Post.by_month.your_scope
155
- Post.by_month(1).include(:tags).where("tags.name" => "ruby")
156
- ```
157
-
158
- Want to count records? Simple:
159
-
160
- ```ruby
161
- Post.by_month.count
162
- ```
163
-
164
- ### Timezone Handling
165
-
166
- ByStar date-range finders will use value of `Time.zone` to evaluate the args.
167
- This may cause unexpected behavior when use Time values in timezones other than `Time.zone`.
168
-
169
- ```ruby
170
- Time.zone = 'Australia/Sydney'
171
- Post.by_day('2020-04-05 18:00:00 EST')
172
- #=> Returns Apr 6th, 0:00 until Apr 6th, 23:59 in Sydney timezone.
173
- ```
174
-
175
- ### `:offset` Option
176
-
177
- All ByStar finders support an `:offset` option which is applied to time period of the query condition.
178
- This is useful in cases where the daily cycle occurs at a time other than midnight.
179
-
180
- For example, if you'd like to find all Posts from 9:00 on 2014-03-05 until 8:59:59.999 on 2014-03-06, you can do:
181
-
182
- ```ruby
183
- Post.by_day('2014-03-05', offset: 9.hours)
184
- ```
185
-
186
- **Note:** When passing `offset` in date finders, it will set the hour, minute, and second on the queried date in order to properly handle DST transitions. Example:
187
-
188
- ```ruby
189
- Time.zone = 'Australia/Sydney'
190
- Post.by_day('2020-04-05', offset: 9.hours)
191
- #=> Returns Apr 5th, 09:00 until Apr 6th, 08:59
192
- ```
193
-
194
- ### Timespan Objects
195
-
196
- If your object has both a start and end time, you may pass both params to `by_star_field`:
197
-
198
- ```ruby
199
- by_star_field :start_time, :end_time
200
- ```
201
-
202
- By default, ByStar queries will return all objects whose range has any overlap within the desired period (permissive):
203
-
204
- ```ruby
205
- MultiDayEvent.by_month("January")
206
- #=> returns MultiDayEvents that overlap in January,
207
- # even if they start in December and/or end in February
208
- ```
209
-
210
- ### Timespan Objects: `#at_time`
211
-
212
- To find all instances of a timespan object which contain a specific time:
213
-
214
- ```ruby
215
- Post.at_time(time)
216
- ```
217
-
218
- This can be useful to find all currently active instances. Note that object instances which start
219
- exactly at the given `time` will be included in the result, but instances that end exactly at the given
220
- `time` will not be.
221
-
222
- ### Timespan Objects: `:strict` Option
223
-
224
- If you'd like to confine results to only those both starting and ending within the given range, use the `:strict` option:
225
-
226
- ```ruby
227
- MultiDayEvent.by_month("January", :strict => true)
228
- #=> returns MultiDayEvents that both start AND end in January
229
- ```
230
-
231
- ### Timespan Objects: Database Indexing and `:index_scope` Option
232
-
233
- In order to ensure query performance on large dataset, you must add an index to the query field (e.g. "created_at") be indexed. ByStar does **not** define indexes automatically.
234
-
235
- Database indexes require querying a range query on a single field, i.e. `start_time >= X and start_time <= Y`.
236
- If we use a single-sided query, the database will iterate through all items either from the beginning or until the end of time.
237
- This poses a challenge for timespan-type objects which have two fields, i.e. `start_time` and `end_time`.
238
- There are two cases to consider:
239
-
240
- 1) Timespan with `:strict` option, e.g. `start_time >= X and end_time <= Y`.
241
-
242
- Given that this gem requires `start_time >= end_time`, we add the converse constraint `start_time <= Y and end_time >= X`
243
- to ensure both fields are double-sided, i.e. an index can be used on either field.
244
-
245
- 2) Timespan without `:strict` option, e.g. "start_time < Y and end_time > X".
246
-
247
- Here we need to add a condition `start_time >= X` to ensure `start_time` is bounded on both sides.
248
- To achieve this, we allow an `:index_scope` option which is the minimum "strict" bound on the querying range,
249
- in other words, it is an assumption about the maximum timespan of objects.
250
-
251
- `:index_scope` supports multiple value types:
252
-
253
- | `:index_scope` Value | Meaning |
254
- | --- | --- |
255
- | `nil` or `false` | No constraint set; query will be one-sided (default, but not recommended) |
256
- | `Date` or `Time`, etc. | A fixed point in time |
257
- | `ActiveSupport::Duration` (e.g. `1.month`) | The duration value will be subtracted from the start of the range. In other words, a value of `1.month` would imply the longest possible object in the database is no longer than `1.month`. |
258
- | `Numeric` | Will be converted to seconds, then handled the same as `ActiveSupport::Duration` |
259
- | `:beginning_of_day` (`Symbol` literal) |
260
- | `Proc<Range, Hash(options)>` | A proc which evaluates to one of the above types. Args are `(start_time, end_time, options)` |
261
-
262
- An example settings of `:index_scope`:
263
-
264
- ```
265
- # The maximum possible object length is 5 hours.
266
- by_star index_scope: 5.hours
267
-
268
- # Objects are guaranteed to start within the same month, with some offset.
269
- by_star index_scope: ->(start_time, end_time, options){ start_time.beginning_of_month + (options[:offset] || 0) }
270
-
271
- # The maximum possible object length half the range being queried.
272
- by_star index_scope: ->(start_time, end_time, options){ ((start_time - end_time)*0.5).seconds }
273
- ```
274
-
275
- ### Chronic Support
276
-
277
- If [Chronic](https://github.com/mojombo/chronic) gem is present, it will be used to parse natural-language date/time
278
- strings in all ByStar finder methods. Otherwise, the Ruby `Time.parse` kernel method will be used as a fallback.
279
-
280
- As of ByStar 2.2.0, you must explicitly include `gem 'chronic'` into your Gemfile in order to use Chronic.
281
-
282
- ## Advanced Usage
283
-
284
- ### between_times
285
-
286
- To find records between two times:
287
-
288
- ```ruby
289
- Post.between_times(time1, time2)
290
- ```
291
-
292
- You use a Range like so:
293
-
294
- ```ruby
295
- Post.between_times(time1..time2)
296
- ```
297
-
298
- Also works with dates - WARNING: there are currently some caveats see [Issue #49](https://github.com/radar/by_star/issues/49):
299
-
300
- ```ruby
301
- Post.between_times(date1, date2)
302
- ```
303
-
304
- It will query records from `date1` (00:00:00 Hrs) until `date2` (23:59:59 Hrs).
305
-
306
- ### before and after
307
-
308
- To find all posts before / after the current time:
309
-
310
- ```ruby
311
- Post.before
312
- Post.after
313
- ```
314
-
315
- To find all posts before certain time or date:
316
-
317
- ```ruby
318
- Post.before(Date.today + 2)
319
- Post.after(Time.now + 5.days)
320
- ```
321
-
322
- You can also pass a string:
323
-
324
- ```ruby
325
- Post.before("next tuesday")
326
- ```
327
-
328
- For Time-Range type objects, only the start time is considered for `before` and `after`.
329
-
330
- ### previous and next
331
-
332
- To find the prior/subsequent record to a model instance, `previous`/`next` on it:
333
-
334
- ```ruby
335
- Post.last.previous
336
- Post.first.next
337
- ```
338
-
339
- You can specify a field also:
340
-
341
- ```ruby
342
- Post.last.previous(field: "published_at")
343
- Post.first.next(field: "published_at")
344
- ```
345
-
346
- For Time-Range type objects, only the start time is considered for `previous` and `next`.
347
-
348
- ### by_year
349
-
350
- To find records from the current year, simply call the method without any arguments:
351
-
352
- ```ruby
353
- Post.by_year
354
- ```
355
-
356
- To find records based on a year you can pass it a two or four digit number:
357
-
358
- ```ruby
359
- Post.by_year(09)
360
- ```
361
-
362
- This will return all posts in 2009, whereas:
363
-
364
- ```ruby
365
- Post.by_year(99)
366
- ```
367
-
368
- will return all the posts in the year 1999.
369
-
370
- You can also specify the full year:
371
-
372
- ```ruby
373
- Post.by_year(2009)
374
- Post.by_year(1999)
375
- ```
376
-
377
- ### by_month
378
-
379
- If you know the number of the month you want:
380
-
381
- ```ruby
382
- Post.by_month(1)
383
- ```
384
-
385
- This will return all posts in the first month (January) of the current year.
386
-
387
- If you like being verbose:
388
-
389
- ```ruby
390
- Post.by_month("January")
391
- ```
392
-
393
- This will return all posts created in January of the current year.
394
-
395
- If you want to find all posts in January of last year just do
396
-
397
- ```ruby
398
- Post.by_month(1, year: 2007)
399
- ```
400
-
401
- or
402
-
403
- ```ruby
404
- Post.by_month("January", year: 2007)
405
- ```
406
-
407
- This will perform a find using the column you've specified.
408
-
409
- If you have a Time object you can use it to find the posts:
410
-
411
- ```ruby
412
- Post.by_month(Time.local(2012, 11, 24))
413
- ```
414
-
415
- This will find all the posts in November 2012.
416
-
417
- ### by_calendar_month
418
-
419
- Finds records for a given month as shown on a calendar. Includes all the results of `by_month`, plus any results which fall in the same week as the first and last of the month. Useful for working with UI calendars which show rows of weeks.
420
-
421
- ```ruby
422
- Post.by_calendar_month
423
- ```
424
-
425
- Parameter behavior is otherwise the same as `by_month`. Also, `:start_day` option is supported to specify the start day of the week (`:monday`, `:tuesday`, etc.)
426
-
427
- ### by_fortnight
428
-
429
- Fortnight numbering starts at 0. The beginning of a fortnight is Monday, 12am.
430
-
431
- To find records from the current fortnight:
432
-
433
- ```ruby
434
- Post.by_fortnight
435
- ```
436
-
437
- To find records based on a fortnight, you can pass in a number (representing the fortnight number) or a time object:
438
-
439
- ```ruby
440
- Post.by_fortnight(18)
441
- ```
442
-
443
- This will return all posts in the 18th fortnight of the current year.
444
-
445
- ```ruby
446
- Post.by_fortnight(18, year: 2012)
447
- ```
448
-
449
- This will return all posts in the 18th fortnight week of 2012.
450
-
451
- ```ruby
452
- Post.by_fortnight(Time.local(2012,1,1))
453
- ```
454
-
455
- This will return all posts from the first fortnight of 2012.
456
-
457
- ### by_week and by_cweek
458
-
459
- Week numbering starts at 0, and cweek numbering starts at 1 (same as `Date#cweek`). The beginning of a week is as defined in `ActiveSupport#beginning_of_week`, which can be configured.
460
-
461
- To find records from the current week:
462
-
463
- ```ruby
464
- Post.by_week
465
- Post.by_cweek # same result
466
- ```
467
-
468
- This will return all posts in the 37th week of the current year (remember week numbering starts at 0):
469
-
470
- ```ruby
471
- Post.by_week(36)
472
- Post.by_cweek(37) # same result
473
- ```
474
-
475
- This will return all posts in the 37th week of 2012:
476
-
477
- ```ruby
478
- Post.by_week(36, year: 2012)
479
- Post.by_cweek(37, year: 2012) # same result
480
- ```
481
-
482
- This will return all posts in the week which contains Jan 1, 2012:
483
-
484
- ```ruby
485
- Post.by_week(Time.local(2012,1,1))
486
- Post.by_cweek(Time.local(2012,1,1)) # same result
487
- ```
488
-
489
- You may pass in a `:start_day` option (`:monday`, `:tuesday`, etc.) to specify the starting day of the week. This may also be configured in Rails.
490
-
491
- ### by_weekend
492
-
493
- If the time passed in (or the time now is a weekend) it will return posts from 0:00 Saturday to 23:59:59 Sunday. If the time is a week day, it will show all posts for the coming weekend.
494
-
495
- ```ruby
496
- Post.by_weekend(Time.now)
497
- ```
498
-
499
- ### by_day and today
500
-
501
- To find records for today:
502
-
503
- ```ruby
504
- Post.by_day
505
- Post.today
506
- ```
507
-
508
- To find records for a certain day:
509
-
510
- ```ruby
511
- Post.by_day(Time.local(2012, 1, 1))
512
- ```
513
-
514
- You can also pass a string:
515
-
516
- ```ruby
517
- Post.by_day("next tuesday")
518
- ```
519
-
520
- This will return all posts for the given day.
521
-
522
- ### by_quarter
523
-
524
- Finds records by 3-month quarterly period of year. Quarter numbering starts at 1. The four quarters of the year begin on Jan 1, Apr 1, Jul 1, and Oct 1 respectively.
525
-
526
- To find records from the current quarter:
527
-
528
- ```ruby
529
- Post.by_quarter
530
- ```
531
-
532
- To find records based on a quarter, you can pass in a number (representing the quarter number) or a time object:
533
-
534
- ```ruby
535
- Post.by_quarter(4)
536
- ```
537
-
538
- This will return all posts in the 4th quarter of the current year.
539
-
540
- ```ruby
541
- Post.by_quarter(2, year: 2012)
542
- ```
543
-
544
- This will return all posts in the 2nd quarter of 2012.
545
-
546
- ```ruby
547
- Post.by_week(Time.local(2012,1,1))
548
- ```
549
-
550
- This will return all posts from the first quarter of 2012.
551
-
552
- ## Version Support
553
-
554
- ByStar is tested against the following versions:
555
-
556
- * Ruby 2.0.0+
557
- * Rails/ActiveRecord 3.2+
558
- * Mongoid 3.1+
559
-
560
- Note that ByStar automatically adds the following version compatibility shims:
561
-
562
- * ActiveSupport 3.x: Add `Time/Date/DateTime#in_time_zone` (as an alias to `#to_time_in_current_zone`) for compatibility with Rails 4+.
563
- * Mongoid 3.x: Adds `Criteria#reorder` method from Mongoid 4.
564
-
565
-
566
- ## Testing
567
-
568
- ### Test Setup
569
-
570
- Specify a database by supplying a `DB` environmental variable:
571
-
572
- ```bash
573
- bundle exec rake spec DB=sqlite
574
- ```
575
-
576
- You can also take an ORM-specific test task for a ride:
577
-
578
- ```bash
579
- bundle exec rake spec:active_record
580
- ```
581
-
582
- Have an Active Record or Mongoid version in mind? Set the environment variables
583
- `ACTIVE_RECORD_VERSION` and `MONGOID_VERSION` to a version of your choice. A
584
- version number provided will translate to `~> VERSION`, and the string `master`
585
- will grab the latest from Github.
586
-
587
- ```bash
588
- # Update your bundle appropriately...
589
- ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle update
590
-
591
- # ...then run the specs
592
- ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle exec rpsec spec
593
- ```
594
-
595
- ### Test Implementation
596
-
597
- ByStar tests use TimeCop to lock the system `Time.now` at Jan 01, 2014, and seed
598
- objects with fixed dates according to `spec/fixtures/shared/seeds.rb`.
599
- Note that the timezone is randomized on each run to shake-out timezone related quirks.
600
-
601
-
602
- ## Collaborators
603
-
604
- ByStar is actively maintained by Ryan Bigg (radar) and Johnny Shields (johnnyshields)
605
-
606
- Thank you to the following people:
607
-
608
- * Thomas Sinclair for the original bump for implementing ByStar
609
- * [Ruby on Rails](http://rubyonrails.org/) for their support
610
- * Mislav Marohnic
611
- * August Lilleas (leethal)
612
- * gte351s
613
- * Sam Elliott (lenary)
614
- * The creators of the [Chronic](https://github.com/mojombo/chronic) gem
615
- * Erik Fonselius
616
- * Johnny Shields (johnnyshields)
1
+ # ByStar
2
+
3
+ [![Build Status](https://travis-ci.org/radar/by_star.svg)](https://travis-ci.org/radar/by_star)
4
+ [![Code Climate](https://codeclimate.com/github/radar/by_star.svg)](https://codeclimate.com/github/radar/by_star)
5
+
6
+ ByStar (by_*) allows you easily and reliably query ActiveRecord and Mongoid objects based on time.
7
+
8
+ ### Examples
9
+
10
+ ```ruby
11
+ Post.by_year(2013) # all posts in 2013
12
+ Post.before(Date.today) # all posts for before today
13
+ Post.yesterday # all posts for yesterday
14
+ Post.between_times(Time.zone.now - 3.hours, # all posts in last 3 hours
15
+ Time.zone.now)
16
+ @post.next # next post after a given post
17
+ ```
18
+
19
+ ## Installation
20
+
21
+ Install this gem by adding this to your Gemfile:
22
+
23
+ ```ruby
24
+ gem 'by_star', git: 'https://github.com/radar/by_star'
25
+ ```
26
+
27
+ Then run `bundle install`
28
+
29
+ If you are using ActiveRecord, you're done!
30
+
31
+ Mongoid users, please include the Mongoid::ByStar module for each model you wish to use the functionality.
32
+ This is the convention among Mongoid plugins.
33
+
34
+ ```ruby
35
+ class MyModel
36
+ include Mongoid::Document
37
+ include Mongoid::ByStar
38
+ ```
39
+
40
+ ## Finder Methods
41
+
42
+ ### Base Scopes
43
+
44
+ ByStar adds the following finder scopes (class methods) to your model to query time ranges.
45
+ These accept a `Date`, `Time`, or `DateTime` object as an argument, which defaults to `Time.zone.now` if not specified:
46
+
47
+ | Scope | Meaning |
48
+ | --- | --- |
49
+ | `between_times(start_time, end_time)` | Finds all records occurring between two given times. |
50
+ | `between_dates(start_date, end_date)` | Finds all records occurring between two given dates, from beginning of start_date until end of end_date. |
51
+ | `before(end_time)` | Finds all records occurring before the given time. |
52
+ | `after(start_time)` | Finds all records occurring after the given time. |
53
+ | `at_time(time)` | Finds all records occurring exactly at the given time, or which overlap the time in the case of "timespan"-type object (see below) |
54
+
55
+ `between_times` and `between_dates` supports alternate argument forms:
56
+ * `between_times(Range)`
57
+ * `between_times(Array)`
58
+ * `between_times(start_time, nil)` - same as `after(start_time)`
59
+ * `between_times(nil, end_time)` - same as `before(end_time)`
60
+
61
+ ### Time Range Scopes
62
+
63
+ ByStar adds additional shortcut scopes based on commonly used time ranges.
64
+ See sections below for detailed argument usage of each:
65
+
66
+ | Scope | Meaning |
67
+ | --- | --- |
68
+ | `by_day` | Query by a given date. |
69
+ | `by_week` | Allows zero-based week value from 0 to 52. |
70
+ | `by_cweek` | Allows one-based week value from 1 to 53. |
71
+ | `by_weekend` | Saturday and Sunday only of the given week. |
72
+ | `by_fortnight` | A two-week period, with the first fortnight of the year beginning on 1st January. |
73
+ | `by_month` | Query by month. Allows integer arg, e.g. `11` for November. |
74
+ | `by_calendar_month` | Month as it appears on a calendar; days form previous/following months which are part of the first/last weeks of the given month. |
75
+ | `by_quarter` | 3-month intervals of the year. |
76
+ | `by_year` | Query by year. Allows integer arg, e.g. `2017`. |
77
+
78
+ ### Relative Scopes
79
+
80
+ ByStar also adds scopes which are relative to the current time.
81
+ Note the `past_*` and `next_*` methods represent a time distance from current time (`Time.zone.now`),
82
+ and do not strictly end/begin evenly on a calendar week/month/year (unlike `by_*` methods which do.)
83
+
84
+ | Scope | Meaning |
85
+ | --- | --- |
86
+ | `today` | Finds all occurrences on today's date. |
87
+ | `yesterday` | Finds all occurrences on yesterday's date. |
88
+ | `tomorrow` | Finds all occurrences on tomorrow's date. |
89
+ | `past_day` | Prior 24-hour period from current time. |
90
+ | `past_week` | Prior 7-day period from current time. |
91
+ | `past_fortnight` | Prior 14-day period from current time. |
92
+ | `past_month` | Prior 30-day period from current time. |
93
+ | `past_year` | Prior 365-day period from current time. |
94
+ | `next_day` | Subsequent 24-hour period from current time. |
95
+ | `next_week` | Subsequent 7-day period from current time. |
96
+ | `next_fortnight` | Subsequent 14-day period from current time. |
97
+ | `next_month` | Subsequent 30-day period from current time. |
98
+ | `next_year` | Subsequent 365-day period from current time. |
99
+
100
+ ### Superlative Finders
101
+
102
+ Find the oldest or newest records. Returns an object instance (not a relation):
103
+
104
+ * `newest`
105
+ * `oldest`
106
+
107
+ ### Instance Methods
108
+
109
+ In addition, ByStar adds instance methods to return the next / previous record in the timewise sequence.
110
+ Returns an object instance (not a relation):
111
+
112
+ * `object.next`
113
+ * `object.previous`
114
+
115
+ ### Kernel Extensions
116
+
117
+ ByStar extends the kernel `Date`, `Time`, and `DateTime` objects with the following instance methods,
118
+ which mirror the ActiveSupport methods `beginning_of_day`, `end_of_week`, etc:
119
+
120
+ * `beginning_of_weekend`
121
+ * `end_of_weekend`
122
+ * `beginning_of_fortnight`
123
+ * `end_of_fortnight`
124
+ * `beginning_of_calendar_month`
125
+ * `end_of_calendar_month`
126
+
127
+ Lastly, ByStar aliases Rails 3 `Date#to_time_in_current_zone` to the Rails 4 syntax `#in_time_zone`, if it has not already been defined.
128
+
129
+ ## Usage
130
+
131
+ ### Setting the Query Field
132
+
133
+ By default, ByStar assumes you will use the `created_at` field to query objects by time.
134
+ You may specify an alternate field on all query methods as follows:
135
+
136
+ ```ruby
137
+ Post.by_month("January", field: :updated_at)
138
+ ```
139
+
140
+ Alternatively, you may set a default in your model using the `by_star_field` macro:
141
+
142
+ ```ruby
143
+ class Post < ActiveRecord::Base
144
+ by_star_field :updated_at
145
+ end
146
+ ```
147
+
148
+ ### Scoping the Query
149
+
150
+ All ByStar methods (except `oldest`, `newest`, `previous`, `next`) return an `ActiveRecord::Relation`
151
+ (or `Mongoid::Criteria`) which can be daisy-chained with other scopes/finder methods:
152
+
153
+ ```ruby
154
+ Post.by_month.your_scope
155
+ Post.by_month(1).include(:tags).where("tags.name" => "ruby")
156
+ ```
157
+
158
+ Want to count records? Simple:
159
+
160
+ ```ruby
161
+ Post.by_month.count
162
+ ```
163
+
164
+ ### Timezone Handling
165
+
166
+ ByStar date-range finders will use value of `Time.zone` to evaluate the args.
167
+ This may cause unexpected behavior when use Time values in timezones other than `Time.zone`.
168
+
169
+ ```ruby
170
+ Time.zone = 'Australia/Sydney'
171
+ Post.by_day('2020-04-05 18:00:00 EST')
172
+ #=> Returns Apr 6th, 0:00 until Apr 6th, 23:59 in Sydney timezone.
173
+ ```
174
+
175
+ ### `:offset` Option
176
+
177
+ All ByStar finders support an `:offset` option which is applied to time period of the query condition.
178
+ This is useful in cases where the daily cycle occurs at a time other than midnight.
179
+
180
+ For example, if you'd like to find all Posts from 9:00 on 2014-03-05 until 8:59:59.999 on 2014-03-06, you can do:
181
+
182
+ ```ruby
183
+ Post.by_day('2014-03-05', offset: 9.hours)
184
+ ```
185
+
186
+ **Note:** When passing `offset` in date finders, it will set the hour, minute, and second on the queried date in order to properly handle DST transitions. Example:
187
+
188
+ ```ruby
189
+ Time.zone = 'Australia/Sydney'
190
+ Post.by_day('2020-04-05', offset: 9.hours)
191
+ #=> Returns Apr 5th, 09:00 until Apr 6th, 08:59
192
+ ```
193
+
194
+ ### Timespan Objects
195
+
196
+ If your object has both a start and end time, you may pass both params to `by_star_field`:
197
+
198
+ ```ruby
199
+ by_star_field :start_time, :end_time
200
+ ```
201
+
202
+ By default, ByStar queries will return all objects whose range has any overlap within the desired period (permissive):
203
+
204
+ ```ruby
205
+ MultiDayEvent.by_month("January")
206
+ #=> returns MultiDayEvents that overlap in January,
207
+ # even if they start in December and/or end in February
208
+ ```
209
+
210
+ ### Timespan Objects: `#at_time`
211
+
212
+ To find all instances of a timespan object which contain a specific time:
213
+
214
+ ```ruby
215
+ Post.at_time(time)
216
+ ```
217
+
218
+ This can be useful to find all currently active instances. Note that object instances which start
219
+ exactly at the given `time` will be included in the result, but instances that end exactly at the given
220
+ `time` will not be.
221
+
222
+ ### Timespan Objects: `:strict` Option
223
+
224
+ If you'd like to confine results to only those both starting and ending within the given range, use the `:strict` option:
225
+
226
+ ```ruby
227
+ MultiDayEvent.by_month("January", :strict => true)
228
+ #=> returns MultiDayEvents that both start AND end in January
229
+ ```
230
+
231
+ ### Timespan Objects: Database Indexing and `:index_scope` Option
232
+
233
+ In order to ensure query performance on large dataset, you must add an index to the query field (e.g. "created_at") be indexed. ByStar does **not** define indexes automatically.
234
+
235
+ Database indexes require querying a range query on a single field, i.e. `start_time >= X and start_time <= Y`.
236
+ If we use a single-sided query, the database will iterate through all items either from the beginning or until the end of time.
237
+ This poses a challenge for timespan-type objects which have two fields, i.e. `start_time` and `end_time`.
238
+ There are two cases to consider:
239
+
240
+ 1) Timespan with `:strict` option, e.g. `start_time >= X and end_time <= Y`.
241
+
242
+ Given that this gem requires `start_time >= end_time`, we add the converse constraint `start_time <= Y and end_time >= X`
243
+ to ensure both fields are double-sided, i.e. an index can be used on either field.
244
+
245
+ 2) Timespan without `:strict` option, e.g. "start_time < Y and end_time > X".
246
+
247
+ Here we need to add a condition `start_time >= X` to ensure `start_time` is bounded on both sides.
248
+ To achieve this, we allow an `:index_scope` option which is the minimum "strict" bound on the querying range,
249
+ in other words, it is an assumption about the maximum timespan of objects.
250
+
251
+ `:index_scope` supports multiple value types:
252
+
253
+ | `:index_scope` Value | Meaning |
254
+ | --- | --- |
255
+ | `nil` or `false` | No constraint set; query will be one-sided (default, but not recommended) |
256
+ | `Date` or `Time`, etc. | A fixed point in time |
257
+ | `ActiveSupport::Duration` (e.g. `1.month`) | The duration value will be subtracted from the start of the range. In other words, a value of `1.month` would imply the longest possible object in the database is no longer than `1.month`. |
258
+ | `Numeric` | Will be converted to seconds, then handled the same as `ActiveSupport::Duration` |
259
+ | `:beginning_of_day` (`Symbol` literal) |
260
+ | `Proc<Range, Hash(options)>` | A proc which evaluates to one of the above types. Args are `(start_time, end_time, options)` |
261
+
262
+ An example settings of `:index_scope`:
263
+
264
+ ```
265
+ # The maximum possible object length is 5 hours.
266
+ by_star index_scope: 5.hours
267
+
268
+ # Objects are guaranteed to start within the same month, with some offset.
269
+ by_star index_scope: ->(start_time, end_time, options){ start_time.beginning_of_month + (options[:offset] || 0) }
270
+
271
+ # The maximum possible object length half the range being queried.
272
+ by_star index_scope: ->(start_time, end_time, options){ ((start_time - end_time)*0.5).seconds }
273
+ ```
274
+
275
+ ### Chronic Support
276
+
277
+ If [Chronic](https://github.com/mojombo/chronic) gem is present, it will be used to parse natural-language date/time
278
+ strings in all ByStar finder methods. Otherwise, the Ruby `Time.parse` kernel method will be used as a fallback.
279
+
280
+ As of ByStar 2.2.0, you must explicitly include `gem 'chronic'` into your Gemfile in order to use Chronic.
281
+
282
+ ## Advanced Usage
283
+
284
+ ### between_times
285
+
286
+ To find records between two times:
287
+
288
+ ```ruby
289
+ Post.between_times(time1, time2)
290
+ ```
291
+
292
+ You use a Range like so:
293
+
294
+ ```ruby
295
+ Post.between_times(time1..time2)
296
+ ```
297
+
298
+ Also works with dates - WARNING: there are currently some caveats see [Issue #49](https://github.com/radar/by_star/issues/49):
299
+
300
+ ```ruby
301
+ Post.between_times(date1, date2)
302
+ ```
303
+
304
+ It will query records from `date1` (00:00:00 Hrs) until `date2` (23:59:59 Hrs).
305
+
306
+ ### before and after
307
+
308
+ To find all posts before / after the current time:
309
+
310
+ ```ruby
311
+ Post.before
312
+ Post.after
313
+ ```
314
+
315
+ To find all posts before certain time or date:
316
+
317
+ ```ruby
318
+ Post.before(Date.today + 2)
319
+ Post.after(Time.now + 5.days)
320
+ ```
321
+
322
+ You can also pass a string:
323
+
324
+ ```ruby
325
+ Post.before("next tuesday")
326
+ ```
327
+
328
+ For Time-Range type objects, only the start time is considered for `before` and `after`.
329
+
330
+ ### previous and next
331
+
332
+ To find the prior/subsequent record to a model instance, `previous`/`next` on it:
333
+
334
+ ```ruby
335
+ Post.last.previous
336
+ Post.first.next
337
+ ```
338
+
339
+ You can specify a field also:
340
+
341
+ ```ruby
342
+ Post.last.previous(field: "published_at")
343
+ Post.first.next(field: "published_at")
344
+ ```
345
+
346
+ For Time-Range type objects, only the start time is considered for `previous` and `next`.
347
+
348
+ ### by_year
349
+
350
+ To find records from the current year, simply call the method without any arguments:
351
+
352
+ ```ruby
353
+ Post.by_year
354
+ ```
355
+
356
+ To find records based on a year you can pass it a two or four digit number:
357
+
358
+ ```ruby
359
+ Post.by_year(09)
360
+ ```
361
+
362
+ This will return all posts in 2009, whereas:
363
+
364
+ ```ruby
365
+ Post.by_year(99)
366
+ ```
367
+
368
+ will return all the posts in the year 1999.
369
+
370
+ You can also specify the full year:
371
+
372
+ ```ruby
373
+ Post.by_year(2009)
374
+ Post.by_year(1999)
375
+ ```
376
+
377
+ ### by_month
378
+
379
+ If you know the number of the month you want:
380
+
381
+ ```ruby
382
+ Post.by_month(1)
383
+ ```
384
+
385
+ This will return all posts in the first month (January) of the current year.
386
+
387
+ If you like being verbose:
388
+
389
+ ```ruby
390
+ Post.by_month("January")
391
+ ```
392
+
393
+ This will return all posts created in January of the current year.
394
+
395
+ If you want to find all posts in January of last year just do
396
+
397
+ ```ruby
398
+ Post.by_month(1, year: 2007)
399
+ ```
400
+
401
+ or
402
+
403
+ ```ruby
404
+ Post.by_month("January", year: 2007)
405
+ ```
406
+
407
+ This will perform a find using the column you've specified.
408
+
409
+ If you have a Time object you can use it to find the posts:
410
+
411
+ ```ruby
412
+ Post.by_month(Time.local(2012, 11, 24))
413
+ ```
414
+
415
+ This will find all the posts in November 2012.
416
+
417
+ ### by_calendar_month
418
+
419
+ Finds records for a given month as shown on a calendar. Includes all the results of `by_month`, plus any results which fall in the same week as the first and last of the month. Useful for working with UI calendars which show rows of weeks.
420
+
421
+ ```ruby
422
+ Post.by_calendar_month
423
+ ```
424
+
425
+ Parameter behavior is otherwise the same as `by_month`. Also, `:start_day` option is supported to specify the start day of the week (`:monday`, `:tuesday`, etc.)
426
+
427
+ ### by_fortnight
428
+
429
+ Fortnight numbering starts at 0. The beginning of a fortnight is Monday, 12am.
430
+
431
+ To find records from the current fortnight:
432
+
433
+ ```ruby
434
+ Post.by_fortnight
435
+ ```
436
+
437
+ To find records based on a fortnight, you can pass in a number (representing the fortnight number) or a time object:
438
+
439
+ ```ruby
440
+ Post.by_fortnight(18)
441
+ ```
442
+
443
+ This will return all posts in the 18th fortnight of the current year.
444
+
445
+ ```ruby
446
+ Post.by_fortnight(18, year: 2012)
447
+ ```
448
+
449
+ This will return all posts in the 18th fortnight week of 2012.
450
+
451
+ ```ruby
452
+ Post.by_fortnight(Time.local(2012,1,1))
453
+ ```
454
+
455
+ This will return all posts from the first fortnight of 2012.
456
+
457
+ ### by_week and by_cweek
458
+
459
+ Week numbering starts at 0, and cweek numbering starts at 1 (same as `Date#cweek`). The beginning of a week is as defined in `ActiveSupport#beginning_of_week`, which can be configured.
460
+
461
+ To find records from the current week:
462
+
463
+ ```ruby
464
+ Post.by_week
465
+ Post.by_cweek # same result
466
+ ```
467
+
468
+ This will return all posts in the 37th week of the current year (remember week numbering starts at 0):
469
+
470
+ ```ruby
471
+ Post.by_week(36)
472
+ Post.by_cweek(37) # same result
473
+ ```
474
+
475
+ This will return all posts in the 37th week of 2012:
476
+
477
+ ```ruby
478
+ Post.by_week(36, year: 2012)
479
+ Post.by_cweek(37, year: 2012) # same result
480
+ ```
481
+
482
+ This will return all posts in the week which contains Jan 1, 2012:
483
+
484
+ ```ruby
485
+ Post.by_week(Time.local(2012,1,1))
486
+ Post.by_cweek(Time.local(2012,1,1)) # same result
487
+ ```
488
+
489
+ You may pass in a `:start_day` option (`:monday`, `:tuesday`, etc.) to specify the starting day of the week. This may also be configured in Rails.
490
+
491
+ ### by_weekend
492
+
493
+ If the time passed in (or the time now is a weekend) it will return posts from 0:00 Saturday to 23:59:59 Sunday. If the time is a week day, it will show all posts for the coming weekend.
494
+
495
+ ```ruby
496
+ Post.by_weekend(Time.now)
497
+ ```
498
+
499
+ ### by_day and today
500
+
501
+ To find records for today:
502
+
503
+ ```ruby
504
+ Post.by_day
505
+ Post.today
506
+ ```
507
+
508
+ To find records for a certain day:
509
+
510
+ ```ruby
511
+ Post.by_day(Time.local(2012, 1, 1))
512
+ ```
513
+
514
+ You can also pass a string:
515
+
516
+ ```ruby
517
+ Post.by_day("next tuesday")
518
+ ```
519
+
520
+ This will return all posts for the given day.
521
+
522
+ ### by_quarter
523
+
524
+ Finds records by 3-month quarterly period of year. Quarter numbering starts at 1. The four quarters of the year begin on Jan 1, Apr 1, Jul 1, and Oct 1 respectively.
525
+
526
+ To find records from the current quarter:
527
+
528
+ ```ruby
529
+ Post.by_quarter
530
+ ```
531
+
532
+ To find records based on a quarter, you can pass in a number (representing the quarter number) or a time object:
533
+
534
+ ```ruby
535
+ Post.by_quarter(4)
536
+ ```
537
+
538
+ This will return all posts in the 4th quarter of the current year.
539
+
540
+ ```ruby
541
+ Post.by_quarter(2, year: 2012)
542
+ ```
543
+
544
+ This will return all posts in the 2nd quarter of 2012.
545
+
546
+ ```ruby
547
+ Post.by_week(Time.local(2012,1,1))
548
+ ```
549
+
550
+ This will return all posts from the first quarter of 2012.
551
+
552
+ ## Version Support
553
+
554
+ ByStar is tested against the following versions:
555
+
556
+ * Ruby 2.0.0+
557
+ * Rails/ActiveRecord 3.2+
558
+ * Mongoid 3.1+
559
+
560
+ Note that ByStar automatically adds the following version compatibility shims:
561
+
562
+ * ActiveSupport 3.x: Add `Time/Date/DateTime#in_time_zone` (as an alias to `#to_time_in_current_zone`) for compatibility with Rails 4+.
563
+ * Mongoid 3.x: Adds `Criteria#reorder` method from Mongoid 4.
564
+
565
+
566
+ ## Testing
567
+
568
+ ### Test Setup
569
+
570
+ Specify a database by supplying a `DB` environmental variable:
571
+
572
+ ```bash
573
+ bundle exec rake spec DB=sqlite
574
+ ```
575
+
576
+ You can also take an ORM-specific test task for a ride:
577
+
578
+ ```bash
579
+ bundle exec rake spec:active_record
580
+ ```
581
+
582
+ Have an Active Record or Mongoid version in mind? Set the environment variables
583
+ `ACTIVE_RECORD_VERSION` and `MONGOID_VERSION` to a version of your choice. A
584
+ version number provided will translate to `~> VERSION`, and the string `master`
585
+ will grab the latest from Github.
586
+
587
+ ```bash
588
+ # Update your bundle appropriately...
589
+ ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle update
590
+
591
+ # ...then run the specs
592
+ ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle exec rpsec spec
593
+ ```
594
+
595
+ ### Test Implementation
596
+
597
+ ByStar tests use TimeCop to lock the system `Time.now` at Jan 01, 2014, and seed
598
+ objects with fixed dates according to `spec/fixtures/shared/seeds.rb`.
599
+ Note that the timezone is randomized on each run to shake-out timezone related quirks.
600
+
601
+
602
+ ## Collaborators
603
+
604
+ ByStar is actively maintained by Ryan Bigg (radar) and Johnny Shields (johnnyshields)
605
+
606
+ Thank you to the following people:
607
+
608
+ * Thomas Sinclair for the original bump for implementing ByStar
609
+ * [Ruby on Rails](http://rubyonrails.org/) for their support
610
+ * Mislav Marohnic
611
+ * August Lilleas (leethal)
612
+ * gte351s
613
+ * Sam Elliott (lenary)
614
+ * The creators of the [Chronic](https://github.com/mojombo/chronic) gem
615
+ * Erik Fonselius
616
+ * Johnny Shields (johnnyshields)