nobrainer 0.22.0 → 0.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/no_brainer/criteria/enumerable.rb +2 -0
- data/lib/no_brainer/criteria/where.rb +2 -1
- data/lib/no_brainer/document/association/belongs_to.rb +23 -11
- data/lib/no_brainer/document/association/has_many.rb +3 -6
- data/lib/no_brainer/document/dirty.rb +3 -1
- data/lib/no_brainer/locale/en.yml +1 -0
- data/lib/no_brainer/{query_runner/logger.rb → logger.rb} +6 -21
- data/lib/no_brainer/profiler.rb +11 -0
- data/lib/no_brainer/profiler/controller_runtime.rb +76 -0
- data/lib/no_brainer/profiler/logger.rb +46 -0
- data/lib/no_brainer/query_runner.rb +3 -3
- data/lib/no_brainer/query_runner/profiler.rb +42 -0
- data/lib/no_brainer/railtie.rb +5 -0
- data/lib/no_brainer/rql.rb +3 -1
- data/lib/nobrainer.rb +2 -1
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f77c9211fbf5a8c8fb7a695542b862cfd693ed82
|
4
|
+
data.tar.gz: 72bd41593dfbb997eb1dd72a70c00c08f75f4124
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd5ca8440015b18adfc34f61bb7862e15bc445a459cf1c2310843c3c7194862f1d1abecd72c4ce14a7501a6345704fea20f49b332331b227d8ee0309494a36bb
|
7
|
+
data.tar.gz: cd4d1d5b1e8cb9ee69eed9b6a0956626e407fd3e1c1bf9e60bedd502def6c0adbe8684826afde573dde490ae6db0842cb602eb03b2025166780574bb17c474a5
|
@@ -401,7 +401,8 @@ module NoBrainer::Criteria::Where
|
|
401
401
|
options[:right_bound] = {:lt => :open, :le => :closed}[right_bound.op] if right_bound
|
402
402
|
|
403
403
|
return IndexStrategy.new(self, ast, [left_bound, right_bound].compact, index, :between,
|
404
|
-
[left_bound.try(:value)
|
404
|
+
[left_bound ? left_bound.try(:value) : RethinkDB::RQL.new.minval,
|
405
|
+
right_bound ? right_bound.try(:value) : RethinkDB::RQL.new.maxval], options)
|
405
406
|
end
|
406
407
|
return nil
|
407
408
|
end
|
@@ -7,17 +7,14 @@ class NoBrainer::Document::Association::BelongsTo
|
|
7
7
|
extend NoBrainer::Document::Association::EagerLoader::Generic
|
8
8
|
|
9
9
|
def foreign_key
|
10
|
-
# TODO test :foreign_key
|
11
10
|
options[:foreign_key].try(:to_sym) || :"#{target_name}_#{primary_key}"
|
12
11
|
end
|
13
12
|
|
14
13
|
def primary_key
|
15
|
-
# TODO test :primary_key
|
16
14
|
options[:primary_key].try(:to_sym) || target_model.pk_name
|
17
15
|
end
|
18
16
|
|
19
17
|
def target_model
|
20
|
-
# TODO test :class_name
|
21
18
|
(options[:class_name] || target_name.to_s.camelize).constantize
|
22
19
|
end
|
23
20
|
|
@@ -27,15 +24,30 @@ class NoBrainer::Document::Association::BelongsTo
|
|
27
24
|
|
28
25
|
def hook
|
29
26
|
super
|
27
|
+
# XXX We are loading the target_model unless the primary_key is not
|
28
|
+
# specified. This may eager load a part of the application.
|
29
|
+
# Oh well.
|
30
30
|
|
31
|
-
# TODO
|
32
|
-
#
|
33
|
-
|
34
|
-
#
|
35
|
-
# of the primary key of the target.
|
31
|
+
# TODO if the primary key of the target_model changes, we need to revisit
|
32
|
+
# our default foreign_key/primary_key value
|
33
|
+
|
34
|
+
# TODO set the type of the foreign key to be the same as the target's primary key
|
36
35
|
owner_model.field(foreign_key, :store_as => options[:foreign_key_store_as], :index => options[:index])
|
37
|
-
|
38
|
-
|
36
|
+
|
37
|
+
unless options[:validates] == false
|
38
|
+
owner_model.validates(target_name, options[:validates]) if options[:validates]
|
39
|
+
|
40
|
+
if options[:required]
|
41
|
+
owner_model.validates(target_name, :presence => options[:required])
|
42
|
+
else
|
43
|
+
# Always validate the validity of the foreign_key if not nil.
|
44
|
+
owner_model.validates_each(foreign_key) do |doc, attr, value|
|
45
|
+
if !value.nil? && doc.read_attribute_for_validation(target_name).nil?
|
46
|
+
doc.errors.add(attr, :invalid_foreign_key, :target_model => target_model, :primary_key => primary_key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
39
51
|
|
40
52
|
delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
|
41
53
|
delegate("#{target_name}_changed?", "#{foreign_key}_changed?", :to => :self)
|
@@ -64,7 +76,7 @@ class NoBrainer::Document::Association::BelongsTo
|
|
64
76
|
|
65
77
|
def write(target)
|
66
78
|
assert_target_type(target)
|
67
|
-
owner.write_attribute(foreign_key, target.try(
|
79
|
+
owner.write_attribute(foreign_key, target.try(primary_key))
|
68
80
|
preload(target)
|
69
81
|
end
|
70
82
|
|
@@ -7,17 +7,14 @@ class NoBrainer::Document::Association::HasMany
|
|
7
7
|
extend NoBrainer::Document::Association::EagerLoader::Generic
|
8
8
|
|
9
9
|
def foreign_key
|
10
|
-
|
11
|
-
options[:foreign_key].try(:to_sym) || :"#{owner_model.name.underscore}_#{owner_model.pk_name}"
|
10
|
+
options[:foreign_key].try(:to_sym) || :"#{owner_model.name.underscore}_#{primary_key}"
|
12
11
|
end
|
13
12
|
|
14
13
|
def primary_key
|
15
|
-
|
16
|
-
options[:primary_key].try(:to_sym) || target_model.pk_name
|
14
|
+
options[:primary_key].try(:to_sym) || owner_model.pk_name
|
17
15
|
end
|
18
16
|
|
19
17
|
def target_model
|
20
|
-
# TODO test :class_name
|
21
18
|
(options[:class_name] || target_name.to_s.singularize.camelize).constantize
|
22
19
|
end
|
23
20
|
|
@@ -60,7 +57,7 @@ class NoBrainer::Document::Association::HasMany
|
|
60
57
|
end
|
61
58
|
|
62
59
|
def target_criteria
|
63
|
-
@target_criteria ||= base_criteria.where(foreign_key => owner.
|
60
|
+
@target_criteria ||= base_criteria.where(foreign_key => owner.__send__(primary_key))
|
64
61
|
.after_find(set_inverse_proc)
|
65
62
|
end
|
66
63
|
|
@@ -17,7 +17,9 @@ module NoBrainer::Document::Dirty
|
|
17
17
|
def clear_dirtiness(options={})
|
18
18
|
if options[:keep_ivars] && options[:missing_attributes].try(:[], :pluck)
|
19
19
|
attrs = options[:missing_attributes][:pluck].keys
|
20
|
-
@_old_attributes = @_old_attributes.reject { |k,v| attrs.include?(k) }
|
20
|
+
@_old_attributes = @_old_attributes.reject { |k,v| attrs.include?(k) }
|
21
|
+
else
|
22
|
+
@_old_attributes = {}.with_indifferent_access
|
21
23
|
end
|
22
24
|
|
23
25
|
@_old_attributes_keys = @_attributes.keys # to track undefined -> nil changes
|
@@ -1,16 +1,9 @@
|
|
1
|
-
class NoBrainer::
|
2
|
-
def
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
raise e
|
8
|
-
end
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def log_query(env, start_time, exception=nil)
|
13
|
-
return if handle_on_demand_exception?(env, exception)
|
1
|
+
class NoBrainer::Logger
|
2
|
+
def on_query(env)
|
3
|
+
# env[:end_time] = Time.now
|
4
|
+
# env[:duration] = env[:end_time] - env[:start_time]
|
5
|
+
# env[:exception] = exception
|
6
|
+
# env[:query_type] = NoBrainer::RQL.type_of(env[:query])
|
14
7
|
|
15
8
|
not_indexed = env[:criteria] && env[:criteria].where_present? &&
|
16
9
|
!env[:criteria].where_indexed? &&
|
@@ -20,8 +13,6 @@ class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
|
|
20
13
|
not_indexed ? Logger::INFO : Logger::DEBUG
|
21
14
|
return if NoBrainer.logger.nil? || NoBrainer.logger.level > level
|
22
15
|
|
23
|
-
duration = Time.now - start_time
|
24
|
-
|
25
16
|
msg_duration = (duration * 1000.0).round(1).to_s
|
26
17
|
msg_duration = " " * [0, 6 - msg_duration.size].max + msg_duration
|
27
18
|
msg_duration = "[#{msg_duration}ms] "
|
@@ -52,10 +43,4 @@ class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
|
|
52
43
|
msg = [msg_duration, msg_db, msg_query, msg_exception, msg_last].join
|
53
44
|
NoBrainer.logger.add(level, msg)
|
54
45
|
end
|
55
|
-
|
56
|
-
def handle_on_demand_exception?(env, e)
|
57
|
-
# pretty gross I must say.
|
58
|
-
e && (NoBrainer::QueryRunner::DatabaseOnDemand.new(nil).handle_database_on_demand_exception?(env, e) ||
|
59
|
-
NoBrainer::QueryRunner::TableOnDemand.new(nil).handle_table_on_demand_exception?(env, e))
|
60
|
-
end
|
61
46
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Rails specific. TODO Test
|
2
|
+
module NoBrainer::Profiler::ControllerRuntime
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class Profiler
|
6
|
+
attr_accessor :write_duration, :read_duration, :other_duration
|
7
|
+
def initialize
|
8
|
+
@write_duration = @read_duration = @other_duration = 0.0
|
9
|
+
end
|
10
|
+
|
11
|
+
def total_duration
|
12
|
+
read_duration + write_duration + other_duration
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_query(env)
|
16
|
+
case env[:query_type]
|
17
|
+
when :write then @write_duration += env[:duration]
|
18
|
+
when :read then @read_duration += env[:duration]
|
19
|
+
else @other_duration += env[:duration]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.spawn_controller_profiler
|
24
|
+
Thread.current[:nobrainer_controller_profiler] = new
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.cleanup_controller_profiler
|
28
|
+
Thread.current[:nobrainer_controller_profiler] = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.current
|
32
|
+
Thread.current[:nobrainer_controller_profiler]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.on_query(env)
|
36
|
+
current.try(:add_query, env)
|
37
|
+
end
|
38
|
+
|
39
|
+
NoBrainer::Profiler.register(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def process_action(action, *args)
|
43
|
+
Profiler.spawn_controller_profiler
|
44
|
+
super
|
45
|
+
ensure
|
46
|
+
Profiler.cleanup_controller_profiler
|
47
|
+
end
|
48
|
+
|
49
|
+
def cleanup_view_runtime
|
50
|
+
time_spent_in_db_before_views = Profiler.current.total_duration
|
51
|
+
runtime = super
|
52
|
+
time_spent_in_db_after_views = Profiler.current.total_duration
|
53
|
+
|
54
|
+
time_spent_in_db_during_views = (time_spent_in_db_after_views - time_spent_in_db_before_views) * 1000
|
55
|
+
runtime - time_spent_in_db_during_views
|
56
|
+
end
|
57
|
+
|
58
|
+
def append_info_to_payload(payload)
|
59
|
+
super
|
60
|
+
payload[:nobrainer_profiler] = Profiler.current
|
61
|
+
end
|
62
|
+
|
63
|
+
module ClassMethods # :nodoc:
|
64
|
+
def log_process_action(payload)
|
65
|
+
messages, profiler = super, payload[:nobrainer_profiler]
|
66
|
+
if profiler && !profiler.total_duration.zero?
|
67
|
+
msg = []
|
68
|
+
msg << "%.1fms (write)" % (profiler.write_duration * 1000) unless profiler.write_duration.zero?
|
69
|
+
msg << "%.1fms (read)" % (profiler.read_duration * 1000) unless profiler.read_duration.zero?
|
70
|
+
msg << "%.1fms (other)" % (profiler.other_duration * 1000) unless profiler.other_duration.zero?
|
71
|
+
messages << "NoBrainer: #{msg.join(", ")}"
|
72
|
+
end
|
73
|
+
messages
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class NoBrainer::Profiler::Logger
|
2
|
+
def on_query(env)
|
3
|
+
not_indexed = env[:criteria] && env[:criteria].where_present? &&
|
4
|
+
!env[:criteria].where_indexed? &&
|
5
|
+
!env[:criteria].model.try(:perf_warnings_disabled)
|
6
|
+
|
7
|
+
level = env[:exception] ? Logger::ERROR :
|
8
|
+
not_indexed ? Logger::INFO : Logger::DEBUG
|
9
|
+
return if NoBrainer.logger.nil? || NoBrainer.logger.level > level
|
10
|
+
|
11
|
+
msg_duration = (env[:duration] * 1000.0).round(1).to_s
|
12
|
+
msg_duration = " " * [0, 6 - msg_duration.size].max + msg_duration
|
13
|
+
msg_duration = "[#{msg_duration}ms] "
|
14
|
+
|
15
|
+
env[:query_type] = NoBrainer::RQL.type_of(env[:query])
|
16
|
+
env[:custom_db_name] = env[:db_name] if env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
|
17
|
+
|
18
|
+
msg_db = "[#{env[:custom_db_name]}] " if env[:custom_db_name]
|
19
|
+
msg_query = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
|
20
|
+
|
21
|
+
msg_exception = "#{env[:exception].class} #{env[:exception].message.split("\n").first}" if env[:exception]
|
22
|
+
msg_exception ||= "perf: filtering without using an index" if not_indexed
|
23
|
+
|
24
|
+
msg_last = nil
|
25
|
+
|
26
|
+
if NoBrainer::Config.colorize_logger
|
27
|
+
query_color = case env[:query_type]
|
28
|
+
when :write then "\e[1;31m" # red
|
29
|
+
when :read then "\e[1;32m" # green
|
30
|
+
when :management then "\e[1;33m" # yellow
|
31
|
+
end
|
32
|
+
msg_duration = [query_color, msg_duration].join
|
33
|
+
msg_db = ["\e[0;34m", msg_db, query_color].join if msg_db
|
34
|
+
if msg_exception
|
35
|
+
exception_color = "\e[0;31m" if level == Logger::ERROR
|
36
|
+
msg_exception = ["\e[0;39m", " -- ", exception_color, msg_exception].compact.join
|
37
|
+
end
|
38
|
+
msg_last = "\e[0m"
|
39
|
+
end
|
40
|
+
|
41
|
+
msg = [msg_duration, msg_db, msg_query, msg_exception, msg_last].join
|
42
|
+
NoBrainer.logger.add(level, msg)
|
43
|
+
end
|
44
|
+
|
45
|
+
NoBrainer::Profiler.register(self.new)
|
46
|
+
end
|
@@ -11,7 +11,7 @@ module NoBrainer::QueryRunner
|
|
11
11
|
end
|
12
12
|
|
13
13
|
autoload :Driver, :DatabaseOnDemand, :TableOnDemand, :WriteError,
|
14
|
-
:Reconnect, :Selection, :RunOptions, :
|
14
|
+
:Reconnect, :Selection, :RunOptions, :Profiler, :MissingIndex,
|
15
15
|
:ConnectionLock
|
16
16
|
|
17
17
|
class << self
|
@@ -28,11 +28,11 @@ module NoBrainer::QueryRunner
|
|
28
28
|
# thread-safe, since require() is ran with a mutex.
|
29
29
|
self.stack = ::Middleware::Builder.new do
|
30
30
|
use RunOptions
|
31
|
-
use WriteError
|
32
31
|
use MissingIndex
|
33
32
|
use DatabaseOnDemand
|
34
33
|
use TableOnDemand
|
35
|
-
use
|
34
|
+
use Profiler
|
35
|
+
use WriteError
|
36
36
|
use ConnectionLock
|
37
37
|
use Reconnect
|
38
38
|
use Driver
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class NoBrainer::QueryRunner::Profiler < NoBrainer::QueryRunner::Middleware
|
2
|
+
def call(env)
|
3
|
+
profiler_start(env)
|
4
|
+
@runner.call(env).tap { profiler_end(env) }
|
5
|
+
rescue Exception => e
|
6
|
+
profiler_end(env, e)
|
7
|
+
raise e
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def profiler_start(env)
|
13
|
+
env[:start_time] = Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
def profiler_end(env, exception=nil)
|
17
|
+
return if handle_on_demand_exception?(env, exception)
|
18
|
+
|
19
|
+
env[:end_time] = Time.now
|
20
|
+
env[:duration] = env[:end_time] - env[:start_time]
|
21
|
+
env[:exception] = exception
|
22
|
+
|
23
|
+
env[:model] = env[:criteria] && env[:criteria].model
|
24
|
+
env[:query_type] = NoBrainer::RQL.type_of(env[:query])
|
25
|
+
env[:custom_db_name] = env[:db_name] if env[:db_name] && env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
|
26
|
+
|
27
|
+
NoBrainer::Profiler.registered_profilers.each do |profiler|
|
28
|
+
begin
|
29
|
+
profiler.on_query(env)
|
30
|
+
rescue Exception => e
|
31
|
+
STDERR.puts "[NoBrainer] Profiling error: #{e.class} #{e.message}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle_on_demand_exception?(env, e)
|
37
|
+
# pretty gross I must say.
|
38
|
+
e && (NoBrainer::QueryRunner::DatabaseOnDemand.new(nil).handle_database_on_demand_exception?(env, e) ||
|
39
|
+
NoBrainer::QueryRunner::TableOnDemand.new(nil).handle_table_on_demand_exception?(env, e))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
data/lib/no_brainer/railtie.rb
CHANGED
data/lib/no_brainer/rql.rb
CHANGED
@@ -8,7 +8,9 @@ module NoBrainer::RQL
|
|
8
8
|
|
9
9
|
def rql_proc_as_json(block)
|
10
10
|
reset_lambda_var_counter
|
11
|
-
RethinkDB::
|
11
|
+
RethinkDB::Shim.load_json(
|
12
|
+
RethinkDB::Shim.dump_json(
|
13
|
+
RethinkDB::RQL.new.new_func(&block)))
|
12
14
|
end
|
13
15
|
|
14
16
|
def is_write_query?(rql_query)
|
data/lib/nobrainer.rb
CHANGED
@@ -13,7 +13,7 @@ module NoBrainer
|
|
13
13
|
|
14
14
|
# We eager load things that could be loaded when handling the first web request.
|
15
15
|
# Code that is loaded through the DSL of NoBrainer should not be eager loaded.
|
16
|
-
autoload :Document, :IndexManager, :Loader, :Fork, :Geo, :Lock
|
16
|
+
autoload :Document, :IndexManager, :Loader, :Fork, :Geo, :Lock, :Profiler
|
17
17
|
eager_autoload :Config, :Connection, :ConnectionManager, :Error,
|
18
18
|
:QueryRunner, :Criteria, :RQL
|
19
19
|
|
@@ -41,4 +41,5 @@ ActiveSupport.on_load(:i18n) do
|
|
41
41
|
I18n.load_path << File.dirname(__FILE__) + '/no_brainer/locale/en.yml'
|
42
42
|
end
|
43
43
|
|
44
|
+
require 'no_brainer/profiler/logger'
|
44
45
|
require 'no_brainer/railtie' if defined?(Rails)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nobrainer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.23.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolas Viennot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rethinkdb
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.0.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.0.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -171,12 +171,16 @@ files:
|
|
171
171
|
- lib/no_brainer/loader.rb
|
172
172
|
- lib/no_brainer/locale/en.yml
|
173
173
|
- lib/no_brainer/lock.rb
|
174
|
+
- lib/no_brainer/logger.rb
|
175
|
+
- lib/no_brainer/profiler.rb
|
176
|
+
- lib/no_brainer/profiler/controller_runtime.rb
|
177
|
+
- lib/no_brainer/profiler/logger.rb
|
174
178
|
- lib/no_brainer/query_runner.rb
|
175
179
|
- lib/no_brainer/query_runner/connection_lock.rb
|
176
180
|
- lib/no_brainer/query_runner/database_on_demand.rb
|
177
181
|
- lib/no_brainer/query_runner/driver.rb
|
178
|
-
- lib/no_brainer/query_runner/logger.rb
|
179
182
|
- lib/no_brainer/query_runner/missing_index.rb
|
183
|
+
- lib/no_brainer/query_runner/profiler.rb
|
180
184
|
- lib/no_brainer/query_runner/reconnect.rb
|
181
185
|
- lib/no_brainer/query_runner/run_options.rb
|
182
186
|
- lib/no_brainer/query_runner/table_on_demand.rb
|
@@ -208,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
208
212
|
version: '0'
|
209
213
|
requirements: []
|
210
214
|
rubyforge_project:
|
211
|
-
rubygems_version: 2.4.
|
215
|
+
rubygems_version: 2.4.6
|
212
216
|
signing_key:
|
213
217
|
specification_version: 4
|
214
218
|
summary: ORM for RethinkDB
|