chronicle 0.0.6 → 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/lib/chronicle.rb +45 -44
- data/lib/chronicle/version.rb +1 -1
- data/spec/chronicle_spec.rb +94 -69
- metadata +4 -4
data/lib/chronicle.rb
CHANGED
@@ -51,7 +51,7 @@ module Chronicle
|
|
51
51
|
"3 minutes ago",
|
52
52
|
"2 minutes ago",
|
53
53
|
"1 minute ago",
|
54
|
-
"
|
54
|
+
"right now",
|
55
55
|
"1 minute from now",
|
56
56
|
"2 minutes from now",
|
57
57
|
"3 minutes from now",
|
@@ -98,59 +98,60 @@ module Chronicle
|
|
98
98
|
def initialize(collection, options)
|
99
99
|
|
100
100
|
eras = options[:eras]
|
101
|
-
|
102
|
-
# Sort order, defaut is new to old
|
103
|
-
order = options[:order] || :desc
|
104
|
-
|
101
|
+
|
105
102
|
# Remove objects with nil timestamps
|
106
|
-
collection
|
103
|
+
collection.reject! {|obj| obj.send(options[:date_attr]).nil? }
|
104
|
+
|
105
|
+
# Determine whether collection contains future or past timestamps
|
106
|
+
if collection.all? { |obj| obj.send(options[:date_attr]) < Time.now }
|
107
|
+
order = :past
|
108
|
+
elsif collection.all? { |obj| obj.send(options[:date_attr]) > Time.now }
|
109
|
+
order = :future
|
110
|
+
else
|
111
|
+
raise "Chronicle collections must be entirely in the past or the future."
|
112
|
+
end
|
107
113
|
|
108
114
|
# Sort collection by date
|
109
|
-
|
110
|
-
|
115
|
+
# For past collections, newest objects come first
|
116
|
+
# For future collections, oldest objects come first
|
117
|
+
collection = collection.sort_by {|obj| obj.send(options[:date_attr]) }
|
118
|
+
collection.reverse! if order == :past
|
119
|
+
|
120
|
+
# Force inclusion of 'now' era in case it's missing.
|
121
|
+
eras.push('right now').uniq!
|
111
122
|
|
123
|
+
# Parse era strings using Chronic
|
112
124
|
# Ensure all eras can be parsed
|
113
|
-
|
114
|
-
|
115
|
-
|
125
|
+
# { "7 years ago" => 2006-01-02 23:29:05 -0800, ... }
|
126
|
+
era_date_pairs = eras.inject({}) {|h,era|
|
127
|
+
h[era] = Chronic.parse(era)
|
128
|
+
raise "Could not parse era: #{era}" if h[era].nil?
|
129
|
+
h
|
130
|
+
}
|
116
131
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
# { "7 years ago"=>2006-01-02 23:29:05 -0800, ... }
|
123
|
-
era_date_pairs = eras.inject({}) {|h,e| h[e] = Chronic.parse(e); h }
|
124
|
-
|
125
|
-
# Sort eras oldest to newest
|
126
|
-
eras = eras.sort_by {|era| Chronic.parse(era) }
|
127
|
-
# .. or newest to oldest
|
128
|
-
eras = eras.reverse unless order == :desc
|
132
|
+
# Sort eras by date
|
133
|
+
# For past collections, newest eras come first
|
134
|
+
# For future collections, oldest eras come first
|
135
|
+
eras = eras.sort_by {|era| era_date_pairs[era] }
|
136
|
+
eras.reverse! if order == :future
|
129
137
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
# Initialize all hash keys chronologically (oldest to newest)
|
144
|
-
eras.reverse.each {|era| self[era] = [] }
|
145
|
-
|
146
|
-
# Find the newest era in which each object was created
|
147
|
-
collection.each do |obj|
|
148
|
-
era = eras.find {|era| obj.send(options[:date_attr]) > era_date_pairs[era] }
|
149
|
-
self[era] << obj
|
138
|
+
# Initialize all hash keys chronologically
|
139
|
+
eras.reverse.each {|era| self[era] = [] }
|
140
|
+
|
141
|
+
collection.each do |obj|
|
142
|
+
era = eras.find do |era|
|
143
|
+
if order == :future
|
144
|
+
# Find newest possible era for the object
|
145
|
+
obj.send(options[:date_attr]) > era_date_pairs[era]
|
146
|
+
else
|
147
|
+
# Find oldest possible era for the object
|
148
|
+
obj.send(options[:date_attr]) < era_date_pairs[era]
|
149
|
+
end
|
150
150
|
end
|
151
|
+
self[era] << obj
|
151
152
|
end
|
152
153
|
|
153
|
-
# Remove
|
154
|
+
# Remove empty eras
|
154
155
|
self.keys.each {|k| self.delete(k) if self[k].empty? }
|
155
156
|
|
156
157
|
self
|
data/lib/chronicle/version.rb
CHANGED
data/spec/chronicle_spec.rb
CHANGED
@@ -6,54 +6,114 @@ describe Chronicle do
|
|
6
6
|
@minute = 60
|
7
7
|
@hour = @minute*60
|
8
8
|
@day = @hour*24
|
9
|
-
@things = [
|
10
|
-
double("thing", :created_at => Time.now - 39*@day),
|
11
|
-
double("thing", :created_at => Time.now - 8*@day),
|
12
|
-
double("thing", :created_at => Time.now - 9*@day),
|
13
|
-
double("thing", :created_at => Time.now - 3*@day),
|
14
|
-
double("thing", :created_at => Time.now - 10*@minute),
|
15
|
-
double("thing", :created_at => Time.now)
|
16
|
-
]
|
17
|
-
|
18
|
-
@chronicle = Chronicle.new(@things)
|
19
9
|
end
|
10
|
+
|
11
|
+
context "dates in the past and the future" do
|
12
|
+
|
13
|
+
before do
|
14
|
+
offsets = [@day, -@day]
|
15
|
+
@things = offsets.map do |offset|
|
16
|
+
double("thing", :created_at => Time.now+offset)
|
17
|
+
end
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
it "raises an error" do
|
21
|
+
expect { Chronicle.new(@things) }.to raise_error("Chronicle collections must be entirely in the past or the future.")
|
22
|
+
end
|
23
|
+
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
context "dates in the past" do
|
27
|
+
|
28
|
+
before do
|
29
|
+
offsets = [-39*@day, -8*@day, -9*@day, -3*@day, -10*@minute, 0]
|
30
|
+
@things = offsets.map do |offset|
|
31
|
+
double("thing", :created_at => Time.now+offset)
|
32
|
+
end
|
33
|
+
@chronicle = Chronicle.new(@things)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "doesn't have any empty eras" do
|
37
|
+
@chronicle.values.all? {|v| !v.empty? }.should == true
|
38
|
+
end
|
29
39
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
40
|
+
it "sorts era keys from newest to oldest" do
|
41
|
+
@chronicle.keys.first.should == 'right now'
|
42
|
+
@chronicle.keys.last.should == '1 month ago'
|
43
|
+
end
|
35
44
|
|
36
|
-
|
37
|
-
|
38
|
-
|
45
|
+
it "sorts objects in eras from newest to oldest" do
|
46
|
+
era = @chronicle['1 week ago']
|
47
|
+
era.size.should == 2
|
48
|
+
era.last.created_at.should be < era.first.created_at
|
49
|
+
end
|
39
50
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
51
|
+
it "doesn't lose any items during processing" do
|
52
|
+
@chronicle.values.flatten.size.should == @things.size
|
53
|
+
end
|
54
|
+
|
55
|
+
it "accounts for objects that were just created" do
|
56
|
+
now = @chronicle['right now']
|
57
|
+
now.should_not be_empty
|
58
|
+
now.should be_an(Array)
|
59
|
+
now.first.should == @things.last
|
60
|
+
end
|
61
|
+
|
45
62
|
end
|
46
63
|
|
64
|
+
context "dates in the future" do
|
65
|
+
|
66
|
+
before(:each) do
|
67
|
+
@things = [
|
68
|
+
double("thing", :created_at => Time.now + 39*@day),
|
69
|
+
double("thing", :created_at => Time.now + 9*@day),
|
70
|
+
double("thing", :created_at => Time.now + 8*@day),
|
71
|
+
double("thing", :created_at => Time.now + 3*@day),
|
72
|
+
double("thing", :created_at => Time.now + 9*@minute),
|
73
|
+
double("thing", :created_at => Time.now + 2),
|
74
|
+
]
|
75
|
+
@chronicle = Chronicle.new(@things, order: :asc)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "doesn't have any empty eras" do
|
79
|
+
@chronicle.values.all? {|v| !v.empty? }.should == true
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sorts era keys from oldest to newest" do
|
83
|
+
@chronicle.keys.first.should == 'right now'
|
84
|
+
@chronicle.keys.last.should == '1 month from now'
|
85
|
+
end
|
86
|
+
|
87
|
+
it "sorts objects in eras from oldest to newest" do
|
88
|
+
era = @chronicle['1 week from now']
|
89
|
+
era.size.should == 2
|
90
|
+
era.last.created_at.should be > era.first.created_at
|
91
|
+
end
|
92
|
+
|
93
|
+
it "doesn't lose any items during processing" do
|
94
|
+
@chronicle.values.flatten.size.should == @things.size
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
47
99
|
context "custom eras" do
|
100
|
+
|
101
|
+
before do
|
102
|
+
offsets = [-39*@day, -8*@day, -9*@day, -3*@day, -10*@minute, 0]
|
103
|
+
@things = offsets.map do |offset|
|
104
|
+
double("thing", :created_at => Time.now+offset)
|
105
|
+
end
|
106
|
+
@chronicle = Chronicle.new(@things)
|
107
|
+
end
|
48
108
|
|
49
|
-
it "add '
|
109
|
+
it "add 'right now' to the list of eras if it's missing, to keep from losing very new objects" do
|
50
110
|
@chronicle = Chronicle.new(@things, :eras => ['3 minutes ago', '35 days ago'])
|
51
|
-
@chronicle.keys.first.should == '
|
111
|
+
@chronicle.keys.first.should == 'right now'
|
52
112
|
end
|
53
113
|
|
54
114
|
it "allows custom eras to be set in any order" do
|
55
|
-
@chronicle = Chronicle.new(@things, :eras => ['
|
56
|
-
@chronicle.keys.should == ['
|
115
|
+
@chronicle = Chronicle.new(@things, :eras => ['right now', '35 days ago', '3 minutes ago'])
|
116
|
+
@chronicle.keys.should == ['right now', '3 minutes ago', '35 days ago']
|
57
117
|
end
|
58
118
|
|
59
119
|
it "raises an exception for eras that cannot be parsed" do
|
@@ -79,7 +139,7 @@ describe Chronicle do
|
|
79
139
|
@chronicle = Chronicle.new(@things, :date_attr => :updated_at)
|
80
140
|
@chronicle.values.flatten.size.should == @things.size
|
81
141
|
@chronicle.keys.last.should == '1 year ago'
|
82
|
-
@chronicle.keys.first.should == '
|
142
|
+
@chronicle.keys.first.should == 'right now'
|
83
143
|
end
|
84
144
|
|
85
145
|
it "gracefully ignores objects with nil timestamps" do
|
@@ -93,40 +153,5 @@ describe Chronicle do
|
|
93
153
|
end
|
94
154
|
|
95
155
|
end
|
96
|
-
|
97
|
-
context "future dates" do
|
98
|
-
|
99
|
-
before(:each) do
|
100
|
-
@things = [
|
101
|
-
double("thing", :created_at => Time.now + 39*@day),
|
102
|
-
double("thing", :created_at => Time.now + 8*@day),
|
103
|
-
double("thing", :created_at => Time.now + 9*@day),
|
104
|
-
double("thing", :created_at => Time.now + 3*@day),
|
105
|
-
double("thing", :created_at => Time.now + 9*@minute),
|
106
|
-
]
|
107
|
-
@chronicle = Chronicle.new(@things, order: :asc)
|
108
|
-
end
|
109
|
-
|
110
|
-
it "doesn't have any empty eras" do
|
111
|
-
@chronicle.values.all? {|v| !v.empty? }.should == true
|
112
|
-
end
|
113
|
-
|
114
|
-
it "sorts era keys from oldest to newest" do
|
115
|
-
@chronicle.keys.first.should == '5 minutes from now'
|
116
|
-
@chronicle.keys.last.should == '1 month from now'
|
117
|
-
end
|
118
|
-
|
119
|
-
it "sorts objects in eras from oldest to newest" do
|
120
|
-
era = @chronicle['1 week from now']
|
121
|
-
era.size.should == 2
|
122
|
-
era.last.created_at.should be > era.first.created_at
|
123
|
-
end
|
124
|
-
|
125
|
-
it "doesn't lose any items during processing" do
|
126
|
-
@chronicle.values.flatten.size.should == @things.size
|
127
|
-
end
|
128
|
-
|
129
|
-
end
|
130
|
-
|
131
156
|
|
132
157
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chronicle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: hoe
|
@@ -106,7 +106,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
106
|
version: '0'
|
107
107
|
segments:
|
108
108
|
- 0
|
109
|
-
hash:
|
109
|
+
hash: 408063275270650171
|
110
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
111
|
none: false
|
112
112
|
requirements:
|
@@ -115,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
115
|
version: '0'
|
116
116
|
segments:
|
117
117
|
- 0
|
118
|
-
hash:
|
118
|
+
hash: 408063275270650171
|
119
119
|
requirements: []
|
120
120
|
rubyforge_project: chronicle
|
121
121
|
rubygems_version: 1.8.24
|