chronicle 0.0.1
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 +4 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README.md +60 -0
- data/Rakefile +6 -0
- data/chronicle.gemspec +28 -0
- data/lib/chronicle.rb +82 -0
- data/lib/chronicle/version.rb +3 -0
- data/spec/chronicle_spec.rb +84 -0
- data/spec/spec_helper.rb +14 -0
- metadata +112 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
Chronicle
|
2
|
+
=========
|
3
|
+
|
4
|
+
Chronicle groups collections of ActiveRecord objects into chronologically ordered hashes.
|
5
|
+
It uses [Chronic](https://github.com/mojombo/chronic/) to parse natural language dates
|
6
|
+
and [ActiveSupport::OrderedHash](http://apidock.com/rails/ActiveSupport/OrderedHash)
|
7
|
+
to keep the hash order consistent.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Before...
|
11
|
+
[
|
12
|
+
object_1,
|
13
|
+
object_2,
|
14
|
+
object_3,
|
15
|
+
object_4,
|
16
|
+
object_5,
|
17
|
+
object_6,
|
18
|
+
object_7,
|
19
|
+
object_8
|
20
|
+
]
|
21
|
+
|
22
|
+
# After...
|
23
|
+
{
|
24
|
+
"just now": [object_10, object_9],
|
25
|
+
"two hours ago": [object_8, object_7, object_6],
|
26
|
+
"yesterday": [object_5, object_4, object_3],
|
27
|
+
"6 months ago": [object_2],
|
28
|
+
"1 year ago": [object_1]
|
29
|
+
}
|
30
|
+
```
|
31
|
+
|
32
|
+
Installation
|
33
|
+
------------
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# Put this in your Gemfile and smoke it.
|
37
|
+
gem 'chronicle'
|
38
|
+
```
|
39
|
+
|
40
|
+
Usage
|
41
|
+
-----
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# Fetch some objects (presumably ActiveRecord)
|
45
|
+
things = Thing.all
|
46
|
+
|
47
|
+
# Put them into buckets, using Chronicle's default eras
|
48
|
+
chronicle = Chronicle.new(things)
|
49
|
+
|
50
|
+
# To deviate from the default eras...
|
51
|
+
chronicle = Chronicle.new(things, :eras => ["5 minutes ago", "2 hours ago", "three weeks ago"])
|
52
|
+
|
53
|
+
# To sort based on an attribute other than :created_at
|
54
|
+
chronicle = Chronicle.new(things, :date_attr => :updated_at)
|
55
|
+
```
|
56
|
+
|
57
|
+
License
|
58
|
+
-------
|
59
|
+
|
60
|
+
MIT License. Do whatever you want.
|
data/Rakefile
ADDED
data/chronicle.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "chronicle/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "chronicle"
|
7
|
+
s.version = Chronicle::VERSION
|
8
|
+
s.authors = ["Zeke Sikelianos"]
|
9
|
+
s.email = ["zeke@sikelianos.com"]
|
10
|
+
s.homepage = "http://zeke.sikelianos.com"
|
11
|
+
s.summary = %q{Chronicle groups collections of ActiveRecord objects into chronologically ordered hashes.}
|
12
|
+
s.description = %q{Chronicle groups collections of ActiveRecord objects into chronologically ordered hashes.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "chronicle"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "hoe"
|
23
|
+
s.add_development_dependency 'rspec', '~> 2.8.0'
|
24
|
+
s.add_development_dependency 'autotest'
|
25
|
+
|
26
|
+
s.add_runtime_dependency "chronic"
|
27
|
+
s.add_dependency 'rails'
|
28
|
+
end
|
data/lib/chronicle.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'chronic'
|
3
|
+
require 'active_support/all' # OrderedHash
|
4
|
+
require 'chronicle'
|
5
|
+
|
6
|
+
module Chronicle
|
7
|
+
|
8
|
+
def self.new(collection, options={})
|
9
|
+
defaults = {
|
10
|
+
:eras => Chronicle.default_eras,
|
11
|
+
:date_attr => :created_at
|
12
|
+
}
|
13
|
+
ChronicleHash.new(collection, defaults.merge(options))
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.default_eras
|
17
|
+
[
|
18
|
+
"3 years ago",
|
19
|
+
"2 years ago",
|
20
|
+
"one year ago",
|
21
|
+
"6 months ago",
|
22
|
+
"3 months ago",
|
23
|
+
"2 months ago",
|
24
|
+
"one month ago",
|
25
|
+
"3 weeks ago",
|
26
|
+
"2 weeks ago",
|
27
|
+
"one week ago",
|
28
|
+
"6 days ago",
|
29
|
+
"4 days ago",
|
30
|
+
"3 days ago",
|
31
|
+
"2 days ago",
|
32
|
+
"yesterday",
|
33
|
+
"8 hours ago",
|
34
|
+
"4 hours ago",
|
35
|
+
"2 hours ago",
|
36
|
+
"1 hour ago",
|
37
|
+
"30 minutes ago",
|
38
|
+
"20 minutes ago",
|
39
|
+
"10 minutes ago",
|
40
|
+
"5 minutes ago",
|
41
|
+
"3 minutes ago",
|
42
|
+
"2 minutes ago",
|
43
|
+
"one minute ago",
|
44
|
+
"just now"
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
class ChronicleHash < ActiveSupport::OrderedHash
|
49
|
+
|
50
|
+
def initialize(collection, options)
|
51
|
+
|
52
|
+
eras = options[:eras]
|
53
|
+
|
54
|
+
# Make sure 'just now' is included, so no objects fall through the cracks
|
55
|
+
eras << "just now" unless eras.any? {|era| era =~ /now/i }
|
56
|
+
|
57
|
+
# Ensure all eras can be parsed
|
58
|
+
eras.each do |era|
|
59
|
+
raise "Could not parse era: #{era}" if Chronic.parse(era).nil?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sort eras oldest to newest
|
63
|
+
eras = eras.sort_by {|era| Chronic.parse(era) }
|
64
|
+
|
65
|
+
# Initialize all OrderedHash keys chronologically (newest to oldest)
|
66
|
+
eras.reverse.each {|era| self[era] = [] }
|
67
|
+
|
68
|
+
# Find the oldest era in which each object was created
|
69
|
+
collection.sort_by {|obj| obj.send(options[:date_attr])}.reverse.each do |obj|
|
70
|
+
era = eras.find {|era| obj.send(options[:date_attr]) < Chronic.parse(era) }
|
71
|
+
self[era] << obj
|
72
|
+
end
|
73
|
+
|
74
|
+
# Remove keys for empty eras
|
75
|
+
self.keys.each {|k| self.delete(k) if self[k].blank? }
|
76
|
+
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Chronicle do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@things = [
|
7
|
+
double("thing", :created_at => 39.days.ago),
|
8
|
+
double("thing", :created_at => 8.days.ago),
|
9
|
+
double("thing", :created_at => 9.days.ago),
|
10
|
+
double("thing", :created_at => 3.days.ago),
|
11
|
+
double("thing", :created_at => 10.minutes.ago),
|
12
|
+
double("thing", :created_at => Time.now)
|
13
|
+
]
|
14
|
+
|
15
|
+
@chronicle = Chronicle.new(@things)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "doesn't have any empty eras" do
|
19
|
+
@chronicle.values.all? {|v| v.present? }.should == true
|
20
|
+
end
|
21
|
+
|
22
|
+
it "sorts era keys from newest to oldest" do
|
23
|
+
@chronicle.keys.first.should == 'just now'
|
24
|
+
@chronicle.keys.last.should == 'one month ago'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "sorts objects in eras from newest to oldest" do
|
28
|
+
era = @chronicle['one week ago']
|
29
|
+
era.size.should == 2
|
30
|
+
era.last.created_at.should be < era.first.created_at
|
31
|
+
end
|
32
|
+
|
33
|
+
it "doesn't lose any items during processing" do
|
34
|
+
@chronicle.values.flatten.size.should == @things.size
|
35
|
+
end
|
36
|
+
|
37
|
+
it "accounts for objects that were just created" do
|
38
|
+
now = @chronicle['just now']
|
39
|
+
now.should_not be_blank
|
40
|
+
now.should be_an(Array)
|
41
|
+
now.first.should == @things.last
|
42
|
+
end
|
43
|
+
|
44
|
+
context "custom eras" do
|
45
|
+
|
46
|
+
it "add 'just now' to the list of eras if it's missing, to keep from losing very new objects" do
|
47
|
+
@chronicle = Chronicle.new(@things, :eras => ['3 minutes ago', '35 days ago'])
|
48
|
+
@chronicle.keys.first.should == 'just now'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "allows custom eras to be set in any order" do
|
52
|
+
@chronicle = Chronicle.new(@things, :eras => ['just now', '35 days ago', '3 minutes ago'])
|
53
|
+
@chronicle.keys.should == ['just now', '3 minutes ago', '35 days ago']
|
54
|
+
end
|
55
|
+
|
56
|
+
it "raises an exception for eras that cannot be parse" do
|
57
|
+
expect { Chronicle.new(@things, :eras => ['63 eons hence']) }.to raise_error("Could not parse era: 63 eons hence")
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
context "custom date attribute" do
|
63
|
+
|
64
|
+
before(:each) do
|
65
|
+
@things = [
|
66
|
+
double("thing", :updated_at => 369.days.ago, :created_at => nil),
|
67
|
+
double("thing", :updated_at => 9.days.ago, :created_at => nil),
|
68
|
+
double("thing", :updated_at => 8.days.ago, :created_at => nil),
|
69
|
+
double("thing", :updated_at => 3.days.ago, :created_at => nil),
|
70
|
+
double("thing", :updated_at => 10.minutes.ago, :created_at => nil),
|
71
|
+
double("thing", :updated_at => Time.now, :created_at => nil)
|
72
|
+
]
|
73
|
+
end
|
74
|
+
|
75
|
+
it "allows an alternative to created_at" do
|
76
|
+
@chronicle = Chronicle.new(@things, :date_attr => :updated_at)
|
77
|
+
@chronicle.values.flatten.size.should == @things.size
|
78
|
+
@chronicle.keys.last.should == 'one year ago'
|
79
|
+
@chronicle.keys.first.should == 'just now'
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'chronicle'
|
5
|
+
include Chronicle
|
6
|
+
|
7
|
+
require 'active_support/all' # to get methods like blank? and starts_with?
|
8
|
+
|
9
|
+
# include ActionView::Helpers
|
10
|
+
include ActiveSupport
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
# some (optional) config here
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chronicle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Zeke Sikelianos
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: hoe
|
16
|
+
requirement: &70361455414560 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70361455414560
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70361455413780 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.8.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70361455413780
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: autotest
|
38
|
+
requirement: &70361455412920 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70361455412920
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: chronic
|
49
|
+
requirement: &70361455411820 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70361455411820
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rails
|
60
|
+
requirement: &70361455410980 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70361455410980
|
69
|
+
description: Chronicle groups collections of ActiveRecord objects into chronologically
|
70
|
+
ordered hashes.
|
71
|
+
email:
|
72
|
+
- zeke@sikelianos.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- .rspec
|
79
|
+
- Gemfile
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- chronicle.gemspec
|
83
|
+
- lib/chronicle.rb
|
84
|
+
- lib/chronicle/version.rb
|
85
|
+
- spec/chronicle_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
homepage: http://zeke.sikelianos.com
|
88
|
+
licenses: []
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project: chronicle
|
107
|
+
rubygems_version: 1.8.10
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: Chronicle groups collections of ActiveRecord objects into chronologically
|
111
|
+
ordered hashes.
|
112
|
+
test_files: []
|