influxer 0.0.1 → 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.
- checksums.yaml +4 -4
- data/.gitignore +1 -2
- data/.travis.yml +3 -2
- data/Changelog.md +19 -0
- data/Gemfile +0 -13
- data/MIT-LICENSE +1 -1
- data/README.md +51 -6
- data/gemfiles/rails42.gemfile +7 -0
- data/influxer.gemspec +7 -5
- data/lib/influxer/client.rb +46 -0
- data/lib/influxer/config.rb +10 -0
- data/lib/influxer/metrics/fanout.rb +53 -0
- data/lib/influxer/metrics/metrics.rb +33 -5
- data/lib/influxer/metrics/relation/fanout_query.rb +33 -0
- data/lib/influxer/metrics/relation/time_query.rb +73 -0
- data/lib/influxer/metrics/relation.rb +177 -39
- data/lib/influxer/metrics/scoping/default.rb +28 -0
- data/lib/influxer/metrics/scoping/named.rb +18 -0
- data/lib/influxer/metrics/scoping.rb +56 -0
- data/lib/influxer/model.rb +3 -1
- data/lib/influxer/version.rb +1 -1
- data/spec/client_spec.rb +34 -0
- data/spec/dummy/app/metrics/testo_metrics.rb +1 -1
- data/spec/dummy/app/models/testo.rb +2 -0
- data/spec/dummy/config/application.rb +1 -0
- data/spec/dummy/config/environments/test.rb +0 -1
- data/spec/metrics/fanout_spec.rb +46 -0
- data/spec/metrics/metrics_spec.rb +42 -7
- data/spec/metrics/relation_spec.rb +199 -51
- data/spec/metrics/scoping_spec.rb +68 -0
- data/spec/model/testo_spec.rb +12 -3
- data/spec/spec_helper.rb +1 -1
- metadata +78 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bb445897c491a860d94249f174bc02e9ad6b61f
|
4
|
+
data.tar.gz: 011778f550be687a6d1503618f98ec70dd454c2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85e6922159fc728186d5b3a0736ff1c9950f1af0a1d70f8452e91f6e1759e2508a1e1566d9f55482ffefe62a01b39d2843aea81aec8e5382217e015291b26dd5
|
7
|
+
data.tar.gz: 232e5a9108e36d1378134a1e26fbc9d25367a569cae4a3f7ab752c2c48f3da5ea47e395019032fd2896cc6ed90421c6e8b07283b94690453e2d8ec120b0fe144
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
language: ruby
|
2
|
+
cache: bundler
|
2
3
|
rvm:
|
3
4
|
- 2.0.0
|
4
5
|
- 2.1
|
@@ -14,8 +15,8 @@ matrix:
|
|
14
15
|
- rvm: 2.1
|
15
16
|
gemfile: gemfiles/rails40.gemfile
|
16
17
|
|
17
|
-
- rvm: 2.
|
18
|
+
- rvm: 2.1
|
18
19
|
gemfile: gemfiles/rails41.gemfile
|
19
20
|
|
20
21
|
- rvm: 2.1
|
21
|
-
gemfile: gemfiles/
|
22
|
+
gemfile: gemfiles/rails42.gemfile
|
data/Changelog.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
## 0.1.0
|
2
|
+
- Add logs
|
3
|
+
- Add `foreign_key` param to `has_metrics` options
|
4
|
+
|
5
|
+
## 0.1.0-rc
|
6
|
+
- Fix `Relation#to_a` (now returns array of points correctrly)
|
7
|
+
- Fix fanout queries with array args (now use `merge(Regexp)`)
|
8
|
+
|
9
|
+
## 0.1.0-alpha
|
10
|
+
- Add `time` method to Relation to group by time with constants (`:hour`, `:day`, etc) and fill support
|
11
|
+
- Series names now properly quoted with double-quotes
|
12
|
+
- Using regexps, ranges and arrays within `where` clause
|
13
|
+
- `where.not(...)` support
|
14
|
+
- Add `past` and `since` methods
|
15
|
+
- Add `merge` method and support for regexp series
|
16
|
+
- Add `delete_all` support
|
17
|
+
- Add cache support (using `Rails.cache`)
|
18
|
+
- Scopes (default and named)
|
19
|
+
- Support for fanout series
|
data/Gemfile
CHANGED
@@ -1,16 +1,3 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
|
-
|
3
|
-
# Declare your gem's dependencies in influxer.gemspec.
|
4
|
-
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
-
# development dependencies will be added by default to the :development group.
|
6
|
-
gem 'sqlite3'
|
7
|
-
gem 'pry-byebug'
|
8
2
|
gemspec
|
9
3
|
|
10
|
-
# Declare any dependencies that are still in development here instead of in
|
11
|
-
# your gemspec. These might include edge Rails or gems from your path or
|
12
|
-
# Git. Remember to move these dependencies to your gemspec before releasing
|
13
|
-
# your gem to rubygems.org.
|
14
|
-
|
15
|
-
# To use debugger
|
16
|
-
# gem 'debugger'
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -2,14 +2,59 @@
|
|
2
2
|
|
3
3
|
## Influxer
|
4
4
|
|
5
|
-
|
5
|
+
ActiveRecord-like wrapper for [influxdb-ruby](https://github.com/influxdb/influxdb-ruby) with many useful features, such as:
|
6
|
+
- Familar query language (use `select`, `where`, `not`, `group` etc).
|
7
|
+
- Support for Regex conditions: `where(page_id: /^home\/.*/) #=> select * ... where page_id=~/^home\/.*/`.
|
8
|
+
- Special query methods for InfluxDB:
|
9
|
+
- `time` - group by time (e.g. `Metrics.time(:hour) => # select * ... group by time(1h)`);
|
10
|
+
- `past` - get only points for last hour/minute/whatever (e.g. `Metrics.past(:day) => # select * ... where time > now() - 1d`);
|
11
|
+
- `since` - get only points since date (e.g. `Metrics.since(Time.utc(2014,12,31)) => # select * ... where time > 1419984000s`);
|
12
|
+
- `merge` - merge series.
|
13
|
+
- Scopes support
|
14
|
+
```
|
15
|
+
class Metrics < Influxer::Metrics
|
16
|
+
default_scope -> { time(:hour).limit(1000) }
|
17
|
+
scope :unlimited, -> { limit(nil) }
|
18
|
+
scope :by_account, -> (id) { where(account_id: id) if id.present? }
|
19
|
+
end
|
6
20
|
|
7
|
-
|
21
|
+
Metrics.by_account(1)
|
22
|
+
# => select * from "metrics" group by time(1h) where account_id=1 limit 1000
|
8
23
|
|
9
|
-
|
24
|
+
Metrics.unlimited.by_account(1).time(:week)
|
25
|
+
# => select * from "metrics" group by time(1w) where account_id=1
|
10
26
|
|
11
|
-
|
27
|
+
```
|
28
|
+
- Support for handling fanout series as one metrics.
|
29
|
+
```
|
30
|
+
class Metrics < Influxer::Metrics
|
31
|
+
fanout :account, :user, :page
|
32
|
+
end
|
12
33
|
|
13
|
-
|
34
|
+
Metrics.where(account: 1)
|
35
|
+
# => select * from "metrics_account_1"
|
14
36
|
|
15
|
-
|
37
|
+
|
38
|
+
Metrics.where(page: 'home').where(user: 12)
|
39
|
+
# => select * from "metrics_user_12_page_home"
|
40
|
+
|
41
|
+
Metrics.where(page: /(home|faq)/).where(account: 1).where(user: 12)
|
42
|
+
# => select * from /^metrics_account_1_user_12_page_(home|faq)$/
|
43
|
+
|
44
|
+
```
|
45
|
+
- Integrate with your model:
|
46
|
+
```
|
47
|
+
class UserVisits < Influxer::Metrics
|
48
|
+
end
|
49
|
+
|
50
|
+
class User < ActiveRecord::Base
|
51
|
+
has_metrics :visits
|
52
|
+
end
|
53
|
+
|
54
|
+
user = User.find(1)
|
55
|
+
user.visits.write(page_id: 'home')
|
56
|
+
#=> < creates point {user_id: 1, page_id: 'home'} in 'user_visits' series >
|
57
|
+
|
58
|
+
user.visits.where(page_id: 'home')
|
59
|
+
#=> select * from user_visits where page_id='home'
|
60
|
+
```
|
data/influxer.gemspec
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
$:.push File.expand_path("../lib", __FILE__)
|
2
2
|
|
3
|
-
# Maintain your gem's version:
|
4
3
|
require "influxer/version"
|
5
4
|
|
6
|
-
# Describe your gem and declare its dependencies:
|
7
5
|
Gem::Specification.new do |s|
|
8
6
|
s.name = "influxer"
|
9
7
|
s.version = Influxer::VERSION
|
10
8
|
s.authors = ["Vlad Dem"]
|
11
9
|
s.email = ["dementiev.vm@gmail.com"]
|
12
10
|
s.homepage = "http://github.com/palkan/influxer"
|
13
|
-
s.summary = "InfluxDB
|
11
|
+
s.summary = "InfluxDB for Rails"
|
14
12
|
s.description = "InfluxDB the Rails way"
|
15
13
|
s.license = "MIT"
|
16
14
|
|
@@ -22,6 +20,10 @@ Gem::Specification.new do |s|
|
|
22
20
|
|
23
21
|
s.add_development_dependency "timecop"
|
24
22
|
s.add_development_dependency "simplecov", ">= 0.3.8"
|
25
|
-
|
26
|
-
s.add_development_dependency
|
23
|
+
|
24
|
+
s.add_development_dependency 'sqlite3'
|
25
|
+
s.add_development_dependency 'pry'
|
26
|
+
s.add_development_dependency 'pry-byebug'
|
27
|
+
s.add_development_dependency "rspec", "~> 3.1.0"
|
28
|
+
s.add_development_dependency "rspec-rails", "~> 3.1.0"
|
27
29
|
end
|
data/lib/influxer/client.rb
CHANGED
@@ -3,7 +3,53 @@ require 'influxdb'
|
|
3
3
|
module Influxer
|
4
4
|
class Client < ::InfluxDB::Client
|
5
5
|
def initialize
|
6
|
+
@instrumenter = ActiveSupport::Notifications.instrumenter
|
6
7
|
super Influxer.config.database, Influxer.config.as_json.symbolize_keys!
|
7
8
|
end
|
9
|
+
|
10
|
+
def cached_query(sql)
|
11
|
+
log(sql) do
|
12
|
+
unless Influxer.config.cache == false
|
13
|
+
Rails.cache.fetch(normalized_cache_key(sql), cache_options(sql)) { self.query(sql) }
|
14
|
+
else
|
15
|
+
self.query(sql)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def log(sql)
|
23
|
+
return yield unless logger.debug?
|
24
|
+
|
25
|
+
_start = Time.now
|
26
|
+
res = yield
|
27
|
+
_duration = (Time.now - _start)*1000
|
28
|
+
|
29
|
+
name = "InfluxDB SQL (#{_duration.round(1)}ms)"
|
30
|
+
|
31
|
+
# bold black name and blue query string
|
32
|
+
msg = "\e[1m\e[30m#{name}\e[0m \e[34m#{sql}\e[0m"
|
33
|
+
logger.debug msg
|
34
|
+
res
|
35
|
+
end
|
36
|
+
|
37
|
+
def cache_options(sql=nil)
|
38
|
+
options = Influxer.config.cache.dup
|
39
|
+
# if sql contains 'now()' set expires to 1 minute or :cache_now_for value of config.cache if defined
|
40
|
+
if sql =~ /\snow\(\)/
|
41
|
+
options[:expires_in] = options[:cache_now_for] || 60
|
42
|
+
end
|
43
|
+
options
|
44
|
+
end
|
45
|
+
|
46
|
+
# add prefix; remove whitespaces
|
47
|
+
def normalized_cache_key(sql)
|
48
|
+
"influxer:#{sql.gsub(/\s*/, '')}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def logger
|
52
|
+
Rails.logger
|
53
|
+
end
|
8
54
|
end
|
9
55
|
end
|
data/lib/influxer/config.rb
CHANGED
@@ -7,6 +7,7 @@ module Influxer
|
|
7
7
|
:password,
|
8
8
|
:use_ssl,
|
9
9
|
:async,
|
10
|
+
:cache,
|
10
11
|
:retry,
|
11
12
|
:time_precision,
|
12
13
|
:initial_delay,
|
@@ -20,6 +21,7 @@ module Influxer
|
|
20
21
|
@port = 8083
|
21
22
|
@use_ssl = false
|
22
23
|
@async = true
|
24
|
+
@cache = false
|
23
25
|
@retry = false
|
24
26
|
@time_precision = 's'
|
25
27
|
@max_delay = 30
|
@@ -42,6 +44,14 @@ module Influxer
|
|
42
44
|
config.each do |key, val|
|
43
45
|
self.send("#{key}=",val)
|
44
46
|
end
|
47
|
+
|
48
|
+
# we want pass @cache value as options to cache store, so we want it to be a Hash
|
49
|
+
if @cache == true
|
50
|
+
@cache = {}
|
51
|
+
elsif @cache != false
|
52
|
+
# cache keys should be symbols to work as Rails.cache options
|
53
|
+
@cache.symbolize_keys!
|
54
|
+
end
|
45
55
|
end
|
46
56
|
end
|
47
57
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Influxer
|
2
|
+
module Fanout
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :fanouts, :fanouts_by_name, :fanout_options
|
7
|
+
self.fanouts = []
|
8
|
+
self.fanouts_by_name = {} # to use within `fanout?`
|
9
|
+
self.fanout_options = {delimeter: "_"}
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# Define fanouts for metrics as array of keys.
|
14
|
+
# Order of keys is important.
|
15
|
+
# Fanout delimeter can be set with 'delimiter' option (defaults to '_').
|
16
|
+
#
|
17
|
+
# class MyMetrics < Influxer::Metrics
|
18
|
+
# set_series "my_points"
|
19
|
+
# fanout :account_id, :user_id
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# MyMetrics.where(user_id: 1).where(account_id: 10)
|
23
|
+
# # select * from my_points_account_id_10_user_id_1
|
24
|
+
#
|
25
|
+
# class MyMetrics < Influxer::Metrics
|
26
|
+
# set_series "my_points"
|
27
|
+
# fanout :account_id, :user_id, delimiter: "."
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# MyMetrics.where(user_id: 1).where(account_id: 10).where("req_time > 1000")
|
31
|
+
# # select * from my_points.account_id.10.user_id.1 where req_time > 1000
|
32
|
+
|
33
|
+
def fanout(*args, **hargs)
|
34
|
+
self.fanout_options = self.fanout_options.merge hargs
|
35
|
+
|
36
|
+
names = args.map(&:to_s) # convert all to strings (because args can be both Symbols and Strings)
|
37
|
+
|
38
|
+
self.fanouts = (self.fanouts+names).uniq
|
39
|
+
|
40
|
+
names_hash = {}
|
41
|
+
names.each do |name|
|
42
|
+
names_hash[name] = 1
|
43
|
+
end
|
44
|
+
|
45
|
+
self.fanouts_by_name = self.fanouts_by_name.merge names_hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def fanout?(key)
|
49
|
+
self.fanouts_by_name.key?(key.to_s)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'influxer/metrics/scoping'
|
2
|
+
require 'influxer/metrics/fanout'
|
3
|
+
|
1
4
|
module Influxer
|
2
5
|
class MetricsError < StandardError; end
|
3
6
|
class MetricsInvalid < MetricsError; end
|
@@ -7,10 +10,13 @@ module Influxer
|
|
7
10
|
include ActiveModel::Validations
|
8
11
|
extend ActiveModel::Callbacks
|
9
12
|
|
13
|
+
include Influxer::Scoping
|
14
|
+
include Influxer::Fanout
|
15
|
+
|
10
16
|
define_model_callbacks :write
|
11
17
|
|
12
18
|
class << self
|
13
|
-
delegate :select, :where, :group, :limit, :delete_all, to: :all
|
19
|
+
delegate :select, :where, :group, :merge, :time, :past, :since, :limit, :fill, :delete_all, to: :all
|
14
20
|
|
15
21
|
def attributes(*attrs)
|
16
22
|
attrs.each do |name|
|
@@ -32,14 +38,14 @@ module Influxer
|
|
32
38
|
if args.empty?
|
33
39
|
matches = self.to_s.match(/^(.*)Metrics$/)
|
34
40
|
if matches.nil?
|
35
|
-
@series = self.to_s.underscore
|
41
|
+
@series = self.superclass.respond_to?(:series) ? self.superclass.series : self.to_s.underscore
|
36
42
|
else
|
37
43
|
@series = matches[1].split("::").join("_").underscore
|
38
44
|
end
|
39
45
|
elsif args.first.is_a?(Proc)
|
40
46
|
@series = args.first
|
41
47
|
else
|
42
|
-
@series = args
|
48
|
+
@series = args
|
43
49
|
end
|
44
50
|
end
|
45
51
|
|
@@ -48,7 +54,11 @@ module Influxer
|
|
48
54
|
end
|
49
55
|
|
50
56
|
def all
|
51
|
-
|
57
|
+
if current_scope
|
58
|
+
current_scope.clone
|
59
|
+
else
|
60
|
+
default_scoped
|
61
|
+
end
|
52
62
|
end
|
53
63
|
end
|
54
64
|
|
@@ -83,13 +93,31 @@ module Influxer
|
|
83
93
|
end
|
84
94
|
|
85
95
|
def series
|
86
|
-
|
96
|
+
quote_series(self.class.series)
|
87
97
|
end
|
88
98
|
|
89
99
|
def client
|
90
100
|
Influxer.client
|
91
101
|
end
|
92
102
|
|
103
|
+
def quote_series(val)
|
104
|
+
case val
|
105
|
+
when Regexp
|
106
|
+
val.inspect
|
107
|
+
when Proc
|
108
|
+
quote_series(self.class.series.call(self))
|
109
|
+
when Array
|
110
|
+
if val.length > 1
|
111
|
+
"merge(#{ val.map{ |s| quote_series(s) }.join(',') })"
|
112
|
+
else
|
113
|
+
quote_series(val.first)
|
114
|
+
end
|
115
|
+
else
|
116
|
+
'"'+val.to_s.gsub(/\"/){ %q{\"} }+'"'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
93
120
|
attributes :time
|
121
|
+
|
94
122
|
end
|
95
123
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Influxer
|
2
|
+
module FanoutQuery
|
3
|
+
# Instance methods are included to Relation
|
4
|
+
def build_fanout(key, val)
|
5
|
+
@values[:has_fanout] = true
|
6
|
+
if val.is_a?(Regexp)
|
7
|
+
@values[:fanout_rxp] = true
|
8
|
+
fanout_values[key.to_s] = val.inspect[1..-2]
|
9
|
+
else
|
10
|
+
fanout_values[key.to_s] = val.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_series_name
|
15
|
+
if @values[:has_fanout] == true
|
16
|
+
fan_parts = [@instance.series[1..-2]]
|
17
|
+
@klass.fanouts.each do |name|
|
18
|
+
if fanout_values.key?(name)
|
19
|
+
fan_parts << name
|
20
|
+
fan_parts << fanout_values[name]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
if @values[:fanout_rxp] == true
|
24
|
+
"merge(/^#{ fan_parts.join( @klass.fanout_options[:delimeter] ) }$/)"
|
25
|
+
else
|
26
|
+
@instance.quote_series(fan_parts.join(@klass.fanout_options[:delimeter]))
|
27
|
+
end
|
28
|
+
else
|
29
|
+
@instance.series
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Influxer
|
2
|
+
module TimeQuery
|
3
|
+
TIME_ALIASES = {
|
4
|
+
hour: '1h',
|
5
|
+
minute: '1m',
|
6
|
+
second: '1s',
|
7
|
+
ms: '1u',
|
8
|
+
week: '1w',
|
9
|
+
day: '1d',
|
10
|
+
month: '30d'
|
11
|
+
}
|
12
|
+
|
13
|
+
# Add group value to relation. To be used instead of `group("time(...)").
|
14
|
+
# Accepts symbols and strings.
|
15
|
+
#
|
16
|
+
# You can set fill value within options.
|
17
|
+
#
|
18
|
+
# Metrics.time(:hour)
|
19
|
+
# # select * from metrics group by time(1h)
|
20
|
+
#
|
21
|
+
# Metrics.time("4d", fill: 0)
|
22
|
+
# # select * from metrics group by time(4d) fill(0)
|
23
|
+
|
24
|
+
def time(val, options={})
|
25
|
+
if val.is_a?(Symbol)
|
26
|
+
@values[:time] = TIME_ALIASES[val] || '1'+val.to_s
|
27
|
+
else
|
28
|
+
@values[:time] = val
|
29
|
+
end
|
30
|
+
|
31
|
+
unless options[:fill].nil?
|
32
|
+
fill( (options[:fill] == :null) ? 'null' : options[:fill].to_i)
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Shortcut to define time interval with regard to current time.
|
38
|
+
# Accepts symbols and numbers.
|
39
|
+
#
|
40
|
+
# Metrics.past(:hour)
|
41
|
+
# # select * from metrics where time > now() - 1h
|
42
|
+
#
|
43
|
+
# Metrics.past(:d)
|
44
|
+
# # select * from metrics where time > now() - 1d
|
45
|
+
#
|
46
|
+
# Metrics.past(2.days)
|
47
|
+
# # select * from metrics where time > now() - 172800s
|
48
|
+
|
49
|
+
def past(val)
|
50
|
+
case val
|
51
|
+
when Symbol
|
52
|
+
where("time > now() - #{ TIME_ALIASES[val] || ('1'+val.to_s) }")
|
53
|
+
when String
|
54
|
+
where("time > now() - #{val}")
|
55
|
+
else
|
56
|
+
where("time > now() - #{val.to_i}s")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Shortcut to define start point of the time interval.
|
61
|
+
# Accepts DateTime objects.
|
62
|
+
#
|
63
|
+
# Metrics.since(1.day.ago) # assume that now is 2014-12-31 12:00:00 UTC
|
64
|
+
# # select * from metrics where time > 1420027200s
|
65
|
+
#
|
66
|
+
# Metrics.since(Time.local(2014,12,31))
|
67
|
+
# # select * from metrics where time > 1419984000s
|
68
|
+
|
69
|
+
def since(val)
|
70
|
+
where("time > #{val.to_i}s")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|