aggtive_record 0.0.1 → 0.1.2
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.
- checksums.yaml +8 -8
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +21 -3
- data/README.md +50 -10
- data/VERSION +1 -1
- data/aggtive_record.gemspec +93 -0
- data/lib/aggtive_record.rb +3 -1
- data/lib/aggtive_record/aggable.rb +7 -9
- data/lib/aggtive_record/egg_scopes.rb +10 -0
- data/lib/aggtive_record/egg_scopes/collation.rb +47 -0
- data/lib/aggtive_record/egg_scopes/collation/count_by.rb +20 -0
- data/lib/aggtive_record/egg_scopes/collation/rate.rb +35 -0
- data/lib/aggtive_record/egg_scopes/time_bucket.rb +30 -0
- data/lib/aggtive_record/egg_scopes/time_span.rb +43 -0
- data/lib/aggtive_record/time.rb +38 -28
- data/spec/functional/basic_object_run_spec.rb +5 -1
- data/spec/lib/aggable_spec.rb +37 -0
- data/spec/lib/count_by_spec.rb +37 -0
- data/spec/lib/rate_spec.rb +69 -0
- data/spec/lib/time_bucket_spec.rb +65 -0
- data/spec/lib/time_span_spec.rb +54 -0
- data/spec/spec_helper.rb +38 -1
- metadata +72 -7
- data/lib/aggtive_record/scopes.rb +0 -5
- data/lib/aggtive_record/scopes/collation.rb +0 -7
- data/lib/aggtive_record/scopes/time_bucket.rb +0 -12
- data/lib/aggtive_record/scopes/time_span.rb +0 -22
- data/spec/unit/aggable.rb +0 -24
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YzUxOTY3OTI2MWVmYjg0ZWU0YzVmOTUyMjA0NzhmNGEzMTU5ODhhMg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YmIzZWJjOGMzMzEzYTc0NzQwZjc0ODQ1YjUyNTVjZWU4ZTFhMmNmZA==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDBhZGYwOThkN2U5NjQ4NjNiOTA1ZDdkMTA1MTE4YmNmYjI3M2VjZWQxZDg0
|
10
|
+
NWZkNDE2NDYxNGRiM2QxYjMwMTJmNDIwMDM2MGFhYTAzNDBhM2Y5MGFmMjQw
|
11
|
+
MzI3NGRiYjkzZjMwNWU5NjdiYzhkMjA4OTA5YjBkMmZhNzExYzg=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZWQyMjNiMzE3Y2ZlYzZiYmE5MWJkYjdhOWQ1NGM3ODY4YzhmYmUyZDJhNWJl
|
14
|
+
MTg4MWUxOWRlZThkNTYyMWM5ZWUyNWZkN2QwN2MyNzQwNmRjNDhjYzM1ZDc0
|
15
|
+
NzdhMGU3OWVhNGVmMDRhZjNmMDkyY2Q4Yzk4NTVjODlkYzc1ZjY=
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
aggtive_record
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-head
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,22 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
+
activemodel (3.2.14)
|
5
|
+
activesupport (= 3.2.14)
|
6
|
+
builder (~> 3.0.0)
|
7
|
+
activerecord (3.2.14)
|
8
|
+
activemodel (= 3.2.14)
|
9
|
+
activesupport (= 3.2.14)
|
10
|
+
arel (~> 3.0.2)
|
11
|
+
tzinfo (~> 0.3.29)
|
4
12
|
activesupport (3.2.14)
|
5
13
|
i18n (~> 0.6, >= 0.6.4)
|
6
14
|
multi_json (~> 1.0)
|
7
15
|
addressable (2.3.5)
|
8
|
-
|
16
|
+
arel (3.0.2)
|
17
|
+
builder (3.0.4)
|
9
18
|
coderay (1.0.9)
|
19
|
+
database_cleaner (1.0.1)
|
10
20
|
diff-lcs (1.2.4)
|
11
21
|
faraday (0.8.8)
|
12
22
|
multipart-post (~> 1.2.0)
|
@@ -18,6 +28,8 @@ GEM
|
|
18
28
|
multi_json (~> 1.4)
|
19
29
|
nokogiri (~> 1.5.2)
|
20
30
|
oauth2
|
31
|
+
groupdate (1.0.4)
|
32
|
+
activerecord (>= 3.0.0)
|
21
33
|
hashie (2.0.5)
|
22
34
|
highline (1.6.19)
|
23
35
|
httpauth (0.2.0)
|
@@ -35,9 +47,10 @@ GEM
|
|
35
47
|
jwt (0.1.8)
|
36
48
|
multi_json (>= 1.5)
|
37
49
|
method_source (0.8.2)
|
38
|
-
multi_json (1.
|
50
|
+
multi_json (1.8.0)
|
39
51
|
multi_xml (0.5.5)
|
40
52
|
multipart-post (1.2.0)
|
53
|
+
mysql2 (0.3.13)
|
41
54
|
nokogiri (1.5.10)
|
42
55
|
oauth2 (0.9.2)
|
43
56
|
faraday (~> 0.8)
|
@@ -59,17 +72,22 @@ GEM
|
|
59
72
|
rspec-expectations (~> 2.14.0)
|
60
73
|
rspec-mocks (~> 2.14.0)
|
61
74
|
rspec-core (2.14.5)
|
62
|
-
rspec-expectations (2.14.
|
75
|
+
rspec-expectations (2.14.3)
|
63
76
|
diff-lcs (>= 1.1.3, < 2.0)
|
64
77
|
rspec-mocks (2.14.3)
|
65
78
|
slop (3.4.6)
|
79
|
+
tzinfo (0.3.37)
|
66
80
|
|
67
81
|
PLATFORMS
|
68
82
|
ruby
|
69
83
|
|
70
84
|
DEPENDENCIES
|
85
|
+
activerecord (~> 3.2.14)
|
71
86
|
activesupport (~> 3.2.14)
|
72
87
|
bundler
|
88
|
+
database_cleaner (~> 1.0.1)
|
89
|
+
groupdate
|
73
90
|
jeweler (~> 1.8.4)
|
91
|
+
mysql2
|
74
92
|
pry
|
75
93
|
rspec (~> 2.14.1)
|
data/README.md
CHANGED
@@ -61,31 +61,31 @@ This gem takes some inspiration from the highly useful groupdate.
|
|
61
61
|
|
62
62
|
@person.messages_count_during_past_year_per_day
|
63
63
|
|
64
|
-
@person.
|
64
|
+
@person.messages_rate_per_day_during_past_year
|
65
65
|
|
66
|
-
@person.
|
67
|
-
@person.
|
66
|
+
@person.messages_count_in_past_year_by_day
|
67
|
+
@person.messages_count_in_past_year_by_day
|
68
68
|
|
69
69
|
Message.count_during_past_year
|
70
70
|
|
71
71
|
|
72
72
|
A standard verbose call:
|
73
73
|
|
74
|
-
@person.
|
74
|
+
@person.sum_word_count_from_messages_during_past_year_by_day
|
75
75
|
|
76
76
|
|
77
77
|
*Under the hood query:*
|
78
78
|
|
79
|
-
@person.egg.
|
79
|
+
@person.egg.
|
80
|
+
collate(:sum, :word_count).
|
80
81
|
from(:messages).
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
in(:past_year).
|
83
|
+
by(:day)
|
84
84
|
*Which translates to this in ActiveRecord:*
|
85
85
|
|
86
86
|
@person.messages.
|
87
87
|
where('sent_at >= ?', 1.year.ago).
|
88
|
-
|
88
|
+
group_by_day(:sent_at).
|
89
89
|
sum(:word_count)
|
90
90
|
|
91
91
|
|
@@ -94,6 +94,46 @@ A standard verbose call:
|
|
94
94
|
|
95
95
|
|
96
96
|
|
97
|
-
### Scalars
|
97
|
+
### Scalars
|
98
|
+
|
99
|
+
@messages.count_during_past_year
|
100
|
+
@messages.rate_per_day_during_past_year
|
101
|
+
@messages.average_per_day_of_word_count_during_past_year
|
102
|
+
@messages.sum_of_word_count_during_past_year
|
103
|
+
|
104
|
+
### Arrays
|
105
|
+
*similar to groupdate*
|
106
|
+
|
107
|
+
@messages.count_in_past_year_by_day
|
108
|
+
@messages.count_in_past_year_by_weekday
|
109
|
+
@messages.count_in_past_year_by_dayhour
|
110
|
+
@messages.count_in_past_year_by_hour
|
111
|
+
|
112
|
+
@messages.rate_per_hour_in_past_year_by_day
|
113
|
+
@messages.average_per_day_of_word_count_during_past_year_by_month
|
114
|
+
@messages.sum_of_word_count_during_past_year_by_day
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
### Custom time periods
|
119
|
+
|
120
|
+
@messages.count_during_year(year: 2010)
|
121
|
+
@messages.count_during_years(years: 2008..2012)
|
122
|
+
@messages.count_during_day(day: '2012-12-01')
|
123
|
+
@messages.count_during_year_by_week(year: 2010, timezone: 'PST', first_day: 'Sunday')
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
count
|
129
|
+
|
130
|
+
rate_per
|
131
|
+
average_per, of_numeric_value
|
132
|
+
|
133
|
+
sum, of_numeric_value
|
134
|
+
|
135
|
+
list_of_proper_nouns_during_past_year
|
136
|
+
|
137
|
+
counted_list_of_proper_nouns_during_past_year
|
98
138
|
|
99
139
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.1.2
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "aggtive_record"
|
8
|
+
s.version = "0.1.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Dan Nguyen"]
|
12
|
+
s.date = "2013-09-23"
|
13
|
+
s.description = "This is not even remotely finished or even started on. Please don't download."
|
14
|
+
s.email = "dansonguyen@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
".ruby-gemset",
|
23
|
+
".ruby-version",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"README.md",
|
28
|
+
"Rakefile",
|
29
|
+
"VERSION",
|
30
|
+
"aggtive_record.gemspec",
|
31
|
+
"lib/aggtive_record.rb",
|
32
|
+
"lib/aggtive_record/adapter.rb",
|
33
|
+
"lib/aggtive_record/adapter/mysql.rb",
|
34
|
+
"lib/aggtive_record/aggable.rb",
|
35
|
+
"lib/aggtive_record/egg_scopes.rb",
|
36
|
+
"lib/aggtive_record/egg_scopes/collation.rb",
|
37
|
+
"lib/aggtive_record/egg_scopes/collation/count_by.rb",
|
38
|
+
"lib/aggtive_record/egg_scopes/collation/rate.rb",
|
39
|
+
"lib/aggtive_record/egg_scopes/time_bucket.rb",
|
40
|
+
"lib/aggtive_record/egg_scopes/time_span.rb",
|
41
|
+
"lib/aggtive_record/memo.rb",
|
42
|
+
"lib/aggtive_record/time.rb",
|
43
|
+
"spec/functional/basic_object_run_spec.rb",
|
44
|
+
"spec/lib/aggable_spec.rb",
|
45
|
+
"spec/lib/count_by_spec.rb",
|
46
|
+
"spec/lib/rate_spec.rb",
|
47
|
+
"spec/lib/time_bucket_spec.rb",
|
48
|
+
"spec/lib/time_span_spec.rb",
|
49
|
+
"spec/spec_helper.rb"
|
50
|
+
]
|
51
|
+
s.homepage = "http://github.com/dannguyen/aggtive_record"
|
52
|
+
s.licenses = ["MIT"]
|
53
|
+
s.require_paths = ["lib"]
|
54
|
+
s.rubygems_version = "2.0.5"
|
55
|
+
s.summary = "A convoluted way to describe aggregations of ActiveRecords over a datetime attribute"
|
56
|
+
|
57
|
+
if s.respond_to? :specification_version then
|
58
|
+
s.specification_version = 4
|
59
|
+
|
60
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
61
|
+
s.add_runtime_dependency(%q<groupdate>, [">= 0"])
|
62
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 3.2.14"])
|
63
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.14.1"])
|
64
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
|
66
|
+
s.add_development_dependency(%q<pry>, [">= 0"])
|
67
|
+
s.add_development_dependency(%q<database_cleaner>, ["~> 1.0.1"])
|
68
|
+
s.add_development_dependency(%q<mysql2>, [">= 0"])
|
69
|
+
s.add_development_dependency(%q<activerecord>, ["~> 3.2.14"])
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<groupdate>, [">= 0"])
|
72
|
+
s.add_dependency(%q<activesupport>, ["~> 3.2.14"])
|
73
|
+
s.add_dependency(%q<rspec>, ["~> 2.14.1"])
|
74
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
75
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
76
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
77
|
+
s.add_dependency(%q<database_cleaner>, ["~> 1.0.1"])
|
78
|
+
s.add_dependency(%q<mysql2>, [">= 0"])
|
79
|
+
s.add_dependency(%q<activerecord>, ["~> 3.2.14"])
|
80
|
+
end
|
81
|
+
else
|
82
|
+
s.add_dependency(%q<groupdate>, [">= 0"])
|
83
|
+
s.add_dependency(%q<activesupport>, ["~> 3.2.14"])
|
84
|
+
s.add_dependency(%q<rspec>, ["~> 2.14.1"])
|
85
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
86
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
87
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
88
|
+
s.add_dependency(%q<database_cleaner>, ["~> 1.0.1"])
|
89
|
+
s.add_dependency(%q<mysql2>, [">= 0"])
|
90
|
+
s.add_dependency(%q<activerecord>, ["~> 3.2.14"])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
data/lib/aggtive_record.rb
CHANGED
@@ -2,13 +2,18 @@ module AggtiveRecord
|
|
2
2
|
module Aggable
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
+
|
5
6
|
included do
|
7
|
+
include AggtiveRecord::EggScopes::TimeBucket
|
8
|
+
include AggtiveRecord::EggScopes::TimeSpan
|
9
|
+
include AggtiveRecord::EggScopes::Collation
|
10
|
+
|
6
11
|
class_attribute :datetime_attribute
|
7
12
|
end
|
8
13
|
|
9
14
|
module ClassMethods
|
10
|
-
def
|
11
|
-
raise ArgumentError unless is_a_datetime?(
|
15
|
+
def attr_datetime(attname)
|
16
|
+
raise ArgumentError unless is_a_datetime?(attname)
|
12
17
|
self.datetime_attribute = attname
|
13
18
|
end
|
14
19
|
|
@@ -18,12 +23,5 @@ module AggtiveRecord
|
|
18
23
|
end
|
19
24
|
|
20
25
|
|
21
|
-
def method_missing
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
def respond_to
|
26
|
-
|
27
|
-
end
|
28
26
|
end
|
29
27
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'collation/count_by'
|
2
|
+
require_relative 'collation/rate'
|
3
|
+
|
4
|
+
module AggtiveRecord
|
5
|
+
module EggScopes
|
6
|
+
module Collation
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include CountBy
|
9
|
+
include Rate
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# Public: a helper
|
13
|
+
# sorts the records after ActiveRecord query, ascending
|
14
|
+
#
|
15
|
+
# Returns a mapped array of timestamps
|
16
|
+
def enumerable_asc_sort(records)
|
17
|
+
records.map{|r| r.send(self.datetime_attribute)}.sort
|
18
|
+
end
|
19
|
+
|
20
|
+
def earliest_time_of(records)
|
21
|
+
enumerable_asc_sort(records).first
|
22
|
+
end
|
23
|
+
|
24
|
+
def latest_time_of(records)
|
25
|
+
enumerable_asc_sort(records).last
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public:
|
29
|
+
# records: An ActiveRecord collection
|
30
|
+
#
|
31
|
+
# Returns the number of seconds
|
32
|
+
# TODO - eliminate use of helper methods
|
33
|
+
def timespan_of(records)
|
34
|
+
latest_time_of(records) - earliest_time_of(records)
|
35
|
+
end
|
36
|
+
|
37
|
+
def timespan_to_now(records)
|
38
|
+
::Time.now - earliest_time_of(records)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module AggtiveRecord
|
2
|
+
module EggScopes
|
3
|
+
module Collation
|
4
|
+
module CountBy
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Public: convenience for #by_:time_period and #count
|
8
|
+
# self is a ActiveRecord scope
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def count_by(time_period)
|
12
|
+
span_foo = "by_#{time_period}"
|
13
|
+
self.send(span_foo).count
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module AggtiveRecord
|
2
|
+
module EggScopes
|
3
|
+
module Collation
|
4
|
+
module Rate
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Public: a rate
|
8
|
+
# expects that ActiveRelation has a grouping
|
9
|
+
# self is a ActiveRecord scope
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# Public
|
14
|
+
#
|
15
|
+
# Returns float indicating rate of records per given time period
|
16
|
+
def rate_per(time_period)
|
17
|
+
time_period_secs = AggtiveRecord::Time.to_seconds(time_period)
|
18
|
+
records = self.scoped.to_a
|
19
|
+
|
20
|
+
return records.size.to_f * time_period_secs / self.timespan_to_now(records)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
=begin
|
30
|
+
|
31
|
+
Record.rate_per :hour
|
32
|
+
.rate_per :day
|
33
|
+
|
34
|
+
|
35
|
+
=end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# defines named_scopes
|
2
|
+
|
3
|
+
require 'groupdate'
|
4
|
+
module AggtiveRecord
|
5
|
+
module EggScopes
|
6
|
+
module TimeBucket
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
#TODO: refactor
|
11
|
+
|
12
|
+
# create a method for each type of groupdate
|
13
|
+
# e.g. by_year == group_by_year(datetime_attribute)
|
14
|
+
AggtiveRecord::Time.periods.each do |bucket_name|
|
15
|
+
agg_foo_name = "by_#{bucket_name}".to_sym
|
16
|
+
groupdate_foo_name = "group_#{agg_foo_name}".to_sym
|
17
|
+
|
18
|
+
# defining scope here, dynamically
|
19
|
+
scope agg_foo_name, ->(*args){
|
20
|
+
send( groupdate_foo_name, self.datetime_attribute )
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# defines named_scopes
|
2
|
+
module AggtiveRecord
|
3
|
+
module EggScopes
|
4
|
+
module TimeSpan
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
|
9
|
+
included do
|
10
|
+
|
11
|
+
|
12
|
+
scope :past_time_periods, ->(num, period_name){ where("#{self.datetime_attribute} >= ?", num.send(period_name).send(:ago) ) }
|
13
|
+
|
14
|
+
|
15
|
+
# singular time periods
|
16
|
+
# e.g. scope :past_year, ->{ past_time_periods(1, :year)}
|
17
|
+
AggtiveRecord::Time.periods.each do |period_name|
|
18
|
+
foo_name = "past_#{period_name}".to_sym
|
19
|
+
# defining scope here, dynamically
|
20
|
+
scope foo_name, ->{
|
21
|
+
send( :past_time_periods, 1, period_name )
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# some custom time periods
|
27
|
+
# e.g. :past_14_days, :past_30_days
|
28
|
+
|
29
|
+
[[14, 'day'], [30, 'day'], [6, 'month']].each do |p_arr|
|
30
|
+
num, period = p_arr
|
31
|
+
foo_name = "past_#{num}_#{period.pluralize}"
|
32
|
+
scope foo_name, ->{
|
33
|
+
send( :past_time_periods, num, period)
|
34
|
+
}
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/aggtive_record/time.rb
CHANGED
@@ -1,35 +1,45 @@
|
|
1
1
|
module AggtiveRecord
|
2
2
|
module Time
|
3
3
|
|
4
|
+
# A week is always 7 * 24 * 60 * 60, for instance
|
4
5
|
UNIFORM_PERIODS = [:week, :day, :hour, :minute, :second]
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
6
|
+
|
7
|
+
# e.g. A year may have 365 days or 366
|
8
|
+
NON_UNIFORM_PERIODS = [:year, :month]
|
9
|
+
|
10
|
+
PERIODS = NON_UNIFORM_PERIODS + UNIFORM_PERIODS
|
11
|
+
|
12
|
+
OTHER_PERIODS = [:day_of_week, :hour_of_day]
|
13
|
+
|
14
|
+
# todo: better naming convention
|
15
|
+
|
16
|
+
|
17
|
+
def self.periods
|
18
|
+
PERIODS + OTHER_PERIODS
|
19
|
+
end
|
20
|
+
|
21
|
+
SECONDS_PER_DAY = 60 * 60 * 24
|
22
|
+
|
23
|
+
# probably reinventing the wheel here...
|
24
|
+
def self.to_seconds(sym)
|
25
|
+
case sym.to_sym
|
26
|
+
when :year
|
27
|
+
SECONDS_PER_DAY * 365
|
28
|
+
when :month
|
29
|
+
SECONDS_PER_DAY * 30
|
30
|
+
when :week
|
31
|
+
SECONDS_PER_DAY * 7
|
32
|
+
when :day
|
33
|
+
SECONDS_PER_DAY
|
34
|
+
when :hour
|
35
|
+
60 * 60
|
36
|
+
when :minute
|
37
|
+
60
|
38
|
+
when :second
|
39
|
+
1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
33
43
|
|
34
44
|
end
|
35
45
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
require 'hashie'
|
4
|
+
=begin
|
5
|
+
|
6
|
+
IGNORE ALL OF THIS
|
4
7
|
|
5
8
|
class AnObject < ActiveRecord::Base
|
6
9
|
include AggtiveRecord
|
@@ -57,4 +60,5 @@ describe 'AnObject with AggtiveRecord mixedin', skip: true do
|
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
60
|
-
end
|
63
|
+
end
|
64
|
+
=end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "AggtiveRecord::Aggable" do
|
4
|
+
|
5
|
+
context 'class methods' do
|
6
|
+
|
7
|
+
describe '#is_a_datetime?' do
|
8
|
+
it 'returns true if named column exists and is date/time' do
|
9
|
+
expect(MusicRecord.is_a_datetime?(:published_at)).to be_true
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns false if named column does not exist' do
|
13
|
+
expect(MusicRecord.is_a_datetime?(:not_a_column)).to be_false
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
it 'returns false if named column is not date/time' do
|
18
|
+
expect(MusicRecord.is_a_datetime?(:title)).to be_false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
describe '#attr_datetime' do
|
24
|
+
it 'sets @@datetime_attribute' do
|
25
|
+
# defined in spec helper already
|
26
|
+
# MusicRecord.attr_datetime :published_at
|
27
|
+
expect(MusicRecord.datetime_attribute).to eq :published_at
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'raises ArgumentError if not a valid datetime column' do
|
31
|
+
expect{MusicRecord.attr_datetime :title}.to raise_error ArgumentError
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AggtiveRecord::EggScopes::Collation::CountBy do
|
4
|
+
describe '#count_by' do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@a = MusicRecord.create(
|
8
|
+
published_at: "2010-01-20"
|
9
|
+
)
|
10
|
+
|
11
|
+
@b = MusicRecord.create(
|
12
|
+
published_at: "2010-03-12"
|
13
|
+
)
|
14
|
+
|
15
|
+
@c = MusicRecord.create(
|
16
|
+
published_at: "2012-05-12"
|
17
|
+
)
|
18
|
+
|
19
|
+
@d = MusicRecord.create(
|
20
|
+
published_at: "2012-05-12 15:00"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'basic groupdate usage' do
|
25
|
+
it 'can #count_by_year' do
|
26
|
+
a = MusicRecord.by_year.count.select{|k,v| k.year == 2010}.first
|
27
|
+
b = MusicRecord.count_by(:year).select{|k,v| k.year == 2010}.first
|
28
|
+
expect(a).to eq b
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
it 'should raise error if :time_period is erroneous'
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "AggtiveRecord::EggScopes -- .rate" do
|
4
|
+
|
5
|
+
|
6
|
+
describe '#rate_for' do
|
7
|
+
context 'singular periods' do
|
8
|
+
it 'should get about 1 a year' do
|
9
|
+
@record = MusicRecord.create published_at: 12.months.ago
|
10
|
+
expect(MusicRecord.scoped.rate_per(:year) ).to be_within(0.1).of 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'has two optional parameters' do
|
15
|
+
|
16
|
+
before(:each) do
|
17
|
+
MusicRecord.create(published_at: 1.day.ago )
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:records){ MusicRecord.scoped }
|
21
|
+
|
22
|
+
context 'first parameter is the :start_time' do
|
23
|
+
it 'bounds range with :start_time and Time.now' do
|
24
|
+
pending 'rethink'
|
25
|
+
expect(records.rate_per(:day, 2.days.ago)).to be_within(0.1).of 0.5
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'second parameter is the end_time' do
|
30
|
+
it 'bounds range with :start_time and :end_time' do
|
31
|
+
pending 'rethink'
|
32
|
+
expect(records.rate_per(:day, 2.days.ago)).to be_within(0.1).of 0.5
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'has no effect on :where' do
|
36
|
+
pending('This is troublesome. What if a user calls:
|
37
|
+
records.past_year.rate_per(:day, 10.days.ago, 3.days.ago)
|
38
|
+
-- too many surprises here!
|
39
|
+
')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
it 'should raise an error when a non-time period is passed in'
|
45
|
+
it 'should play nicely with other methods'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#timespan_of' do
|
50
|
+
before(:each) do
|
51
|
+
@latest = 1.months.ago
|
52
|
+
@earliest = 14.months.ago
|
53
|
+
|
54
|
+
MusicRecord.create published_at: @latest
|
55
|
+
MusicRecord.create published_at: 2.months.ago
|
56
|
+
MusicRecord.create published_at: @earliest
|
57
|
+
|
58
|
+
@collection = MusicRecord.all
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should return a range' do
|
62
|
+
expect(MusicRecord.timespan_of(@collection)).to be_within(0.1).of @latest - @earliest
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should get #latest record in a set without re-querying database'
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AggtiveRecord::EggScopes::TimeBucket do
|
4
|
+
|
5
|
+
|
6
|
+
describe '#by' do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@a = MusicRecord.create(
|
10
|
+
published_at: "2010-01-20"
|
11
|
+
)
|
12
|
+
|
13
|
+
@b = MusicRecord.create(
|
14
|
+
published_at: "2010-03-12"
|
15
|
+
)
|
16
|
+
|
17
|
+
@c = MusicRecord.create(
|
18
|
+
published_at: "2012-05-12"
|
19
|
+
)
|
20
|
+
|
21
|
+
@d = MusicRecord.create(
|
22
|
+
published_at: "2012-05-12 15:00"
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'basic groupdate usage' do
|
27
|
+
it 'groups #by_year' do
|
28
|
+
@group = MusicRecord.by_year.
|
29
|
+
count.select{|k,v| k.year == 2010}.first
|
30
|
+
expect(@group[1]).to eq 2
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'groups #by_month' do
|
34
|
+
@group = MusicRecord.by_month.
|
35
|
+
count.select{|k,v| k.year == 2012 && k.month == 5}.first
|
36
|
+
expect(@group[1]).to eq 2
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'groups #by_hour' do
|
40
|
+
@group = MusicRecord.by_hour.
|
41
|
+
count.
|
42
|
+
select{|k,v| k.hour == 15}.first
|
43
|
+
expect(@group[1]).to eq 1
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
context 'groupdate options' do
|
48
|
+
describe '#continuous' do
|
49
|
+
it 'should adopt the usage of groupdate series' do
|
50
|
+
pending %q{May make more sense to apply this operation at the end, as something separate from
|
51
|
+
groupdate as it's not related to ActiveRecord
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'populates default range between two endpoints'
|
56
|
+
it 'populates a specified range'
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AggtiveRecord::EggScopes::TimeSpan do
|
4
|
+
|
5
|
+
|
6
|
+
describe '#in...' do
|
7
|
+
before(:each) do
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
describe '#past...' do
|
13
|
+
context 'singular periods' do
|
14
|
+
it 'should span a #year' do
|
15
|
+
@record = MusicRecord.create published_at: 4.months.ago
|
16
|
+
MusicRecord.create published_at: 2.years.ago
|
17
|
+
expect(MusicRecord.past_year.count).to eq 1
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should span a #month' do
|
21
|
+
@record = MusicRecord.create published_at: 4.months.ago
|
22
|
+
MusicRecord.create published_at: 2.days.ago
|
23
|
+
expect(MusicRecord.past_month.count).to eq 1
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should span an #hour' do
|
27
|
+
@record = MusicRecord.create published_at: 2.hours.ago
|
28
|
+
MusicRecord.create published_at: 1.minute.ago
|
29
|
+
expect(MusicRecord.past_hour.count).to eq 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'plural periods' do
|
34
|
+
it 'has a few of these: 14 days, 30 days, 6 months, etc' do
|
35
|
+
[2.years.ago, 4.months.ago, 15.days.ago, 1.minute.ago].each do |t|
|
36
|
+
MusicRecord.create published_at: t
|
37
|
+
end
|
38
|
+
|
39
|
+
expect(MusicRecord.past_6_months.count).to eq 3
|
40
|
+
expect(MusicRecord.past_30_days.count).to eq 2
|
41
|
+
expect(MusicRecord.past_14_days.count).to eq 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
describe 'just period name' do
|
48
|
+
context 'single argument' do
|
49
|
+
it 'spans #year(1999)'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
3
|
require 'rspec'
|
4
|
-
require '
|
4
|
+
require 'mysql2'
|
5
|
+
|
6
|
+
require 'active_record'
|
7
|
+
require 'database_cleaner'
|
5
8
|
|
9
|
+
require 'aggtive_record'
|
10
|
+
require 'pry'
|
6
11
|
|
7
12
|
# Requires supporting files with custom matchers and macros, etc,
|
8
13
|
# in ./support/ and its subdirectories.
|
@@ -10,6 +15,34 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
10
15
|
|
11
16
|
|
12
17
|
|
18
|
+
ActiveRecord::Base.establish_connection(
|
19
|
+
:adapter => "mysql2",
|
20
|
+
:database => "test_aggtive_record",
|
21
|
+
username: 'root',
|
22
|
+
password: ''
|
23
|
+
|
24
|
+
)
|
25
|
+
|
26
|
+
ActiveRecord::Migration.verbose = false
|
27
|
+
|
28
|
+
ActiveRecord::Schema.define do
|
29
|
+
create_table :music_records , force: true do |t|
|
30
|
+
t.string "title"
|
31
|
+
t.string "genre"
|
32
|
+
t.datetime 'published_at'
|
33
|
+
t.string 'description'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class MusicRecord < ActiveRecord::Base
|
38
|
+
include AggtiveRecord::Aggable
|
39
|
+
|
40
|
+
attr_datetime :published_at
|
41
|
+
end
|
42
|
+
|
43
|
+
DatabaseCleaner.strategy = :truncation
|
44
|
+
|
45
|
+
|
13
46
|
|
14
47
|
RSpec.configure do |config|
|
15
48
|
config.color_enabled = true
|
@@ -17,9 +50,13 @@ RSpec.configure do |config|
|
|
17
50
|
config.formatter = :documentation # :progress, :html, :textmate
|
18
51
|
|
19
52
|
config.before(:each) do
|
53
|
+
DatabaseCleaner.start
|
20
54
|
end
|
21
55
|
|
22
56
|
config.after(:each) do
|
57
|
+
DatabaseCleaner.clean
|
23
58
|
end
|
24
59
|
end
|
25
60
|
|
61
|
+
|
62
|
+
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aggtive_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Nguyen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-09-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: groupdate
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: activesupport
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +94,48 @@ dependencies:
|
|
80
94
|
- - ! '>='
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: database_cleaner
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.0.1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.0.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: mysql2
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: activerecord
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.2.14
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ~>
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 3.2.14
|
83
139
|
description: This is not even remotely finished or even started on. Please don't download.
|
84
140
|
email: dansonguyen@gmail.com
|
85
141
|
executables: []
|
@@ -90,25 +146,34 @@ extra_rdoc_files:
|
|
90
146
|
files:
|
91
147
|
- .document
|
92
148
|
- .rspec
|
149
|
+
- .ruby-gemset
|
150
|
+
- .ruby-version
|
93
151
|
- Gemfile
|
94
152
|
- Gemfile.lock
|
95
153
|
- LICENSE.txt
|
96
154
|
- README.md
|
97
155
|
- Rakefile
|
98
156
|
- VERSION
|
157
|
+
- aggtive_record.gemspec
|
99
158
|
- lib/aggtive_record.rb
|
100
159
|
- lib/aggtive_record/adapter.rb
|
101
160
|
- lib/aggtive_record/adapter/mysql.rb
|
102
161
|
- lib/aggtive_record/aggable.rb
|
162
|
+
- lib/aggtive_record/egg_scopes.rb
|
163
|
+
- lib/aggtive_record/egg_scopes/collation.rb
|
164
|
+
- lib/aggtive_record/egg_scopes/collation/count_by.rb
|
165
|
+
- lib/aggtive_record/egg_scopes/collation/rate.rb
|
166
|
+
- lib/aggtive_record/egg_scopes/time_bucket.rb
|
167
|
+
- lib/aggtive_record/egg_scopes/time_span.rb
|
103
168
|
- lib/aggtive_record/memo.rb
|
104
|
-
- lib/aggtive_record/scopes.rb
|
105
|
-
- lib/aggtive_record/scopes/collation.rb
|
106
|
-
- lib/aggtive_record/scopes/time_bucket.rb
|
107
|
-
- lib/aggtive_record/scopes/time_span.rb
|
108
169
|
- lib/aggtive_record/time.rb
|
109
170
|
- spec/functional/basic_object_run_spec.rb
|
171
|
+
- spec/lib/aggable_spec.rb
|
172
|
+
- spec/lib/count_by_spec.rb
|
173
|
+
- spec/lib/rate_spec.rb
|
174
|
+
- spec/lib/time_bucket_spec.rb
|
175
|
+
- spec/lib/time_span_spec.rb
|
110
176
|
- spec/spec_helper.rb
|
111
|
-
- spec/unit/aggable.rb
|
112
177
|
homepage: http://github.com/dannguyen/aggtive_record
|
113
178
|
licenses:
|
114
179
|
- MIT
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module AggtiveRecord
|
2
|
-
module Scopes
|
3
|
-
module TimeSpan
|
4
|
-
|
5
|
-
extend ActiveRecord::Concern
|
6
|
-
|
7
|
-
# where("#{}" => 1.year.ago..Time.now)
|
8
|
-
#past_year
|
9
|
-
|
10
|
-
mattr_reader :prefix
|
11
|
-
self.prefix = 'during'
|
12
|
-
|
13
|
-
=begin
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
=end
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/spec/unit/aggable.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe "AggtiveRecord::Aggable" do
|
4
|
-
|
5
|
-
context 'class methods' do
|
6
|
-
|
7
|
-
describe '#is_a_datetime?' do
|
8
|
-
it 'returns true if named column exists and is date/time' do
|
9
|
-
@klass.is_a_datetime?
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'returns false if named column does not exist'
|
13
|
-
it 'returns false if named column is not date/time'
|
14
|
-
end
|
15
|
-
|
16
|
-
|
17
|
-
describe '#attr_datetime' do
|
18
|
-
it 'sets @@datetime_attribute'
|
19
|
-
it 'raises ArgumentError if not a valid datetime column'
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|