acts_as_span 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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