nobrainer 0.22.0 → 0.23.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c53755e5f9ec902612c417fcf053404cf114ca0b
4
- data.tar.gz: 529fe4b627d366ba4813fd6ff750192caf0df316
3
+ metadata.gz: f77c9211fbf5a8c8fb7a695542b862cfd693ed82
4
+ data.tar.gz: 72bd41593dfbb997eb1dd72a70c00c08f75f4124
5
5
  SHA512:
6
- metadata.gz: 920d6b1c7bec9acd28b4a0e7f54a9967d068a01d31174396ca961eaf8cbfec31e3b8397ab2242ac87c73b9ca3789e80b47c8ca4b809d7a71b5fa80aeedb81f4b
7
- data.tar.gz: 92144b91eb03ac14d2a01ea5244480b215286228691e5406a9f7bcf218499853ecfb0d1313f1380f62d509d97dc2bb1dac65ae8e97c0c2c3cc2b7b4c3835e1c2
6
+ metadata.gz: fd5ca8440015b18adfc34f61bb7862e15bc445a459cf1c2310843c3c7194862f1d1abecd72c4ce14a7501a6345704fea20f49b332331b227d8ee0309494a36bb
7
+ data.tar.gz: cd4d1d5b1e8cb9ee69eed9b6a0956626e407fd3e1c1bf9e60bedd502def6c0adbe8684826afde573dde490ae6db0842cb602eb03b2025166780574bb17c474a5
@@ -21,4 +21,6 @@ module NoBrainer::Criteria::Enumerable
21
21
  return super unless [].respond_to?(name)
22
22
  to_a.__send__(name, *args, &block)
23
23
  end
24
+
25
+ delegate :as_json, :to => :each
24
26
  end
@@ -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), right_bound.try(:value)], options)
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 It would be good to set the type we want to work with, but because
32
- # the target class is eager loaded, we are not doing it.
33
- # This would have the effect of loading all the models because they
34
- # are likely to be related to each other. So we don't know the type
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
- owner_model.validates(target_name, :presence => options[:required]) if options[:required]
38
- owner_model.validates(target_name, options[:validates]) if options[:validates]
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(:pk_value))
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
- # TODO test :foreign_key
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
- # TODO test :primary_key
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.pk_value)
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) } else @_old_attributes = {}.with_indifferent_access
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
@@ -4,3 +4,4 @@ en:
4
4
  taken: "is already taken"
5
5
  invalid_type: "should be a %{type}"
6
6
  undefined: "must be defined"
7
+ invalid_foreign_key: "is an invalid foreign key: %{target_model}(%{primary_key}: %{value}) is not found"
@@ -1,16 +1,9 @@
1
- class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
2
- def call(env)
3
- start_time = Time.now
4
- @runner.call(env).tap { log_query(env, start_time) }
5
- rescue Exception => e
6
- log_query(env, start_time, e)
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,11 @@
1
+ module NoBrainer::Profiler
2
+ class << self
3
+ attr_accessor :registered_profilers
4
+
5
+ def register(profiler)
6
+ self.registered_profilers << profiler
7
+ end
8
+ end
9
+
10
+ self.registered_profilers = []
11
+ 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, :Logger, :MissingIndex,
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 Logger
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
+
@@ -42,4 +42,9 @@ class NoBrainer::Railtie < Rails::Railtie
42
42
  NoBrainer::Loader.cleanup
43
43
  end
44
44
  end
45
+
46
+ ActiveSupport.on_load(:action_controller) do
47
+ require 'no_brainer/profiler/controller_runtime'
48
+ include NoBrainer::Profiler::ControllerRuntime
49
+ end
45
50
  end
@@ -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::RQL.new.new_func(&block).as_json
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)
@@ -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.22.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-03-07 00:00:00.000000000 Z
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: 1.15.0.0
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: 1.15.0.0
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.4
215
+ rubygems_version: 2.4.6
212
216
  signing_key:
213
217
  specification_version: 4
214
218
  summary: ORM for RethinkDB