Created
May 20, 2011 23:43
-
-
Save tsnow/984025 to your computer and use it in GitHub Desktop.
For stackoverflow Q#4955355
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
db |
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
# This presumes that each day is seperate, and you don't actually want to expose | |
# the durations to the user, ie, for "take this week off" | |
# (thus the goofy name HoliDay which emphasizes the day-nature,) | |
# and also that the HTML-interface you want is checkboxes for :am_pm and :all_day, | |
# and :date as a normal rails-style date select list. |
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
#app/models/holi_day.rb | |
# The actual model logic that extends the general concept of a | |
# holiday into one which only handles single-day requests. | |
require 'holiday' | |
class HoliDay < Holiday | |
# This is ever so slightly a hack. | |
# We're going to use AR::Base's STI facility to transparently | |
# decorate these objects. There's no actual :type field in the db. | |
# The same functionality could be done with normal decorators as well. | |
# or even down in the Presenter class, as private methods. | |
#This is all model-db-level info, the view shouldn't know about this stuff: | |
ALL_DAY = [0.hours, 24.hours] | |
MORNING_EVENING = { | |
false => [9.hours,12.hours], #:pm? #=> false | |
true => [12.hours,17.hours] #:pm? #=> true | |
} | |
#Just building a queryable data structure here for our args mapping the booleans | |
# from the view to ranges of time in the db | |
ALL_DAY_MORNING_EVENING = { | |
true => {true => ALL_DAY, false => ALL_DAY}, | |
false => MORNING_EVENING | |
} | |
#Little bridge for the view-attributes | |
def pm? | |
start_time && (start_time.hour >= 12) #This should be equal to at least EVENING start, if not MORNING end, above. | |
end | |
def date | |
start_time && start_time.to_date | |
end | |
def normalize_holi_day_attrs(unchecked_args) | |
args = {:date => self.date || Date.today, :am_pm => true, :all_day => true}.merge(unchecked_args) | |
#am_pm and all_day need to be boolean, and we assume they're true by default | |
#date must be present | |
args | |
end | |
#Knows how to change the view-style data into the db-style range type | |
#Wat up MTV: Dis my data representation bridge, dis where da magic happen | |
def holi_day_attrs_to_duration(args={:date=>Date.today, :am_pm => true, :all_day => true}) | |
args = normalize_holi_day_attrs(args) | |
days = [args[:date].to_time,args[:date].to_time] #=>[Date.today, Date.today] | |
times = ALL_DAY_MORNING_EVENING[args[:all_day]][args[:am_pm]] #=>[9.hours, 12.hours] | |
a_duration = days.zip(times).map{|day,time| day+time} #=> [[Date.today, 9.hours],[Date.today, 12.hours]] #=> [today at 9, today at noon] | |
Range.new(*a_duration) | |
end | |
def duration=(arg) | |
if arg.is_a?(Hash) | |
self.duration = holi_day_attrs_to_duration(arg) | |
else | |
super(arg) | |
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
#app/helpers/holi_day_presenter.rb | |
require 'forwardable' | |
require 'holi_day' | |
class HoliDayPresenter | |
# A small proxy for the view that mediates between the view-tag-helpers and the model. | |
# You can use it like so in the controller: | |
# @holi_day = HoliDayPresenter.new() #quacks like a HoliDay as far as active_model goes. | |
# Or: | |
# @holi_day = HoliDayPresenter.new(HoliDay.find(params[:id])) #Though, it would be easy to make | |
# #HoliDayPresenter implement a #find | |
# #class_method, if you care. | |
# @holi_day.update_attributes(params[:holi_day]) | |
# @holi_day.valid? && @holi_day.save | |
# And the view _only_ knows the HoliDay via the presenter: | |
# <% semantic_form_for :holi_day, :object => @holi_day do |f| %> | |
# <%= f.input :am_pm %> <%#because these act like t/f attributes to the view %> | |
# <%= f.input :all_day %> | |
# <%= f.input :date %> | |
# <%= f.commit_button %> | |
# <% end %> | |
extend Forwardable | |
def initialize(atts={}) | |
@model= case atts | |
when HoliDay then | |
atts | |
when Hash then | |
HoliDay.new(self.durationize_atts(atts)) | |
else | |
HoliDay.new | |
end | |
end | |
#view-facing methods | |
def_delegator :@model, :pm?, :am_pm #makes :am_pm act like a t/f attribute | |
def_delegator :@model, :date | |
def_delegator :@model, :all_day?, :all_day #makes :all_day act like a t/f attribute | |
#Standard AR::Model bits here for error messages | |
def_delegator :@model, :valid? | |
def_delegator :@model, :save | |
def durationize_atts(att) | |
att = att.clone | |
others = att.slice!(:date, :all_day, :am_pm) | |
others.merge!({:duration => att}) #subtle: needs to exist so records initialize properly. | |
others | |
end | |
def update_attributes(att={}) | |
@model.update_attributes(self.durationize_atts(att)) | |
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
gem 'activerecord', '> 2.3.8','< 3.1' | |
gem 'activesupport', '> 2.3.8', '< 3.1' | |
require 'test/unit' | |
require 'active_record' | |
require 'active_support' | |
require 'forwardable' | |
ActiveRecord::Base.establish_connection({ | |
'adapter' => 'sqlite3', | |
'database' => 'db/holidays', | |
'timeout' => 5000 | |
}) | |
ActiveRecord::Migration.create_table 'holidays', :force => true do |h| #drops on create | |
h.datetime :start_time | |
h.datetime :finish_time | |
h.timestamps | |
end | |
$LOAD_PATH.push(File.dirname(__FILE__)) unless RUBY_VERSION < '1.9' | |
#test/unit/holi_day_test.rb | |
require 'active_support/core_ext/kernel/requires' | |
require 'active_support/test_case' | |
require 'active_record/test_case' | |
class HoliDayTest < Test::Unit::TestCase | |
extend ActiveSupport::Testing::Declarative | |
require 'holiday' | |
#Holiday | |
test "a holiday has a start_time and finish_time duration" do | |
t = Time.current | |
h=Holiday.new(:start_time => t, :finish_time => 4.days.from_now(t)) | |
assert_equal 4.days, h.duration.last - h.duration.first | |
end | |
test "updating a holiday duration" do | |
h=Holiday.new | |
t=Time.current | |
h.duration = Range.new(t, 4.days.from_now(t)) | |
assert_equal 4.days, h.finish_time - h.start_time | |
end | |
require 'holi_day' | |
#HoliDay | |
test "an all_day holi_day from the db's pov" do | |
h=HoliDay.new(:start_time => Date.today, :finish_time => 24.hours.from_now(Date.today)) | |
assert_equal [true], [h.all_day?] | |
end | |
test "a morning holi_day from the db's pov" do | |
h=HoliDay.new(:start_time => 9.hours.from_now(Date.today), :finish_time => 12.hours.from_now(Date.today)) | |
assert_equal [false,false, Date.today], [h.all_day?, h.pm?, h.date] | |
end | |
test "an evening holi_day from the db's pov" do | |
h=HoliDay.new(:start_time => 12.hours.from_now(Date.today), :finish_time => 24.hours.from_now(Date.today)) | |
assert_equal [false,true, Date.today], [h.all_day?, h.pm?, h.date] | |
end | |
test "handles presenter attr hash for duration" do | |
h=HoliDay.new(:start_time => Date.today, :finish_time => 24.hours.from_now(Date.today)) | |
yr = 1.year.from_now(Date.today) | |
h.duration = {:date => yr, :am_pm => true, :all_day => true} | |
assert_equal [true,false, yr.to_time - Date.today.to_time ], [h.all_day?, h.pm?, (h.date.to_time - Date.today.to_time) ] | |
end | |
require 'holi_day_presenter' | |
#HoliDayPresenter | |
#db hit | |
test "the presenter passes on relevant methods" do | |
h=HoliDay.new(:start_time => 12.hours.from_now(Date.today), :finish_time => 24.hours.from_now(Date.today)) | |
h_p = HoliDayPresenter.new(h) | |
assert_equal [false,true, Date.today, true, true], [h_p.all_day, h_p.am_pm, h_p.date, h_p.valid?, h_p.save] | |
end | |
#db hit | |
test "update_attributes handles other args as well as duration" do #This might need the other attrs' getters implemented as well. | |
h=HoliDay.new | |
h_p = HoliDayPresenter.new(h) | |
h_p.update_attributes(:am_pm => false, :all_day => false, | |
:created_at => 2.minutes.ago(Time.current)) | |
assert_equal [2, false,false, Date.today, true, true ], [Time.current.min - h.created_at.min, h_p.all_day, h_p.am_pm, h_p.date, h_p.valid?, h_p.save] | |
end | |
test "on today and all_day, is the default" do | |
h_p = HoliDayPresenter.new | |
assert_equal [true,false, Date.today], [h_p.all_day, h_p.am_pm, h_p.date] | |
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
#app/models/holiday.rb | |
class Holiday < ActiveRecord::Base | |
composed_of :duration, :mapping =>[[:start_time, :first],[:finish_time,:last]], :class_name => 'Range' | |
#note that this will work for days-long durations, db-wise. | |
def all_day? | |
finish_time && start_time && ((finish_time.to_time - start_time.to_time ).to_i % 1.day.to_i == 0) | |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment