acts_as_span 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ coverage
2
+ rdoc
3
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in acts_as_span.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec'
7
+ gem 'acts_as_fu'
data/Gemfile.lock ADDED
@@ -0,0 +1,47 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ acts_as_span (0.0.5)
5
+ activerecord (>= 3)
6
+ activesupport
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ activemodel (3.0.10)
12
+ activesupport (= 3.0.10)
13
+ builder (~> 2.1.2)
14
+ i18n (~> 0.5.0)
15
+ activerecord (3.0.10)
16
+ activemodel (= 3.0.10)
17
+ activesupport (= 3.0.10)
18
+ arel (~> 2.0.10)
19
+ tzinfo (~> 0.3.23)
20
+ activesupport (3.0.10)
21
+ acts_as_fu (0.0.7.2)
22
+ activerecord
23
+ sqlite3-ruby
24
+ arel (2.0.10)
25
+ builder (2.1.2)
26
+ diff-lcs (1.1.2)
27
+ i18n (0.5.0)
28
+ rspec (2.6.0)
29
+ rspec-core (~> 2.6.0)
30
+ rspec-expectations (~> 2.6.0)
31
+ rspec-mocks (~> 2.6.0)
32
+ rspec-core (2.6.4)
33
+ rspec-expectations (2.6.0)
34
+ diff-lcs (~> 1.1.2)
35
+ rspec-mocks (2.6.0)
36
+ sqlite3 (1.3.4)
37
+ sqlite3-ruby (1.3.3)
38
+ sqlite3 (>= 1.3.3)
39
+ tzinfo (0.3.29)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ acts_as_fu
46
+ acts_as_span!
47
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Annkissam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,33 @@
1
+ = acts_as_span
2
+
3
+ ActiveRecord model w/ a start_date and an end_date
4
+
5
+ == Getting Started
6
+
7
+ In your Gemfile:
8
+
9
+ gem "acts_as_span"
10
+
11
+ In your model:
12
+
13
+ class SpanRecord < ActiveRecord::Base
14
+ acts_as_span
15
+ end
16
+
17
+ In your migrations:
18
+
19
+ class AddSpanToSpanRecord < ActiveRecord::Migration
20
+ def self.up
21
+ add_column :span_records, :start_date, :date
22
+ add_column :span_records, :end_date, :date
23
+ end
24
+
25
+ def self.down
26
+ remove_column :span_records, :start_date
27
+ remove_column :span_records, :end_date
28
+ end
29
+ end
30
+
31
+ == Copyright
32
+
33
+ Copyright (c) 2011 Annkissam. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "acts_as_span/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "acts_as_span"
7
+ s.version = ActsAsSpan::VERSION::STRING
8
+ s.authors = ["Eric Sullivan"]
9
+ s.email = ["eric.sullivan@annkissam.com"]
10
+ s.homepage = "https://github.com/annkissam/acts_as_span"
11
+ s.summary = ActsAsSpan::VERSION::SUMMARY
12
+ s.description = %q{start_date and end_date as a span w/ ActiveRecord}
13
+
14
+ s.rubyforge_project = "acts_as_span"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency(%q<activerecord>, [">= 3"])
22
+ s.add_dependency('activesupport')
23
+ end
@@ -0,0 +1,161 @@
1
+ require 'active_support'
2
+ require 'ostruct'
3
+ require 'forwardable'
4
+
5
+ ACTS_AS_SPAN_PATH = File.dirname(__FILE__) + "/acts_as_span/"
6
+
7
+ require ACTS_AS_SPAN_PATH + 'version'
8
+ require ACTS_AS_SPAN_PATH + 'span_klass'
9
+ require ACTS_AS_SPAN_PATH + 'span_instance'
10
+
11
+ module ActsAsSpan
12
+ extend ActiveSupport::Concern
13
+
14
+ class << self
15
+ def options
16
+ @options ||= {
17
+ :start_date_field => :start_date,
18
+ :end_date_field => :end_date,
19
+ :start_date_field_required => false,
20
+ :end_date_field_required => false,
21
+ :span_overlap_scope => nil,
22
+ :span_overlap_count => nil,
23
+ :name => :default
24
+ }
25
+ end
26
+
27
+ def configure
28
+ yield(self) if block_given?
29
+ end
30
+ end
31
+
32
+ #by default, all model classess & their instances will return false w/ acts_as_span?
33
+ included do
34
+ self.send(:extend, ActsAsSpan::NegativeMethods)
35
+ self.send(:include, ActsAsSpan::NegativeMethods)
36
+ end
37
+
38
+ module ClassMethods
39
+ def acts_as_span(*args)
40
+ #this model & its instances will return true w/ acts_as_span?
41
+ self.send(:extend, ActsAsSpan::PositiveMethods)
42
+ self.send(:include, ActsAsSpan::PositiveMethods)
43
+
44
+ self.send(:extend, Forwardable)
45
+
46
+ self.send(:extend, ActsAsSpan::ExtendedClassMethods)
47
+ self.send(:include, ActsAsSpan::IncludedInstanceMethods)
48
+
49
+ options = OpenStruct.new(args.last.is_a?(Hash) ? ActsAsSpan.options.merge(args.pop) : ActsAsSpan.options)
50
+
51
+ #if span_overlap_scope is specified the span_overlap_count defaults to 0
52
+ options.span_overlap_count ||= 0 if options.span_overlap_scope
53
+
54
+ acts_as_span_definitions[options.name] = options
55
+
56
+ def_delegators :span, :close!,
57
+ :close_on!,
58
+ :span_status,
59
+ :span_status_on,
60
+ :span_status_to_s,
61
+ :span_status_to_s_on,
62
+ :current?,
63
+ :current_on?,
64
+ :future?,
65
+ :future_on?,
66
+ :expired?,
67
+ :expired_on?
68
+
69
+ def_delegators 'self.class', :acts_as_span_definitions
70
+
71
+ class << self
72
+ self.send(:extend, Forwardable)
73
+
74
+ def_delegators :span, :current,
75
+ :current_on,
76
+ :future,
77
+ :future_on,
78
+ :expired,
79
+ :expired_on
80
+ end
81
+
82
+ validate :validate_spans
83
+ end
84
+
85
+ def acts_as_span_definitions
86
+ @_acts_as_span_definitions ||= {}
87
+ end
88
+ end
89
+
90
+ module ExtendedClassMethods
91
+ def overlap(test_record)
92
+ overlap_for(test_record, :default, :default)
93
+ end
94
+
95
+ def overlap_for(test_record, test_record_span_name = :default, this_span_name = :default)
96
+ span_for(this_span_name).overlap(test_record.span_for(test_record_span_name))
97
+ end
98
+
99
+ def spans
100
+ acts_as_span_definitions.keys.map { |acts_as_span_definition_name| span_for(acts_as_span_definition_name) }
101
+ end
102
+
103
+ def span
104
+ span_for(:default)
105
+ end
106
+
107
+ def span_for(name = :default)
108
+ acts_as_span_klasses[name] ||= SpanKlass.new(name, self, acts_as_span_definitions[name])
109
+ end
110
+
111
+ def acts_as_span_klasses
112
+ @_acts_as_span_klasses ||= {}
113
+ end
114
+ end
115
+
116
+ module IncludedInstanceMethods
117
+ def spans
118
+ acts_as_span_definitions.keys.map { |acts_as_span_definition_name| span_for(acts_as_span_definition_name) }
119
+ end
120
+
121
+ def span
122
+ span_for(:default)
123
+ end
124
+
125
+ def span_for(name = :default)
126
+ acts_as_span_instances[name] ||= SpanInstance.new(name, self, acts_as_span_definitions[name])
127
+ end
128
+
129
+ def acts_as_span_instances
130
+ @_acts_as_span_instances ||= {}
131
+ end
132
+
133
+ def validate_spans
134
+ spans.each(&:validate)
135
+ end
136
+
137
+ #This syntax assumes :default span
138
+ def overlap?(other_record)
139
+ overlap_for?(other_record, :default, :default)
140
+ end
141
+
142
+ #record.span_for(:this_span_name).overlap?(other_record.span_for(:other_record_span_name))
143
+ def overlap_for?(other_record, this_span_name = :default, other_record_span_name = :default)
144
+ span_for(this_span_name).overlap?(other_record.span_for(other_record_span_name))
145
+ end
146
+ end
147
+
148
+ module PositiveMethods
149
+ send(:define_method, "acts_as_span?") do
150
+ true
151
+ end
152
+ end
153
+
154
+ module NegativeMethods
155
+ send(:define_method, "acts_as_span?") do
156
+ false
157
+ end
158
+ end
159
+ end
160
+
161
+ ActiveRecord::Base.send(:include, ActsAsSpan)
@@ -0,0 +1,53 @@
1
+ require 'forwardable'
2
+
3
+ require ACTS_AS_SPAN_PATH + 'span_instance/validations'
4
+ require ACTS_AS_SPAN_PATH + 'span_instance/status'
5
+ require ACTS_AS_SPAN_PATH + 'span_instance/overlap'
6
+
7
+ module ActsAsSpan
8
+ class SpanInstance
9
+ extend Forwardable
10
+
11
+ include ActsAsSpan::SpanInstance::Validations
12
+ include ActsAsSpan::SpanInstance::Status
13
+ include ActsAsSpan::SpanInstance::Overlap
14
+
15
+ def_delegators :@acts_as_span_definition, :start_date_field,
16
+ :end_date_field,
17
+ :start_date_field_required,
18
+ :end_date_field_required,
19
+ :exclude_end,
20
+ :span_overlap_scope,
21
+ :span_overlap_count
22
+
23
+ def_delegators :span_model, :new_record?
24
+
25
+ attr_reader :name, :span_model, :acts_as_span_definition
26
+
27
+ def initialize(name, span_model, acts_as_span_definition)
28
+ @name = name
29
+ @span_model = span_model
30
+ @acts_as_span_definition = acts_as_span_definition
31
+ end
32
+
33
+ def span_klass
34
+ @span_klass ||= span_model.class
35
+ end
36
+
37
+ def start_date
38
+ span_model[start_date_field]
39
+ end
40
+
41
+ def end_date
42
+ span_model[end_date_field]
43
+ end
44
+
45
+ def close!(close_date = Date.today)
46
+ if end_date.blank?
47
+ span_model.update_attributes!(end_date_field => close_date)
48
+ end
49
+ end
50
+
51
+ alias_method :close_on!, :close!
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ require 'active_support'
2
+
3
+ module ActsAsSpan
4
+ class SpanInstance
5
+ module Overlap
6
+ extend ActiveSupport::Concern
7
+
8
+ module InstanceMethods
9
+ #http://stackoverflow.com/questions/699448/ruby-how-do-you-check-whether-a-range-contains-a-subset-of-another-range
10
+ #start_date <= record_end_date && record_start_date <= end_date
11
+ def overlap?(other_span)
12
+ (start_date.nil? || other_span.end_date.nil? || start_date <= other_span.end_date) && (end_date.nil? || other_span.start_date.nil? || other_span.start_date <= end_date)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ require 'active_support'
2
+
3
+ module ActsAsSpan
4
+ class SpanInstance
5
+ module Status
6
+ extend ActiveSupport::Concern
7
+
8
+ module InstanceMethods
9
+ def span_status(query_date = Date.today)
10
+ if future?(query_date)
11
+ :future
12
+ elsif expired?(query_date)
13
+ :expired
14
+ elsif current?(query_date)
15
+ :current
16
+ else
17
+ :unknown
18
+ end
19
+ end
20
+
21
+ alias_method :span_status_on, :span_status
22
+
23
+ def span_status_to_s(query_date = Date.today)
24
+ case span_status(query_date)
25
+ when :future
26
+ "Future"
27
+ when :expired
28
+ "Expired"
29
+ when :current
30
+ "Current"
31
+ when :unknown
32
+ "Unknown"
33
+ end
34
+ end
35
+
36
+ alias_method :span_status_to_s_on, :span_status_to_s
37
+
38
+ def current?(query_date = Date.today)
39
+ !future?(query_date) && !expired?(query_date)
40
+ end
41
+
42
+ alias_method :current_on?, :current?
43
+
44
+ def future?(query_date = Date.today)
45
+ start_date && start_date > query_date
46
+ end
47
+
48
+ alias_method :future_on?, :future?
49
+
50
+ def expired?(query_date = Date.today)
51
+ end_date && end_date < query_date
52
+ end
53
+
54
+ alias_method :expired_on?, :expired?
55
+ end
56
+ end
57
+ end
58
+ end