hystorical 0.0.1.alpha → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ *.db
data/.rspec CHANGED
@@ -1 +1,2 @@
1
1
  --color
2
+ --order random
data/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # Hystorical
2
2
 
3
- Hystorical is a simple solution for managing explicit historical datasets. Provided that your records have a `start_date` and `end_date`, Hystorical will take care figuring which records are currently active, which were active during a particular date range and managing the archiving of records. It makes the following assumptions:
3
+ Hystorical is a simple solution for managing historical datasets. Hystorical takes care of determining which objects are currently active or active on a particular day. It makes the following assumptions:
4
4
 
5
5
  * Objects in the collection have `start_date` and `end_date` attributes
6
6
  * `start_date` and `end_date` return a ruby `Date` object
7
- * the start and end dates can be accessed via either `[]` method or the `.` operator
7
+ * Typically members of the collection will be objects or hashes, but anything that implements start_date and end_date via `[]` or `.` can be used.
8
+ * Current objects have a `nil` end_date
8
9
 
9
10
  ## Installation
10
11
 
@@ -23,7 +24,7 @@ Or install it yourself as:
23
24
  ## Usage
24
25
 
25
26
  ### current
26
- Hystorical returns the current object which is determined as those with `nil` end_date
27
+ Hystorical returns the current objects which is determined as objects with a end_date that returns `nil`
27
28
  ```ruby
28
29
  Hystorical.current(@subscriptions)
29
30
  # => returns enumerable collection of objects
@@ -46,19 +47,21 @@ Hystorical.current_on(@subscriptions, date)
46
47
 
47
48
  ```ruby
48
49
  Subscription = Struct.new(:start_date, :end_date)
49
- subscriptions = # collection of several Subscription objects
50
+ subscription1 = Subscription.new(Date.new(2012, 9, 1), Date.new(2012, 9, 10))
51
+ subscription2 = Subscription.new(Date.new(2012, 9, 11), nil)
52
+ subscriptions = [subscription1, subscription2]
50
53
 
51
54
  Hystorical.current(subscriptions)
52
- # => returns enumerable collection of objects
55
+ # => returns [subscription2]
53
56
 
54
- date = Date.new(2012, 01, 10)
57
+ date = Date.new(2012, 09, 8)
55
58
  Hystorical.current_on(subscriptions, date)
56
- # => returns enumerable collection of objects that were current on January 10th
59
+ # => returns [subscription1]
57
60
  ```
58
61
 
59
62
  ### Using Rails
60
63
 
61
- Hystorical accepts ActiveRecord::Relation collections. This means that all your scopes integrate fully with Hystorical.
64
+ Hystorical accepts ActiveRecord::Relation collections. This means that all your scopes integrate fully with Hystorical. In order to be optimized for larger datasets, Hystorical takes advantage of ActiveRecord::Relation methods to build a SQL query instead of using Enumerable methods when pass an instance of ActiveRecordRelation.
62
65
 
63
66
  In a model
64
67
  ```ruby
@@ -71,7 +74,11 @@ class Subscription < ActiveRecord::Base
71
74
 
72
75
  def self.user_subscription_count_on(user_id, date)
73
76
  subscriptions = Subscription.for_user(user_id)
74
- Hystorical.current subscriptions, date
77
+ Hystorical.current(subscriptions, date).count
78
+ end
79
+
80
+ def archive
81
+ update_attributes { end_date: Date.today }
75
82
  end
76
83
 
77
84
  end
@@ -85,10 +92,9 @@ end
85
92
  ```
86
93
 
87
94
  ## Philosophy
88
- This gem was created using TDD and README-driven development. The architecture was designed with a strong focus on modularity and extensibility. Using ruby's `Enumerable` methods to return current object was chosen because of it's great flexibility to adapt to all ruby projects. However, when working with large datasets stored in a relational database, using SQL would yield greater performance. Adding an adapter for ActiveRecord or any other ORM (such as DataMapper or Mongoid) is as simple as creating a new class that defines all the methods in the public api and adding a conditional in `Hystorical.delegate_class`.
95
+ This gem was created using TDD and README-driven development. The architecture was designed with a strong focus on modularity and extensibility. Using ruby's `Enumerable` methods to return current object was chosen because of it's great flexibility to adapt to all ruby projects. However, when working with large datasets stored in a relational database, using SQL would yield greater performance. An adapter was added for ActiveRecord, but extending this to another ORM (such as DataMapper or Mongoid) is as simple as creating a new class that defines all the methods in the public api and adding a conditional in `Hystorical.delegate_class`.
89
96
 
90
97
  ## TODO
91
- * Add ability to search via SQL when passed `ActiveRecord::Relation`
92
98
  * Add ability to pass in a block option that can further filter results
93
99
 
94
100
  ## Contributing
@@ -1,6 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/hystorical', __FILE__)
3
- require File.expand_path('../lib/ruby_hystorical', __FILE__)
2
+ require File.expand_path('../lib/hystorical/version', __FILE__)
4
3
 
5
4
  Gem::Specification.new do |gem|
6
5
  gem.authors = ["Joel Quenneville"]
@@ -17,4 +16,6 @@ Gem::Specification.new do |gem|
17
16
  gem.version = Hystorical::VERSION
18
17
 
19
18
  gem.add_development_dependency "rspec"
19
+ gem.add_development_dependency "sqlite3"
20
+ gem.add_development_dependency "with_model"
20
21
  end
@@ -1,17 +1,20 @@
1
- class Hystorical
1
+ require 'hystorical/ruby_collection'
2
+ require 'hystorical/ar_relation'
3
+ module Hystorical
2
4
 
3
- VERSION = "0.0.1.alpha"
4
-
5
- def self.delegate_class
6
- RubyHystorical
7
- # add conditional to determine which subclass to use once other strategies are used (i.e. ActiveRecord)
5
+ def self.delegate_class(collection)
6
+ if collection.class == ActiveRecord::Relation
7
+ return Hystorical::ARRelation
8
+ else
9
+ return Hystorical::RubyCollection
10
+ end
8
11
  end
9
12
 
10
13
  def self.current(collection)
11
- delegate_class.current(collection)
14
+ delegate_class(collection).current(collection)
12
15
  end
13
16
 
14
17
  def self.current_on(collection, date)
15
- delegate_class.current_on(collection, date)
18
+ delegate_class(collection).current_on(collection, date)
16
19
  end
17
20
  end
@@ -0,0 +1,13 @@
1
+ module Hystorical
2
+ class ARRelation
3
+ class << self
4
+ def current(collection)
5
+ collection.where(end_date: nil)
6
+ end
7
+
8
+ def current_on(collection, date)
9
+ collection.where("? BETWEEN start_date AND end_date", date)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module Hystorical
2
+ class RubyCollection
3
+ class << self
4
+
5
+ def current(collection)
6
+ collection.select { |obj| obj.respond_to?(:[]) ? obj[:end_date].nil? : obj.end_date.nil? }
7
+ end
8
+
9
+ def current_on(collection, date)
10
+ collection.select do |obj|
11
+ if obj.respond_to?(:[])
12
+ obj[:start_date] <= date && obj[:end_date] >= date
13
+ else
14
+ obj.start_date <= date && obj.end_date >= date
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Hystorical
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hystorical::ARRelation do
4
+
5
+ with_model :Subscription do
6
+ table do |t|
7
+ t.integer :user_id
8
+ t.date :start_date
9
+ t.date :end_date
10
+ end
11
+
12
+ model do
13
+ def self.for_user(user_id)
14
+ where user_id: user_id
15
+ end
16
+ end
17
+ end
18
+
19
+ describe "current" do
20
+ let!(:obj1) { Subscription.create(user_id: 1, start_date: Date.new(2012, 9, 1), end_date: nil) }
21
+ let!(:obj2) { Subscription.create(user_id: 2, start_date: Date.new(2012, 9, 6), end_date: Date.new(2012, 9, 10)) }
22
+ let!(:obj3) { Subscription.create(user_id: 2, start_date: Date.new(2012, 9, 11), end_date: nil) }
23
+
24
+ it "should return the current subscription" do
25
+ Hystorical::ARRelation.current(Subscription.for_user(2)).should eq [obj3]
26
+ end
27
+ end
28
+
29
+ describe "current" do
30
+ let!(:obj1) { Subscription.create(user_id: 1, start_date: Date.new(2012, 9, 1), end_date: nil) }
31
+ let!(:obj2) { Subscription.create(user_id: 2, start_date: Date.new(2012, 9, 6), end_date: Date.new(2012, 9, 10)) }
32
+ let!(:obj3) { Subscription.create(user_id: 2, start_date: Date.new(2012, 9, 11), end_date: nil) }
33
+
34
+ it "should return the current subscription as of Sept 8" do
35
+ Hystorical::ARRelation.current_on(Subscription.for_user(2), Date.new(2012, 9, 8)).should eq [obj2]
36
+ end
37
+ end
38
+ end
@@ -1,4 +1,6 @@
1
- describe RubyHystorical do
1
+ require 'spec_helper'
2
+
3
+ describe Hystorical::RubyCollection do
2
4
  class HistoricalObjet
3
5
  attr_accessor :start_date, :end_date
4
6
 
@@ -22,13 +24,13 @@ describe RubyHystorical do
22
24
  describe ".current" do
23
25
  context "when attributes are accessed via []" do
24
26
  it "should return the current object" do
25
- RubyHystorical.current(collection).should eq [obj3]
27
+ Hystorical::RubyCollection.current(collection).should eq [obj3]
26
28
  end
27
29
  end
28
30
 
29
31
  context "when attributes are accessed via ." do
30
32
  it "should return the current object" do
31
- RubyHystorical.current(collection2).should eq [obj6]
33
+ Hystorical::RubyCollection.current(collection2).should eq [obj6]
32
34
  end
33
35
  end
34
36
  end
@@ -38,14 +40,14 @@ describe RubyHystorical do
38
40
 
39
41
  context "when attributes are accessed via []" do
40
42
  it "should return the current object" do
41
- RubyHystorical.current_on(collection, date).should eq [obj2]
43
+ Hystorical::RubyCollection.current_on(collection, date).should eq [obj2]
42
44
  end
43
45
  end
44
46
 
45
47
  context "when attributes are accessed via ." do
46
48
  it "should return the current object" do
47
- RubyHystorical.current_on(collection2, date).should eq [obj5]
49
+ Hystorical::RubyCollection.current_on(collection2, date).should eq [obj5]
48
50
  end
49
51
  end
50
52
  end
51
- end
53
+ end
@@ -1,16 +1,26 @@
1
- describe Hystorical do
1
+ require 'spec_helper'
2
2
 
3
+ describe Hystorical do
3
4
  describe ".delegate_class" do
4
- it "should return RubyHystorical" do
5
- Hystorical.delegate_class.should be RubyHystorical
5
+ context "when ActiveRecord::Relation" do
6
+ it "should return RubyHystorical" do
7
+ collection = double("ActiveRecord::Relation")
8
+ collection.stub(:class) {ActiveRecord::Relation}
9
+ Hystorical.delegate_class(collection).should eq Hystorical::ARRelation
10
+ end
11
+ end
12
+ context "when not ActiveRecord:Relation" do
13
+ it "should return RubyHystorical" do
14
+ Hystorical.delegate_class(stub).should eq Hystorical::RubyCollection
15
+ end
6
16
  end
7
17
  end
8
18
 
9
19
  let(:collection) { stub }
10
- before { Hystorical.stub(:delegate_class).and_return(RubyHystorical) }
11
20
  describe ".current" do
21
+ before { Hystorical.stub(:delegate_class).and_return(Hystorical::RubyCollection) }
12
22
  it "send current message to delegate class" do
13
- RubyHystorical.should_receive(:current).with(collection)
23
+ Hystorical::RubyCollection.should_receive(:current).with(collection)
14
24
  Hystorical.current(collection)
15
25
  end
16
26
  end
@@ -18,8 +28,8 @@ describe Hystorical do
18
28
  describe ".current_on" do
19
29
  it "send current_on message to delegate class" do
20
30
  date = stub
21
- RubyHystorical.should_receive(:current_on).with(collection, date)
31
+ Hystorical::RubyCollection.should_receive(:current_on).with(collection, date)
22
32
  Hystorical.current_on(collection, date)
23
33
  end
24
34
  end
25
- end
35
+ end
@@ -1,2 +1,16 @@
1
+ $:.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ require 'date'
4
+ require 'sqlite3'
5
+ require 'active_record'
6
+ require 'with_model'
1
7
  require "hystorical"
2
- require "ruby_hystorical"
8
+
9
+ ActiveRecord::Base.establish_connection(
10
+ :adapter => "sqlite3",
11
+ :database => "test.db"
12
+ )
13
+
14
+ RSpec.configure do |config|
15
+ config.extend WithModel
16
+ end
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hystorical
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.alpha
5
- prerelease: 6
4
+ version: 0.1.0
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Joel Quenneville
@@ -27,6 +27,38 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sqlite3
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: with_model
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
30
62
  description: Hystorical is a simple solution for managing explicit historical datasets
31
63
  email:
32
64
  - joel.quen@gmail.com
@@ -42,9 +74,12 @@ files:
42
74
  - Rakefile
43
75
  - hystorical.gemspec
44
76
  - lib/hystorical.rb
45
- - lib/ruby_hystorical.rb
77
+ - lib/hystorical/ar_relation.rb
78
+ - lib/hystorical/ruby_collection.rb
79
+ - lib/hystorical/version.rb
80
+ - spec/hystorical/ar_relation_spec.rb
81
+ - spec/hystorical/ruby_collection_spec.rb
46
82
  - spec/hystorical_spec.rb
47
- - spec/ruby_hystorical_spec.rb
48
83
  - spec/spec_helper.rb
49
84
  homepage: ''
50
85
  licenses: []
@@ -61,16 +96,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
96
  required_rubygems_version: !ruby/object:Gem::Requirement
62
97
  none: false
63
98
  requirements:
64
- - - ! '>'
99
+ - - ! '>='
65
100
  - !ruby/object:Gem::Version
66
- version: 1.3.1
101
+ version: '0'
67
102
  requirements: []
68
103
  rubyforge_project:
69
- rubygems_version: 1.8.24
104
+ rubygems_version: 1.8.21
70
105
  signing_key:
71
106
  specification_version: 3
72
107
  summary: Hystorical is a simple solution for managing explicit historical datasets
73
108
  test_files:
109
+ - spec/hystorical/ar_relation_spec.rb
110
+ - spec/hystorical/ruby_collection_spec.rb
74
111
  - spec/hystorical_spec.rb
75
- - spec/ruby_hystorical_spec.rb
76
112
  - spec/spec_helper.rb
@@ -1,16 +0,0 @@
1
- class RubyHystorical
2
-
3
- def self.current(collection)
4
- collection.select { |obj| obj.respond_to?(:[]) ? obj[:end_date].nil? : obj.end_date.nil? }
5
- end
6
-
7
- def self.current_on(collection, date)
8
- collection.select do |obj|
9
- if obj.respond_to?(:[])
10
- obj[:start_date] <= date && obj[:end_date] >= date
11
- else
12
- obj.start_date <= date && obj.end_date >= date
13
- end
14
- end
15
- end
16
- end