acts_as_span 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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