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 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