acts_as_span 0.0.5 → 0.0.6

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9ed7f152ad93220467d76ca831763a2a4dbd02ba
4
+ data.tar.gz: eaf693675a0aa764b8d85a77464b9897787993be
5
+ SHA512:
6
+ metadata.gz: 686a48328384d0e29b0aba18af0d957cc06d0e8ab4fd051d72a33a8515c23115eca337427424f0e920edbf5970abb3605d12b582fa9df37799a0ed911624d8de
7
+ data.tar.gz: a35877b8ff3c5748a921fb095dd925791e1212246dc0442835a5e3e5c77cbdc30ec659a69d56cc55fbc0e4eee154f53babf7a70a26ee194674a9ad59e4cb0cdb
data/.gitignore CHANGED
@@ -1,3 +1,11 @@
1
- coverage
2
- rdoc
3
- pkg
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+
6
+ .DS_Store
7
+ *.log
8
+ *.sqlite
9
+
10
+ .rvmrc
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --order rand
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.8
4
+ - 2.3.5
5
+ notifications:
6
+ email: false
data/Gemfile CHANGED
@@ -2,6 +2,3 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in acts_as_span.gemspec
4
4
  gemspec
5
-
6
- gem 'rspec'
7
- gem 'acts_as_fu'
data/README.rdoc CHANGED
@@ -1,6 +1,8 @@
1
1
  = acts_as_span
2
2
 
3
- ActiveRecord model w/ a start_date and an end_date
3
+ ActiveRecord model w/ a start_date and an end_date == ActsAsSpan
4
+
5
+ Treat those date spans like the objects they are!
4
6
 
5
7
  == Getting Started
6
8
 
@@ -14,20 +16,6 @@ In your model:
14
16
  acts_as_span
15
17
  end
16
18
 
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
19
  == Copyright
32
20
 
33
- Copyright (c) 2011 Annkissam. See LICENSE for details.
21
+ Copyright (c) 2011-2018 Annkissam. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,4 +1,8 @@
1
- require 'bundler/gem_tasks'
1
+ require "bundler/gem_tasks"
2
+
3
+ #RSPEC
2
4
  require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new('spec')
7
+
8
+ task :default => :spec
data/acts_as_span.gemspec CHANGED
@@ -9,15 +9,31 @@ Gem::Specification.new do |s|
9
9
  s.email = ["eric.sullivan@annkissam.com"]
10
10
  s.homepage = "https://github.com/annkissam/acts_as_span"
11
11
  s.summary = ActsAsSpan::VERSION::SUMMARY
12
- s.description = %q{start_date and end_date as a span w/ ActiveRecord}
12
+ s.description = %q{ActiveRecord model w/ a start_date and an end_date == ActsAsSpan}
13
+ s.license = "MIT"
13
14
 
14
- s.rubyforge_project = "acts_as_span"
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ if s.respond_to?(:metadata)
18
+ s.metadata["allowed_push_host"] = "https://rubygems.org"
19
+ else
20
+ raise "RubyGems 2.0 or newer is required to protect against " \
21
+ "public gem pushes."
22
+ end
15
23
 
16
24
  s.files = `git ls-files`.split("\n")
17
25
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
26
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
27
  s.require_paths = ["lib"]
20
-
21
- s.add_dependency(%q<activerecord>, [">= 3"])
22
- s.add_dependency('activesupport')
28
+
29
+ s.add_development_dependency "bundler", "~> 1.15"
30
+ s.add_development_dependency "rake", "~> 10.0"
31
+ s.add_development_dependency "rspec", "~> 3.0"
32
+ s.add_development_dependency "sqlite3"
33
+ s.add_development_dependency "has_siblings", "~> 0.2.7"
34
+ s.add_development_dependency "temping"
35
+ s.add_development_dependency "pry-byebug"
36
+
37
+ s.add_runtime_dependency('activerecord', '>= 4.2.0')
38
+ s.add_runtime_dependency('activesupport', '>= 4.2.0')
23
39
  end
data/lib/acts_as_span.rb CHANGED
@@ -1,161 +1,118 @@
1
- require 'active_support'
2
1
  require 'ostruct'
3
- require 'forwardable'
2
+ require 'acts_as_span/version'
3
+ require 'acts_as_span/span_klass'
4
+ require 'acts_as_span/span_instance'
5
+
6
+ require 'acts_as_span/no_overlap_validator'
7
+ require 'acts_as_span/within_parent_date_span_validator'
4
8
 
5
- ACTS_AS_SPAN_PATH = File.dirname(__FILE__) + "/acts_as_span/"
6
9
 
7
- require ACTS_AS_SPAN_PATH + 'version'
8
- require ACTS_AS_SPAN_PATH + 'span_klass'
9
- require ACTS_AS_SPAN_PATH + 'span_instance'
10
+ require 'active_support'
11
+ require 'active_record'
10
12
 
11
13
  module ActsAsSpan
12
14
  extend ActiveSupport::Concern
13
-
15
+
14
16
  class << self
15
17
  def options
16
18
  @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,
19
+ :start_field => :start_date,
20
+ :end_field => :end_date,
23
21
  :name => :default
24
22
  }
25
23
  end
26
-
24
+
27
25
  def configure
28
26
  yield(self) if block_given?
29
27
  end
30
28
  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
-
29
+
38
30
  module ClassMethods
39
31
  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
32
  self.send(:extend, ActsAsSpan::ExtendedClassMethods)
47
33
  self.send(:include, ActsAsSpan::IncludedInstanceMethods)
48
-
34
+
49
35
  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
-
36
+
54
37
  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
-
38
+
39
+ # TODO add tests that check delegation of all methos in span
40
+ delegate :span_status,
41
+ :span_status_on,
42
+ :current?,
43
+ :current_on?,
44
+ :future?,
45
+ :future_on?,
46
+ :expired?,
47
+ :expired_on?,
48
+ :past?,
49
+ :past_on?, to: :span
50
+
51
+ delegate :acts_as_span_definitions, to: :class
52
+
53
+ # TODO idem above
71
54
  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
55
+ delegate :current,
56
+ :current_on,
57
+ :future,
58
+ :future_on,
59
+ :expired,
60
+ :expired_on,
61
+ :past_on,
62
+ :past,
63
+ :current_or_future_on,
64
+ :current_or_future, to: :span
80
65
  end
81
-
66
+
82
67
  validate :validate_spans
83
68
  end
84
-
69
+
85
70
  def acts_as_span_definitions
86
71
  @_acts_as_span_definitions ||= {}
87
72
  end
88
73
  end
89
-
74
+
90
75
  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
76
  def spans
100
77
  acts_as_span_definitions.keys.map { |acts_as_span_definition_name| span_for(acts_as_span_definition_name) }
101
78
  end
102
-
79
+
103
80
  def span
104
81
  span_for(:default)
105
82
  end
106
-
83
+
107
84
  def span_for(name = :default)
108
85
  acts_as_span_klasses[name] ||= SpanKlass.new(name, self, acts_as_span_definitions[name])
109
86
  end
110
-
87
+
111
88
  def acts_as_span_klasses
112
89
  @_acts_as_span_klasses ||= {}
113
90
  end
114
91
  end
115
-
92
+
116
93
  module IncludedInstanceMethods
117
94
  def spans
118
95
  acts_as_span_definitions.keys.map { |acts_as_span_definition_name| span_for(acts_as_span_definition_name) }
119
96
  end
120
-
97
+
121
98
  def span
122
99
  span_for(:default)
123
100
  end
124
-
101
+
125
102
  def span_for(name = :default)
126
103
  acts_as_span_instances[name] ||= SpanInstance.new(name, self, acts_as_span_definitions[name])
127
104
  end
128
-
105
+
129
106
  def acts_as_span_instances
130
107
  @_acts_as_span_instances ||= {}
131
108
  end
132
-
109
+
133
110
  def validate_spans
134
111
  spans.each(&:validate)
135
112
  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
113
  end
159
114
  end
160
115
 
161
- ActiveRecord::Base.send(:include, ActsAsSpan)
116
+ if Object.const_defined?("ActiveRecord")
117
+ ActiveRecord::Base.send(:include, ActsAsSpan)
118
+ end
@@ -0,0 +1,51 @@
1
+ require 'active_model'
2
+
3
+ module ActsAsSpan
4
+ class NoOverlapValidator < ActiveModel::Validator
5
+ def validate(record)
6
+ overlapping_records = temporally_overlapping_for(record)
7
+ instance_scope = options[:instance_scope].is_a?(Proc) ? record.instance_eval(&options[:instance_scope]) : true
8
+
9
+ if overlapping_records.any? && instance_scope
10
+
11
+ error_type = overlapping_records.size == 1 ? "no_overlap.one" : "no_overlap.other"
12
+
13
+ record.errors.add(
14
+ :base,
15
+ error_type.to_sym,
16
+ model_name: record.class.model_name.human,
17
+ model_name_plural: record.class.model_name.plural.humanize,
18
+ start_date: record.start_date,
19
+ end_date: record.end_date,
20
+ count: overlapping_records.size,
21
+ overlapping_records_s: overlapping_records.join(",")
22
+ )
23
+ end
24
+ end
25
+
26
+ #TODO add back condition for start_date nil
27
+ #TODO add configuration for span configuration
28
+ def temporally_overlapping_for(record)
29
+ scope = record.instance_eval(&options[:scope])
30
+
31
+ start_date = record.start_date || Date.current
32
+ end_date = record.end_date
33
+ arel_table = record.class.arel_table
34
+
35
+ if end_date
36
+ scope.where(
37
+ arel_table[:start_date].lteq(end_date).
38
+ and(
39
+ arel_table[:end_date].gteq(start_date).
40
+ or(arel_table[:end_date].eq(nil))
41
+ )
42
+ )
43
+ else
44
+ scope.where(
45
+ arel_table[:end_date].gteq(start_date).
46
+ or(arel_table[:end_date].eq(nil))
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,53 +1,37 @@
1
- require 'forwardable'
1
+ require 'acts_as_span/span_instance/validations'
2
+ require 'acts_as_span/span_instance/status'
2
3
 
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'
4
+ require 'active_support/core_ext/module/delegation'
6
5
 
7
6
  module ActsAsSpan
8
7
  class SpanInstance
9
- extend Forwardable
10
-
11
8
  include ActsAsSpan::SpanInstance::Validations
12
9
  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
-
10
+
11
+ delegate :start_field,
12
+ :end_field,
13
+ :exclude_end, to: :@acts_as_span_definition
14
+
15
+ delegate :new_record?, to: :span_model
16
+
25
17
  attr_reader :name, :span_model, :acts_as_span_definition
26
-
18
+
27
19
  def initialize(name, span_model, acts_as_span_definition)
28
20
  @name = name
29
21
  @span_model = span_model
30
22
  @acts_as_span_definition = acts_as_span_definition
31
23
  end
32
-
24
+
33
25
  def span_klass
34
26
  @span_klass ||= span_model.class
35
27
  end
36
-
28
+
37
29
  def start_date
38
- span_model[start_date_field]
30
+ span_model[start_field]
39
31
  end
40
-
32
+
41
33
  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
34
+ span_model[end_field]
49
35
  end
50
-
51
- alias_method :close_on!, :close!
52
36
  end
53
- end
37
+ end