• Stars
    star
    1,045
  • Rank 44,083 (Top 0.9 %)
  • Language
    Ruby
  • License
    MIT License
  • Created almost 16 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Lets you find ActiveRecord + Mongoid objects by year, month, fortnight, week and more!

ByStar

Build Status Code Climate

ByStar (by_*) allows you easily and reliably query ActiveRecord and Mongoid objects based on time.

Examples

Post.by_year(2013)                           # all posts in 2013
Post.before(Date.today)                      # all posts for before today
Post.yesterday                               # all posts for yesterday
Post.between_times(Time.zone.now - 3.hours,  # all posts in last 3 hours
                   Time.zone.now)
@post.next                                   # next post after a given post

Installation

Install this gem by adding this to your Gemfile:

gem 'by_star', git: 'https://github.com/radar/by_star'

Then run bundle install

If you are using ActiveRecord, you're done!

Mongoid users, please include the Mongoid::ByStar module for each model you wish to use the functionality. This is the convention among Mongoid plugins.

class MyModel
  include Mongoid::Document
  include Mongoid::ByStar

Finder Methods

Base Scopes

ByStar adds the following finder scopes (class methods) to your model to query time ranges. These accept a Date, Time, or DateTime object as an argument, which defaults to Time.zone.now if not specified:

Scope Meaning
between_times(start_time, end_time) Finds all records occurring between two given times.
between_dates(start_date, end_date) Finds all records occurring between two given dates, from beginning of start_date until end of end_date.
before(end_time) Finds all records occurring before the given time.
after(start_time) Finds all records occurring after the given time.
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)

between_times and between_dates supports alternate argument forms:

  • between_times(Range)
  • between_times(Array)
  • between_times(start_time, nil) - same as after(start_time)
  • between_times(nil, end_time) - same as before(end_time)

Time Range Scopes

ByStar adds additional shortcut scopes based on commonly used time ranges. See sections below for detailed argument usage of each:

Scope Meaning
by_day Query by a given date.
by_week Allows zero-based week value from 0 to 52.
by_cweek Allows one-based week value from 1 to 53.
by_weekend Saturday and Sunday only of the given week.
by_fortnight A two-week period, with the first fortnight of the year beginning on 1st January.
by_month Query by month. Allows integer arg, e.g. 11 for November.
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.
by_quarter 3-month intervals of the year.
by_year Query by year. Allows integer arg, e.g. 2017.

Relative Scopes

ByStar also adds scopes which are relative to the current time. Note the past_* and next_* methods represent a time distance from current time (Time.zone.now), and do not strictly end/begin evenly on a calendar week/month/year (unlike by_* methods which do.)

Scope Meaning
today Finds all occurrences on today's date.
yesterday Finds all occurrences on yesterday's date.
tomorrow Finds all occurrences on tomorrow's date.
past_day Prior 24-hour period from current time.
past_week Prior 7-day period from current time.
past_fortnight Prior 14-day period from current time.
past_month Prior 30-day period from current time.
past_year Prior 365-day period from current time.
next_day Subsequent 24-hour period from current time.
next_week Subsequent 7-day period from current time.
next_fortnight Subsequent 14-day period from current time.
next_month Subsequent 30-day period from current time.
next_year Subsequent 365-day period from current time.

Superlative Finders

Find the oldest or newest records. Returns an object instance (not a relation):

  • newest
  • oldest

Instance Methods

In addition, ByStar adds instance methods to return the next / previous record in the timewise sequence. Returns an object instance (not a relation):

  • object.next
  • object.previous

Kernel Extensions

ByStar extends the kernel Date, Time, and DateTime objects with the following instance methods, which mirror the ActiveSupport methods beginning_of_day, end_of_week, etc:

  • beginning_of_weekend
  • end_of_weekend
  • beginning_of_fortnight
  • end_of_fortnight
  • beginning_of_calendar_month
  • end_of_calendar_month

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.

Usage

Setting the Query Field

By default, ByStar assumes you will use the created_at field to query objects by time. You may specify an alternate field on all query methods as follows:

Post.by_month("January", field: :updated_at)

Alternatively, you may set a default in your model using the by_star_field macro:

class Post < ActiveRecord::Base
  by_star_field :updated_at
end

Scoping the Query

All ByStar methods (except oldest, newest, previous, next) return an ActiveRecord::Relation (or Mongoid::Criteria) which can be daisy-chained with other scopes/finder methods:

Post.by_month.your_scope
Post.by_month(1).include(:tags).where("tags.name" => "ruby")

Want to count records? Simple:

Post.by_month.count

Timezone Handling

ByStar date-range finders will use value of Time.zone to evaluate the args. This may cause unexpected behavior when use Time values in timezones other than Time.zone.

Time.zone = 'Australia/Sydney'
Post.by_day('2020-04-05 18:00:00 EST')
#=> Returns Apr 6th, 0:00 until Apr 6th, 23:59 in Sydney timezone.

:offset Option

All ByStar finders support an :offset option which is applied to time period of the query condition. This is useful in cases where the daily cycle occurs at a time other than midnight.

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:

Post.by_day('2014-03-05', offset: 9.hours)

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:

Time.zone = 'Australia/Sydney'
Post.by_day('2020-04-05', offset: 9.hours)
#=> Returns Apr 5th, 09:00 until Apr 6th, 08:59

Timespan Objects

If your object has both a start and end time, you may pass both params to by_star_field:

by_star_field :start_time, :end_time

By default, ByStar queries will return all objects whose range has any overlap within the desired period (permissive):

MultiDayEvent.by_month("January")
#=> returns MultiDayEvents that overlap in January,
#   even if they start in December and/or end in February

Timespan Objects: #at_time

To find all instances of a timespan object which contain a specific time:

Post.at_time(time)

This can be useful to find all currently active instances. Note that object instances which start exactly at the given time will be included in the result, but instances that end exactly at the given time will not be.

Timespan Objects: :strict Option

If you'd like to confine results to only those both starting and ending within the given range, use the :strict option:

MultiDayEvent.by_month("January", :strict => true)
#=> returns MultiDayEvents that both start AND end in January

Timespan Objects: Database Indexing and :index_scope Option

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.

Database indexes require querying a range query on a single field, i.e. start_time >= X and start_time <= Y. If we use a single-sided query, the database will iterate through all items either from the beginning or until the end of time. This poses a challenge for timespan-type objects which have two fields, i.e. start_time and end_time. There are two cases to consider:

  1. Timespan with :strict option, e.g. start_time >= X and end_time <= Y.

Given that this gem requires start_time >= end_time, we add the converse constraint start_time <= Y and end_time >= X to ensure both fields are double-sided, i.e. an index can be used on either field.

  1. Timespan without :strict option, e.g. "start_time < Y and end_time > X".

Here we need to add a condition start_time >= X to ensure start_time is bounded on both sides. To achieve this, we allow an :index_scope option which is the minimum "strict" bound on the querying range, in other words, it is an assumption about the maximum timespan of objects.

:index_scope supports multiple value types:

:index_scope Value Meaning
nil or false No constraint set; query will be one-sided (default, but not recommended)
Date or Time, etc. A fixed point in time
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.
Numeric Will be converted to seconds, then handled the same as ActiveSupport::Duration
:beginning_of_day (Symbol literal)
Proc<Range, Hash(options)> A proc which evaluates to one of the above types. Args are (start_time, end_time, options)

An example settings of :index_scope:

# The maximum possible object length is 5 hours.
by_star index_scope: 5.hours

# Objects are guaranteed to start within the same month, with some offset.
by_star index_scope: ->(start_time, end_time, options){ start_time.beginning_of_month + (options[:offset] || 0) }

# The maximum possible object length half the range being queried.
by_star index_scope: ->(start_time, end_time, options){ ((start_time - end_time)*0.5).seconds }

Chronic Support

If Chronic gem is present, it will be used to parse natural-language date/time strings in all ByStar finder methods. Otherwise, the Ruby Time.parse kernel method will be used as a fallback.

As of ByStar 2.2.0, you must explicitly include gem 'chronic' into your Gemfile in order to use Chronic.

Advanced Usage

between_times

To find records between two times:

Post.between_times(time1, time2)

You use a Range like so:

Post.between_times(time1..time2)

Also works with dates - WARNING: there are currently some caveats see Issue #49:

Post.between_times(date1, date2)

It will query records from date1 (00:00:00 Hrs) until date2 (23:59:59 Hrs).

before and after

To find all posts before / after the current time:

Post.before
Post.after

To find all posts before certain time or date:

Post.before(Date.today + 2)
Post.after(Time.now + 5.days)

You can also pass a string:

Post.before("next tuesday")

For Time-Range type objects, only the start time is considered for before and after.

previous and next

To find the prior/subsequent record to a model instance, previous/next on it:

Post.last.previous
Post.first.next

You can specify a field also:

Post.last.previous(field: "published_at")
Post.first.next(field: "published_at")

For Time-Range type objects, only the start time is considered for previous and next.

by_year

To find records from the current year, simply call the method without any arguments:

Post.by_year

To find records based on a year you can pass it a two or four digit number:

Post.by_year(09)

This will return all posts in 2009, whereas:

Post.by_year(99)

will return all the posts in the year 1999.

You can also specify the full year:

Post.by_year(2009)
Post.by_year(1999)

by_month

If you know the number of the month you want:

Post.by_month(1)

This will return all posts in the first month (January) of the current year.

If you like being verbose:

Post.by_month("January")

This will return all posts created in January of the current year.

If you want to find all posts in January of last year just do

Post.by_month(1, year: 2007)

or

Post.by_month("January", year: 2007)

This will perform a find using the column you've specified.

If you have a Time object you can use it to find the posts:

Post.by_month(Time.local(2012, 11, 24))

This will find all the posts in November 2012.

by_calendar_month

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.

Post.by_calendar_month

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.)

by_fortnight

Fortnight numbering starts at 0. The beginning of a fortnight is Monday, 12am.

To find records from the current fortnight:

Post.by_fortnight

To find records based on a fortnight, you can pass in a number (representing the fortnight number) or a time object:

Post.by_fortnight(18)

This will return all posts in the 18th fortnight of the current year.

Post.by_fortnight(18, year: 2012)

This will return all posts in the 18th fortnight week of 2012.

Post.by_fortnight(Time.local(2012,1,1))

This will return all posts from the first fortnight of 2012.

by_week and by_cweek

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.

To find records from the current week:

Post.by_week
Post.by_cweek  # same result

This will return all posts in the 37th week of the current year (remember week numbering starts at 0):

Post.by_week(36)
Post.by_cweek(37)  # same result

This will return all posts in the 37th week of 2012:

Post.by_week(36, year: 2012)
Post.by_cweek(37, year: 2012)  # same result

This will return all posts in the week which contains Jan 1, 2012:

Post.by_week(Time.local(2012,1,1))
Post.by_cweek(Time.local(2012,1,1))  # same result

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.

by_weekend

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.

Post.by_weekend(Time.now)

by_day and today

To find records for today:

Post.by_day
Post.today

To find records for a certain day:

Post.by_day(Time.local(2012, 1, 1))

You can also pass a string:

Post.by_day("next tuesday")

This will return all posts for the given day.

by_quarter

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.

To find records from the current quarter:

Post.by_quarter

To find records based on a quarter, you can pass in a number (representing the quarter number) or a time object:

Post.by_quarter(4)

This will return all posts in the 4th quarter of the current year.

Post.by_quarter(2, year: 2012)

This will return all posts in the 2nd quarter of 2012.

Post.by_week(Time.local(2012,1,1))

This will return all posts from the first quarter of 2012.

Version Support

ByStar is tested against the following versions:

  • Ruby 2.0.0+
  • Rails/ActiveRecord 3.2+
  • Mongoid 3.1+

Note that ByStar automatically adds the following version compatibility shims:

  • ActiveSupport 3.x: Add Time/Date/DateTime#in_time_zone (as an alias to #to_time_in_current_zone) for compatibility with Rails 4+.
  • Mongoid 3.x: Adds Criteria#reorder method from Mongoid 4.

Testing

Test Setup

Specify a database by supplying a DB environmental variable:

bundle exec rake spec DB=sqlite

You can also take an ORM-specific test task for a ride:

bundle exec rake spec:active_record

Have an Active Record or Mongoid version in mind? Set the environment variables ACTIVE_RECORD_VERSION and MONGOID_VERSION to a version of your choice. A version number provided will translate to ~> VERSION, and the string master will grab the latest from Github.

# Update your bundle appropriately...
ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle update

# ...then run the specs
ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle exec rpsec spec

Test Implementation

ByStar tests use TimeCop to lock the system Time.now at Jan 01, 2014, and seed objects with fixed dates according to spec/fixtures/shared/seeds.rb. Note that the timezone is randomized on each run to shake-out timezone related quirks.

Collaborators

ByStar is actively maintained by Ryan Bigg (radar) and Johnny Shields (johnnyshields)

Thank you to the following people:

  • Thomas Sinclair for the original bump for implementing ByStar
  • Ruby on Rails for their support
  • Mislav Marohnic
  • August Lilleas (leethal)
  • gte351s
  • Sam Elliott (lenary)
  • The creators of the Chronic gem
  • Erik Fonselius
  • Johnny Shields (johnnyshields)

More Repositories

1

guides

Guides for Ruby and Elixir and whatever else I feel like
Ruby
1,528
star
2

distance_of_time_in_words

Better distance of time in words for Rails
Ruby
521
star
3

humanize

Takes your numbers and makes them *fancy*.
Ruby
466
star
4

rboard

A fully featured forum system compatible with Rails 2.3
Ruby
293
star
5

joyofelixir

A gentle introduction to the Elixir programming language
HTML
139
star
6

twist-v2

A book review tool for Leanpub's Markdown Book Format
Ruby
127
star
7

twist

Book review application for LeanPub's Markdown format
Ruby
103
star
8

elastic

A thin veneer over HTTPotion that talks to Elastic Search
Elixir
65
star
9

mtg

Magic: The Gathering, but in Ruby
Ruby
63
star
10

railsbot

The #rubyonrails channel bot
PLSQL
47
star
11

summer

A Tiny IRC Bot framework
Ruby
43
star
12

houser

Lightweight multitenancy gem for Rails 3 and Rails 4
Ruby
32
star
13

chronic

Elixir
30
star
14

saas_book_examples

Ruby
27
star
15

searcher

Fun experiment in parsing search query strings (like Pivotal Tracker does)
Ruby
25
star
16

forem.heroku.com

The application that contains the forem engine example
Ruby
22
star
17

lookup

Lookup for Ruby & Rails api in the terminal
Ruby
20
star
18

surveyor-2

Culture Amp Junior Engineering Program - 2018 Coding Test
Ruby
19
star
19

ryanbigg.com

Jekyll-powered version of ryanbigg.com
HTML
18
star
20

elixir-irc

Naive implementation of an IRC server in Elixir
Elixir
17
star
21

postbin

Postbin written in Sinatra
Ruby
16
star
22

ordinalize

Ordinalize your numbers (like humanize)
Ruby
15
star
23

chirper

Ruby
15
star
24

toy_robot_elixir

Code from leanpub.com/elixir-toyrobot
Elixir
13
star
25

writing-an-interpreter-in-elixir

Elixir
12
star
26

asciidoc-toolchain

Ruby
11
star
27

bix

Ruby
11
star
28

screencasts

Code for the screencasts I record
Ruby
10
star
29

forem-redcarpet

Provides Recarpet markup (with syntax highlighting by pygments.rb) for Forem posts}
Ruby
8
star
30

logs

JavaScript
7
star
31

maintaining-rails-examples

Ruby
6
star
32

exploding-rails-rom-dry-example-app

Ruby
6
star
33

setup

Go
6
star
34

exploding-rails-examples

Ruby
6
star
35

fans

Simple application showing how to do models for user's "favouriting" other users.
Ruby
5
star
36

graphql_book_examples

The code from the book "GraphQL for Rails Developers"
Ruby
5
star
37

jot

A light wrapper around the JWT gem
Ruby
5
star
38

banana

A warden example app
Ruby
4
star
39

find_by_hash

Extension to ActiveRecord to allow you to do findy-thingies with hashes
Ruby
4
star
40

logs.ex

Phoenix application for displaying IRC logs from some select channels
Elixir
4
star
41

numero

Numero language
Ruby
4
star
42

surveyor

Culture Amp's Junior Engineering Program - 2017 Coding Test
Ruby
4
star
43

phoenix-react-websocket-todo

Elixir
3
star
44

debugging_book_examples

Debugging Book Example Code
Ruby
3
star
45

phoenix-views-example

Elixir
3
star
46

cartify

REST / GraphQL demo
Ruby
3
star
47

rails-init

Go
3
star
48

wedding

App for managing wedding seating plan
Ruby
3
star
49

forem-rdiscount

RDiscount formatting support for Forem
Ruby
3
star
50

logs.ex-legacy

The old logs.ex.
JavaScript
3
star
51

promo

Toy project of building a promotion system in Elixir
Elixir
2
star
52

service-vic-qr-code

TypeScript
2
star
53

scrivener_elastic

Elixir
2
star
54

rubyonrailsbook.com

JavaScript
2
star
55

activitypub-spike

Ruby
2
star
56

epub_gen

A small RubyGem to generate epubs
Ruby
2
star
57

rails3book_test

Nothing to see here, move along.
2
star
58

just

For when you're just visiting (someone else's computer)
Ruby
2
star
59

logs-react

JavaScript
1
star
60

buildkite-notifier

Ruby
1
star
61

surveyor-rom

Ruby
1
star
62

http-test

Ruby
1
star
63

trickery

Ruby
1
star
64

yaml-path-finder

CSS
1
star
65

barefoot

JavaScript
1
star
66

webpacker-typescript-css-modules

Ruby
1
star
67

selector

Ruby
1
star
68

jep-mumble

Ruby
1
star
69

toy_robot_example

Ruby
1
star
70

jep-toy-robot

Elixir
1
star
71

rails-init-homebrew-tap

Ruby
1
star
72

sri

A tool to generate Sub-Resource Integrity hashes from the terminal
Ruby
1
star
73

auth_me

Ruby
1
star
74

webpack-example

Ruby
1
star