hystorical 0.0.1.alpha → 0.1.0
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 +1 -0
- data/.rspec +1 -0
- data/README.md +17 -11
- data/hystorical.gemspec +3 -2
- data/lib/hystorical.rb +11 -8
- data/lib/hystorical/ar_relation.rb +13 -0
- data/lib/hystorical/ruby_collection.rb +20 -0
- data/lib/hystorical/version.rb +3 -0
- data/spec/hystorical/ar_relation_spec.rb +38 -0
- data/spec/{ruby_hystorical_spec.rb → hystorical/ruby_collection_spec.rb} +8 -6
- data/spec/hystorical_spec.rb +17 -7
- data/spec/spec_helper.rb +15 -1
- metadata +44 -8
- data/lib/ruby_hystorical.rb +0 -16
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# Hystorical
|
2
2
|
|
3
|
-
Hystorical is a simple solution for managing
|
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
|
-
*
|
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
|
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
|
-
|
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
|
55
|
+
# => returns [subscription2]
|
53
56
|
|
54
|
-
date = Date.new(2012,
|
57
|
+
date = Date.new(2012, 09, 8)
|
55
58
|
Hystorical.current_on(subscriptions, date)
|
56
|
-
# => returns
|
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
|
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.
|
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
|
data/hystorical.gemspec
CHANGED
@@ -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
|
data/lib/hystorical.rb
CHANGED
@@ -1,17 +1,20 @@
|
|
1
|
-
|
1
|
+
require 'hystorical/ruby_collection'
|
2
|
+
require 'hystorical/ar_relation'
|
3
|
+
module Hystorical
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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,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,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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
49
|
+
Hystorical::RubyCollection.current_on(collection2, date).should eq [obj5]
|
48
50
|
end
|
49
51
|
end
|
50
52
|
end
|
51
|
-
end
|
53
|
+
end
|
data/spec/hystorical_spec.rb
CHANGED
@@ -1,16 +1,26 @@
|
|
1
|
-
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
|
+
describe Hystorical do
|
3
4
|
describe ".delegate_class" do
|
4
|
-
|
5
|
-
|
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
|
-
|
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
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
-
|
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.
|
5
|
-
prerelease:
|
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/
|
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:
|
101
|
+
version: '0'
|
67
102
|
requirements: []
|
68
103
|
rubyforge_project:
|
69
|
-
rubygems_version: 1.8.
|
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
|
data/lib/ruby_hystorical.rb
DELETED
@@ -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
|