ActiveTime is a Rails plugin that provides a parent object for has_many-ish associations to other ActiveRecord classes, but instead of a foreign key to scope the queries, a date range is used instead.
While this isn’t exactly how it all works, this should help visualize what I’m aiming for:
class Year has_many :posts has_many :comments end
# All Posts created in 2008 Year.new(2008).posts # All Posts created in the current year Year.new(Time.now) # All Comments created in November 2008 Month.new(2008, 11).comments # All Comments created in the current month Month.new(Time.now).comments # All Users created on November 15, 2008 Day.new(2008, 11, 15).users # All Users created in the current day Day.new(Time.now).users # You aren't stuck with created_at either Year.new(2008).posts(:published_at) # ActiveTime can be used instead ActiveTime.new(2008).posts # Which is helpful when you want a range between two specific dates/times ActiveTime.new(1.year.ago.utc, Time.now.utc).comments # And it's just a wrapper around a named_scope under the hood, so you can do normal stuff: Year.new(2008).posts.public.newest_first.paginate
./script/plugin install git://github.com/justinfrench/activetime.git
I’m not sure why you need it, but I’m building some RESTful controllers that need to present resources in a hierarchy based on dates (rather than a typical has_many/belongs_to association), so I wanted a model for the resource, upon which I could do the usual ActiveRecord has_many associations, scope chaining, pagination, etc. I wanted something that felt like ActiveRecord a bit.
Firstly, the plugin adds a named scope to ActiveRecord::Base (so it’s added to all your AR models) called in_date_range, which you can use directly if needed. It takes two Time objects in UTC as arguments (start time, end time) and optionally, a third argument that specifies which column the date range applies to (the default is :created_at). Examples:
Post.in_date_range(1.year.ago.utc, Time.now_utc) Post.in_date_range(1.year.ago.utc, Time.now_utc, :published_at)
If you have two Time objects for the start and end of the range, ActiveTime is simply a wrapper around this named scope. These both result in the same database query:
Post.in_date_range(1.year.ago.utc, Time.now_utc) ActiveTime.new(1.year.ago.utc, Time.now.utc).posts
Given that the second version is two characters longer, it’s not all that impressive, but what I really needed was to pass in just the start date, or part of one (like params[:year]) and automatically figure out what range I wanted (a whole year, a month, a day).
Year.new(params[:year]).posts Month.new(params[:year], params[:month]).posts
# Get the calculated start and end times: Year.new(2008).starting # => Tue Jan 01 00:00:00 UTC 2008 Year.new(2008).ending # => Wed Dec 31 23:59:59 UTC 2008 # Get a description of the range Year.new(2008) # => in 2008 Month.new(2008, 11) # => in November 2008 Day.new(2008, 11, 18) # => on November 18 2008
It’s pretty fresh and not battle tested yet, but the test suite is pretty solid. There’s heaps more to come, but what’s there right now works great.
- tidy up the code a bit
- generate and publish the rdoc
- support hour, minute and second (Rails doesn’t provide #at_end_of_hour out of the box, so I’m just being lazy so far)
- parse more date formats for input
- time zones? (everything assumes UTC right now)
- localization / internationalization?
- anything better than method_missing magic (something more declarative)?
ActiveTime is hosted on Github, where your contributions, forkings, comments and feedback are greatly welcomed.
Copyright © 2008 Justin French, released under the MIT license.