-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit fab25e0
Showing
11 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2008 Justin French | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
h1. ActiveTime | ||
|
||
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. | ||
|
||
A year has many posts, a month has many comments, etc. | ||
|
||
h2. A few quick examples: | ||
|
||
<pre> | ||
# All Posts created in 2008 | ||
ActiveTime.new(2008).posts | ||
|
||
# All Users created on November 15, 2008 | ||
ActiveTime.new(2008, 11, 15).users | ||
|
||
# All Comments created between two specific Times | ||
ActiveTime.new(1.year.ago.utc, Time.now.utc).comments | ||
|
||
# You aren't stuck with created_at either | ||
ActiveTime.new(2008).posts(:published_at) | ||
|
||
# It's a named_scope under the hood, so you can do normal stuff: | ||
ActiveTime.new(2008).posts.public.newest_first.paginate | ||
</pre> | ||
|
||
h2. Installation | ||
|
||
./script/plugin install git://github.com/justinfrench/active_time.git | ||
|
||
h2. Why? | ||
|
||
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. | ||
|
||
h2. How? | ||
|
||
Firstly, the plugin adds a named scope to ActiveRecord::Base (so it's added to all your AR classes) 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: | ||
|
||
<pre> | ||
Post.in_date_range(1.year.ago.utc, Time.now_utc) | ||
Post.in_date_range(1.year.ago.utc, Time.now_utc, :published_at) | ||
</pre> | ||
|
||
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: | ||
|
||
<pre> | ||
Post.in_date_range(1.year.ago.utc, Time.now_utc) | ||
ActiveTime.new(1.year.ago.utc, Time.now.utc).posts | ||
</pre> | ||
|
||
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). | ||
|
||
<pre> | ||
ActiveTime.new(params[:year]).posts | ||
ActiveTime.new(params[:year], params[:month]).posts | ||
</pre> | ||
|
||
h2. And there's more! | ||
|
||
<pre> | ||
# Get the calculated start and end times: | ||
ActiveTime.new(2008).starting # => Tue Jan 01 00:00:00 UTC 2008 | ||
ActiveTime.new(2008).ending # => Wed Dec 31 23:59:59 UTC 2008 | ||
|
||
# Get a description of the range | ||
ActiveTime.new(2008) # => in 2008 | ||
ActiveTime.new(2008, 11) # => in November 2008 | ||
ActiveTime.new(2008, 11, 18) # => on November 18 2008 | ||
ActiveTime.new(2008, 11, 18, 14) # => from 14:00 to 15:00 on November 18 2008 | ||
ActiveTime.new(2008, 11, 18, 14, 18) # => from 14:18 to 14:19 on November 18 2008 | ||
ActiveTime.new(2008, 11, 18, 14, 18, 22) # => from 14:18:22 to 14:19:23 on November 18 2008 | ||
</pre> | ||
|
||
|
||
h2. Status | ||
|
||
I wrote it and published it to Github in a few hours, so it's really really fresh and not battle tested at all. Working on it! | ||
|
||
|
||
h2. Next | ||
|
||
* tests! | ||
* tidy up the code a bit | ||
* generate and publish the rdoc | ||
* support hour, minute and second | ||
* provide Day, Year and Month wrapper classes | ||
* anything | ||
* parse more date formats for input | ||
* figure out if I need to care about time zones (everything assumes UTC right now) | ||
* figure out if I care about localization | ||
* figure out if I can replace the method_missing magic with something more declarative | ||
|
||
|
||
h2. Project Info | ||
|
||
"ActiveTime is hosted on Github":http://github.com/justinfrench/active_time/, where your contributions, forkings, comments and feedback are greatly welcomed. | ||
|
||
|
||
Copyright (c) 2008 Justin French, released under the MIT license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
require 'rake' | ||
require 'rake/testtask' | ||
require 'rake/rdoctask' | ||
|
||
desc 'Default: run unit tests.' | ||
task :default => :test | ||
|
||
desc 'Test the active_time plugin.' | ||
Rake::TestTask.new(:test) do |t| | ||
t.libs << 'lib' | ||
t.libs << 'test' | ||
t.pattern = 'test/**/*_test.rb' | ||
t.verbose = true | ||
end | ||
|
||
desc 'Generate documentation for the active_time plugin.' | ||
Rake::RDocTask.new(:rdoc) do |rdoc| | ||
rdoc.rdoc_dir = 'rdoc' | ||
rdoc.title = 'ActiveTime' | ||
rdoc.options << '--line-numbers' << '--inline-source' | ||
rdoc.rdoc_files.include('README') | ||
rdoc.rdoc_files.include('lib/**/*.rb') | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
require 'active_time' | ||
require 'active_record_in_date_range_scope_extension' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Install hook code here |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# A new named_scope <tt>in_date_range</tt> is added to ActiveRecord::Base so that all AR classes | ||
# will have a suitable scope for the has_many-style associations. You can also call it directly: | ||
# | ||
# start_date = Time.gm(2006) | ||
# end_date = Time.gm(2007) | ||
# Post.in_date_range(start_date, end_date) | ||
# # => returns all posts with a created_at between start_date and end_date | ||
class ActiveRecord::Base | ||
named_scope :in_date_range, lambda { |start_date, end_date, column_name| { | ||
:conditions => ["#{column_name} between ? and ?", start_date.to_s(:db), end_date.to_s(:db)] | ||
} } | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
class ActiveTime | ||
|
||
attr_accessor :starting, :ending | ||
alias_method :time, :starting | ||
|
||
# Creates an object representing a period of time with a starting and ending | ||
# time. There's a few ways to create the ActiveTime object: | ||
# | ||
# # A whole year (eg Tue Jan 01 00:00:00 UTC 2008 - Wed Dec 31 23:59:59 UTC 2008): | ||
# ActiveTime.new(2008) | ||
# | ||
# # A whole month (eg Sat Nov 01 00:00:00 UTC 2008 - Sun Nov 30 23:59:59 UTC 2008): | ||
# ActiveTime.new(2008, 11) | ||
# | ||
# # A whole day (eg Wed Nov 12 00:00:00 UTC 2008 - Wed Nov 12 23:59:59 UTC 2008) | ||
# ActiveTime.new(2008, 11, 12) | ||
# | ||
# # Any two Time objects: | ||
# ActiveTime.new(Time.now.beginning_of_day, Time.now.end_of_day) | ||
# | ||
# TODO: Allow an hour, minute and even second. | ||
def initialize(*args) | ||
case args.first | ||
when Fixnum, String | ||
@year, @month, @day, @hour, @min, @sec = *args | ||
@starting = Time.gm(year, month, day) | ||
@ending = @starting.send("end_of_#{range}") | ||
when Time | ||
raise(ArgumentError, "both a starting and ending time must be supplied") unless (args[1] && args[1].is_a?(Time)) | ||
@starting, @ending = *args | ||
else | ||
raise ArgumentError, "arguments must either be (starting_time, ending_time) or (year,[month,[day],[hour],[min],[sec]])" | ||
end | ||
end | ||
|
||
# A symbol representing the range: | ||
# | ||
# * :custom if no year was supplied (Time objects were supplied instead) | ||
# * :year if no month is supplied | ||
# * :month if no day is supplied | ||
# * :day if year, month and day are supplied | ||
# * :custom if a specific start and end Time were supplied | ||
def range | ||
return :custom if @year.nil? | ||
return :year if @month.nil? | ||
return :month if @day.nil? | ||
return :day if @hour.nil? | ||
return :hour | ||
end | ||
memoize :range | ||
|
||
# Allows association calls similar to has_many to be called on the ActiveTime object, with the | ||
# missing method name used to infer the class name (posts => Post). We then call in_date_range | ||
# (a named scope which is expected) on the class to get all objects within the date range. | ||
# | ||
# Example method, given a Post class: | ||
# | ||
# def posts | ||
# Post.in_date_range(starting, ending, :created_at) | ||
# end | ||
# | ||
# TODO: memoize the results, to avoid multiple queries for the same method call. | ||
def method_missing(method_name, *args) | ||
if method_name.to_s =~ /[a-z_]+s$/ | ||
args[0] ||= :created_at | ||
begin | ||
klass_name = method_name.to_s.singularize.classify | ||
klass = klass_name.constantize | ||
return klass.in_date_range(starting, ending, args[0]) # Post.in_date_range(start_time, end_time, :created_at) | ||
rescue NoMethodError | ||
raise NoMethodError, "expected #{klass_name} to have a named scoped of 'in_date_range'" | ||
rescue NameError | ||
raise NameError, "called #{method_name} on an ActiveTime object, but could not find a #{klass_name} class" | ||
end | ||
else | ||
super | ||
end | ||
end | ||
|
||
# Provides a human friendly string description of the date or time range being used. Examples: | ||
# | ||
# ActiveTime.new(2008) # => "in 2008" | ||
# ActiveTime.new(2008, 11) # => "in November 2008" | ||
# ActiveTime.new(2008, 11, 18) # => "on November 18 2008" | ||
# ActiveTime.new(2008, 11, 18, 14) # => "from 14:00 to 15:00 on November 18 2008" | ||
# ActiveTime.new(2008, 11, 18, 14, 18) # => "from 14:18 to 14:19 on November 18 2008" | ||
# ActiveTime.new(2008, 11, 18, 14, 18, 22) # => "from 14:18:22 to 14:19:23 on November 18 2008" | ||
# | ||
# TODO: could be a whole lot DRYer and configurable | ||
def description | ||
case range | ||
when :year | ||
"in #{starting.year}" # in 2008 | ||
when :month | ||
"in #{starting.strftime("%B %Y")}" # in November 2008 | ||
when :day | ||
"on #{starting.strftime("%B %d, %Y")}" # on November 18, 2008 | ||
when :hour, :min, :sec | ||
"between #{starting.strftime("%H:%M")} and #{ending.strftime("%H:%M")} on #{starting.strftime("%B %d, %Y")}" # between 22:00 and 23:00 on November 18, 2008 | ||
when :sec | ||
"between #{starting.strftime("%H:%M:%S")} and #{ending.strftime("%H:%M:%S")} on #{starting.strftime("%B %d, %Y")}" # between 22:18 and 23:19 on November 18, 2008 | ||
else | ||
"between #{starting.strftime("%B %d %Y %H:%M:%S")} and #{starting.strftime("%B %d %Y %H:%M:%S")}" # from 14:18:22 to 14:19:23 on November 18 2008 | ||
end | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# desc "Explaining what the task does" | ||
# task :active_time do | ||
# # Task goes here | ||
# end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
require 'test_helper' | ||
|
||
class ActiveTimeTest < ActiveSupport::TestCase | ||
# Replace this with your real tests. | ||
test "the truth" do | ||
assert true | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require 'rubygems' | ||
require 'active_support' | ||
require 'active_support/test_case' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Uninstall hook code here |