nobrainer 0.9.0 → 0.10.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: ea32475119089388850bb55326aab70d4a3dbc16
4
- data.tar.gz: dfbef7c1a239b75cb3b45d32b593026ef4d8df93
3
+ metadata.gz: 0fa1301301888d07f8c607cfd1102555aad40a92
4
+ data.tar.gz: f05a189ae40c4e6b1f2d420dd84a4928a6f65203
5
5
  SHA512:
6
- metadata.gz: d83f20ced889fba1ba1048e6ad41f4c7a6a1ae02562c8bf230580a66e64306d9a064eb179a500b61b230f026bbc93a8bf8876f4b4edbf2e21c1a2140decb743d
7
- data.tar.gz: a9a671db853f43d369550c7b1031a3a9ad17b9d5307aee6aea659e3808364c6d3d77b0ecfe9d2354a08efb99df7f44f085566d36d2f787a3e1d7469ec915ba52
6
+ metadata.gz: 1bfa4718cb043f4847f972d46b2728c772475b8d07683a591d38d6ec039d06c09508d2df855791e935bbaa85f5d11780a367a383e9ebae0c19dce278dab7c83d
7
+ data.tar.gz: 4584d38e6646c426672cea00c0f9aa58f05a6df4778586ae2d699662d927984b1f32ba3b87ebc3b787d4ad9181b19e9e4ecf3f4cfafdbc116f85920566534ed0
@@ -1,14 +1,25 @@
1
1
  module NoBrainer::Autoload
2
2
  include ActiveSupport::Autoload
3
3
 
4
- def autoload(*args)
5
- args.each { |mod| super mod }
4
+ def self.extended(base)
5
+ ActiveSupport::Autoload.extended(base)
6
6
  end
7
7
 
8
- def autoload_and_include(*mods)
9
- mods.each do |mod|
10
- autoload mod
11
- include const_get mod
12
- end
8
+ def autoload(*constants)
9
+ constants.each { |constant| super(constant) }
10
+ end
11
+
12
+ def eager_autoload(*constants)
13
+ super() { autoload(*constants) }
14
+ end
15
+
16
+ def autoload_and_include(*constants)
17
+ constants.each { |constant| autoload constant }
18
+ constants.each { |constant| include const_get constant }
19
+ end
20
+
21
+ def eager_autoload_and_include(*constants)
22
+ eager_autoload(*constants)
23
+ constants.each { |constant| include const_get constant }
13
24
  end
14
25
  end
@@ -1,3 +1,5 @@
1
+ require 'logger'
2
+
1
3
  module NoBrainer::Config
2
4
  class << self
3
5
  mattr_accessor :rethinkdb_url, :logger, :warn_on_active_record,
@@ -45,7 +45,7 @@ module NoBrainer::Criteria::Termination::Cache
45
45
  block.call(instance)
46
46
  cache << instance
47
47
  end
48
- @cache = cache.freeze
48
+ @cache = cache
49
49
  self
50
50
  end
51
51
 
@@ -33,7 +33,9 @@ class NoBrainer::Document::Association::EagerLoader
33
33
  def eager_load_association(docs, association_name, criteria=nil)
34
34
  docs = docs.compact
35
35
  return [] if docs.empty?
36
- association = docs.first.root_class.association_metadata[association_name.to_sym]
36
+ meta = docs.first.root_class.association_metadata
37
+ # TODO test the singularize thingy.
38
+ association = meta[association_name.to_sym] || meta[association_name.to_s.singularize.to_sym]
37
39
  raise "Unknown association #{association_name}" unless association
38
40
  association.eager_load(docs, criteria)
39
41
  end
@@ -1,6 +1,7 @@
1
1
  module NoBrainer::Document::Association
2
2
  extend NoBrainer::Autoload
3
3
  autoload :Core, :BelongsTo, :HasMany, :HasManyThrough, :HasOne, :HasOneThrough, :EagerLoader
4
+ METHODS = [:belongs_to, :has_many, :has_one]
4
5
 
5
6
  extend ActiveSupport::Concern
6
7
 
@@ -19,7 +20,7 @@ module NoBrainer::Document::Association
19
20
  subclass.association_metadata = self.association_metadata.dup
20
21
  end
21
22
 
22
- [:belongs_to, :has_many, :has_one].each do |association|
23
+ METHODS.each do |association|
23
24
  define_method(association) do |target, options={}|
24
25
  target = target.to_sym
25
26
 
@@ -1,5 +1,5 @@
1
1
  module NoBrainer::Document::Attributes
2
- VALID_FIELD_OPTIONS = [:index, :default]
2
+ VALID_FIELD_OPTIONS = [:index, :default, :type]
3
3
  RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
4
4
  extend ActiveSupport::Concern
5
5
 
@@ -40,39 +40,31 @@ module NoBrainer::Document::Attributes
40
40
  end
41
41
  end
42
42
 
43
+ def _assign_attributes(attrs, options={})
44
+ attrs.each { |k,v| self.write_attribute(k,v) }
45
+ end
46
+
43
47
  def assign_attributes(attrs, options={})
44
- # XXX We don't save field that are not explicitly set. The row will
45
- # therefore not contain nil for unset attributes.
46
48
  @attributes.clear if options[:pristine]
47
-
48
- if options[:from_db]
49
- # TODO Should we reject undeclared fields ?
50
- #
51
- # TODO Not using the getter/setters, the dirty tracking won't notice it,
52
- # also we should start thinking about custom serializer/deserializer.
53
- @attributes.merge! attrs
54
- else
55
- attrs.each { |k,v| self.write_attribute(k, v) }
56
- end
57
-
58
- assign_defaults if options[:pristine] || options[:from_db]
49
+ _assign_attributes(attrs, options)
50
+ assign_defaults if options[:pristine]
59
51
  self
60
52
  end
61
53
  def attributes=(*args); assign_attributes(*args); end
62
54
 
63
- # TODO test that thing
64
- def inspect
65
- attrs = self.class.fields.keys.map { |f| "#{f}: #{@attributes[f.to_s].inspect}" }
66
- "#<#{self.class} #{attrs.join(', ')}>"
55
+ def inspectable_attributes
56
+ # TODO test that thing
57
+ Hash[@attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
67
58
  end
68
59
 
69
- def to_s
70
- inspect
60
+ def inspect
61
+ "#<#{self.class} #{inspectable_attributes.map { |k,v| "#{k}: #{v.inspect}" }.join(', ')}>"
71
62
  end
72
63
 
73
64
  module ClassMethods
74
65
  def new_from_db(attrs, options={})
75
- klass_from_attrs(attrs).new(attrs, options.reverse_merge(:from_db => true)) if attrs
66
+ options = options.reverse_merge(:pristine => true, :from_db => true)
67
+ klass_from_attrs(attrs).new(attrs, options) if attrs
76
68
  end
77
69
 
78
70
  def inherited(subclass)
@@ -15,9 +15,9 @@ module NoBrainer::Document::Dirty
15
15
  @changed_attributes ||= {}
16
16
  end
17
17
 
18
- def assign_attributes(attrs, options={})
19
- clear_dirtiness if options[:pristine] || options[:from_db]
18
+ def _assign_attributes(attrs, options={})
20
19
  super
20
+ clear_dirtiness if options[:pristine]
21
21
  end
22
22
 
23
23
  def clear_dirtiness
@@ -23,7 +23,6 @@ module NoBrainer::Document::Index
23
23
  else raise "Index argument must be a lambda or a list of fields"
24
24
  end
25
25
 
26
- # FIXME Primary key may not always be :id
27
26
  if name.in?(NoBrainer::Document::Attributes::RESERVED_FIELD_NAMES)
28
27
  raise "Cannot use a reserved field name: #{name}"
29
28
  end
@@ -3,7 +3,7 @@ module NoBrainer::Document::InjectionLayer
3
3
 
4
4
  module ClassMethods
5
5
  def inject_in_layer(name, code=nil, file=nil, line=nil, &block)
6
- mod = class_eval "module NoBrainer; module #{name.to_s.camelize}; self; end; end"
6
+ mod = class_eval "module NoBrainerLayer; module #{name.to_s.camelize}; self; end; end"
7
7
  mod.module_eval(code, file, line) if code
8
8
  mod.module_exec(&block) if block
9
9
  include mod
@@ -2,8 +2,8 @@ module NoBrainer::Document::Timestamps
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- self.field :created_at
6
- self.field :updated_at
5
+ self.field :created_at, :type => Time
6
+ self.field :updated_at, :type => Time
7
7
 
8
8
  before_create { self.created_at = Time.now if self.respond_to?(:created_at=) }
9
9
  before_save { self.updated_at = Time.now if self.respond_to?(:updated_at=) }
@@ -0,0 +1,129 @@
1
+ module NoBrainer::Document::Types
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # We namespace our fake Boolean class to avoid polluting the global namespace
6
+ class_exec { class Boolean; def initialize; raise; end; end }
7
+ before_validation :add_type_errors
8
+ end
9
+
10
+ module CastingRules
11
+ extend self
12
+
13
+ def String(value)
14
+ case value
15
+ when Symbol then value.to_s
16
+ else raise InvalidType
17
+ end
18
+ end
19
+
20
+ def Integer(value)
21
+ case value
22
+ when String
23
+ value = value.strip.gsub(/^\+/, '')
24
+ value.to_i.tap { |new_value| new_value.to_s == value or raise InvalidType }
25
+ when Float
26
+ value.to_i.tap { |new_value| new_value.to_f == value or raise InvalidType }
27
+ else raise InvalidType
28
+ end
29
+ end
30
+
31
+ def Float(value)
32
+ case value
33
+ when Integer then value.to_f
34
+ when String
35
+ value = value.strip.gsub(/^\+/, '')
36
+ value = value.gsub(/0+$/, '') if value['.']
37
+ value = value.gsub(/\.$/, '')
38
+ value = "#{value}.0" unless value['.']
39
+ value.to_f.tap { |new_value| new_value.to_s == value or raise InvalidType }
40
+ else raise InvalidType
41
+ end
42
+ end
43
+
44
+ def Boolean(value)
45
+ case value
46
+ when TrueClass then true
47
+ when FalseClass then false
48
+ when String, Integer
49
+ value = value.to_s.strip.downcase
50
+ return true if value.in? %w(true yes t 1)
51
+ return false if value.in? %w(false no f 0)
52
+ raise InvalidType
53
+ else raise InvalidType
54
+ end
55
+ end
56
+
57
+ def Symbol(value)
58
+ case value
59
+ when String
60
+ value = value.strip
61
+ raise InvalidType if value.empty?
62
+ value.to_sym
63
+ else raise InvalidType
64
+ end
65
+ end
66
+ end
67
+
68
+ def self.cast(value, type, cast_method)
69
+ return value if value.nil? || type.nil? || value.is_a?(type)
70
+ cast_method.call(value)
71
+ end
72
+
73
+ def self.lookup_cast_method(type)
74
+ type = type.to_s
75
+ type = 'Boolean' if type == 'NoBrainer::Document::Types::Boolean'
76
+ CastingRules.method(type)
77
+ rescue NameError
78
+ proc { raise InvalidType }
79
+ end
80
+
81
+ class InvalidType < RuntimeError
82
+ attr_accessor :type
83
+ def initialize(type=nil)
84
+ @type = type
85
+ end
86
+
87
+ def validation_error_args
88
+ [:invalid_type, :type => type.to_s.underscore.humanize.downcase]
89
+ end
90
+ end
91
+
92
+ def add_type_errors
93
+ return unless @pending_type_errors
94
+ @pending_type_errors.each do |name, error|
95
+ errors.add(name, *error.validation_error_args)
96
+ end
97
+ end
98
+
99
+ module ClassMethods
100
+ def field(name, options={})
101
+ super
102
+ return unless options.has_key?(:type)
103
+ name = name.to_sym
104
+ type = options[:type]
105
+ cast_method = NoBrainer::Document::Types.lookup_cast_method(type)
106
+
107
+ inject_in_layer :types do
108
+ define_method("#{name}=") do |value|
109
+ begin
110
+ value = NoBrainer::Document::Types.cast(value, type, cast_method)
111
+ @pending_type_errors.try(:delete, name)
112
+ rescue NoBrainer::Document::Types::InvalidType => error
113
+ error.type ||= type
114
+ @pending_type_errors ||= {}
115
+ @pending_type_errors[name] = error
116
+ end
117
+ super(value)
118
+ end
119
+ end
120
+ end
121
+
122
+ def remove_field(name)
123
+ super
124
+ inject_in_layer :types, <<-RUBY, __FILE__, __LINE__ + 1
125
+ undef #{name}=
126
+ RUBY
127
+ end
128
+ end
129
+ end
@@ -3,7 +3,6 @@ module NoBrainer::Document::Validation
3
3
  include ActiveModel::Validations
4
4
  include ActiveModel::Validations::Callbacks
5
5
 
6
- # TODO Test that thing
7
6
  def valid?(context=nil)
8
7
  super(context || (new_record? ? :create : :update))
9
8
  end
@@ -4,8 +4,8 @@ module NoBrainer::Document
4
4
  extend ActiveSupport::Concern
5
5
  extend NoBrainer::Autoload
6
6
 
7
- autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Persistance, :Dirty,
8
- :Id, :Association, :Serialization, :Criteria, :Validation,
7
+ autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Validation, :Types,
8
+ :Persistance, :Dirty, :Id, :Association, :Serialization, :Criteria,
9
9
  :Polymorphic, :Index, :Timestamps
10
10
 
11
11
  autoload :DynamicAttributes
@@ -2,3 +2,4 @@ en:
2
2
  errors:
3
3
  messages:
4
4
  taken: "is already taken"
5
+ invalid_type: "should be a %{type}"
@@ -1,7 +1,9 @@
1
- require "nobrainer"
2
- require "rails"
1
+ require 'nobrainer'
3
2
 
4
3
  class NoBrainer::Railtie < Rails::Railtie
4
+ config.app_generators.orm :nobrainer
5
+ config.eager_load_namespaces << NoBrainer
6
+
5
7
  config.action_dispatch.rescue_responses.merge!(
6
8
  "NoBrainer::Errors::DocumentNotFound" => :not_found,
7
9
  "NoBrainer::Errors::DocumentInvalid" => :unprocessable_entity,
@@ -16,7 +18,7 @@ class NoBrainer::Railtie < Rails::Railtie
16
18
 
17
19
  if defined?(ActiveRecord) && NoBrainer::Config.warn_on_active_record
18
20
  STDERR.puts "[NoBrainer] WARNING: ActiveRecord is loaded which is probably not what you want."
19
- STDERR.puts "[NoBrainer] Follow the instructions on http://todo/ to learn how to remove ActiveRecord."
21
+ STDERR.puts "[NoBrainer] Follow the instructions on http://nobrainer.io/docs/configuration/#removing_activerecord"
20
22
  STDERR.puts "[NoBrainer] Configure NoBrainer with 'config.warn_on_active_record = false' to disable with warning."
21
23
  end
22
24
 
@@ -30,6 +32,4 @@ class NoBrainer::Railtie < Rails::Railtie
30
32
  NoBrainer::Loader.cleanup
31
33
  end
32
34
  end
33
-
34
- #config.eager_load_namespaces << NoBrainer
35
35
  end
data/lib/nobrainer.rb CHANGED
@@ -1,15 +1,19 @@
1
- require 'active_support/core_ext'
2
-
3
1
  if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('1.9')
4
2
  raise 'Please use Ruby 1.9 or later'
5
3
  end
6
4
 
5
+ require 'active_support'
6
+ %w(module/delegation module/attribute_accessors class/attribute object/blank object/inclusion
7
+ object/duplicable object/try hash/keys hash/reverse_merge array/extract_options)
8
+ .each { |dep| require "active_support/core_ext/#{dep}" }
9
+
7
10
  module NoBrainer
8
11
  require 'no_brainer/autoload'
9
12
  extend NoBrainer::Autoload
10
13
 
11
- autoload :Config, :Document, :Connection, :Database, :Error, :QueryRunner,
12
- :Criteria, :DecoratedSymbol, :IndexManager, :Loader, :Logging, :Util, :Fork
14
+ # We eager load things that could be loaded for the first time during the web request
15
+ autoload :Document, :IndexManager, :Loader, :Fork, :DecoratedSymbol
16
+ eager_autoload :Config, :Connection, :Error, :QueryRunner, :Criteria, :Util
13
17
 
14
18
  class << self
15
19
  # Note: we always access the connection explicitly, so that in the future,
@@ -34,14 +38,14 @@ module NoBrainer
34
38
  end
35
39
  end
36
40
 
37
- # Not using modules to extend, it's nicer to see the NoBrainer module API here.
38
41
  delegate :db_create, :db_drop, :db_list,
39
42
  :table_create, :table_drop, :table_list,
40
43
  :drop!, :purge!, :to => :connection
41
- delegate :run, :to => 'NoBrainer::QueryRunner'
42
- delegate :update_indexes, :to => 'NoBrainer::IndexManager'
44
+
45
+ delegate :configure, :logger, :to => 'NoBrainer::Config'
46
+ delegate :run, :to => 'NoBrainer::QueryRunner'
47
+ delegate :update_indexes, :to => 'NoBrainer::IndexManager'
43
48
  delegate :with, :with_database, :to => 'NoBrainer::QueryRunner::RunOptions'
44
- delegate :configure, :logger, :to => 'NoBrainer::Config'
45
49
 
46
50
  def jruby?
47
51
  RUBY_PLATFORM == 'java'
@@ -0,0 +1,17 @@
1
+ require "rails/generators/nobrainer"
2
+
3
+ module NoBrainer::Generators
4
+ class ModelGenerator < Base
5
+ argument :attributes, :type => :array, default: [], banner: "field ... field"
6
+
7
+ check_class_collision
8
+
9
+ class_option :parent, :type => :string, :desc => "The parent class for the generated model"
10
+
11
+ def create_model_file
12
+ template "model.rb.tt", File.join("app/models", class_path, "#{file_name}.rb")
13
+ end
14
+
15
+ hook_for :test_framework
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %><%= " < #{options[:parent].classify}" if options[:parent] %>
3
+ <% unless options[:parent] -%>
4
+ include NoBrainer::Document
5
+ <% end -%>
6
+ <% attributes.reject(&:reference?).each do |attribute| -%>
7
+ field :<%= attribute.name %>
8
+ <% end -%>
9
+ <% attributes.select(&:reference?).each do |attribute| -%>
10
+ belongs_to :<%= attribute.name %>
11
+ <% end -%>
12
+ end
13
+ <% end -%>
@@ -0,0 +1,18 @@
1
+ require 'rails/generators/named_base'
2
+ require 'nobrainer'
3
+
4
+ module NoBrainer::Generators
5
+ class Base < Rails::Generators::NamedBase
6
+ def self.base_name
7
+ 'nobrainer'
8
+ end
9
+
10
+ def self.base_root
11
+ File.dirname(__FILE__)
12
+ end
13
+
14
+ def self.namespace
15
+ super.gsub(/no_brainer/,'nobrainer')
16
+ end
17
+ end
18
+ end
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.9.0
4
+ version: 0.10.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: 2014-01-05 00:00:00.000000000 Z
11
+ date: 2014-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rethinkdb
@@ -66,27 +66,28 @@ extensions: []
66
66
  extra_rdoc_files: []
67
67
  files:
68
68
  - lib/no_brainer/document/id.rb
69
- - lib/no_brainer/document/injection_layer.rb
70
- - lib/no_brainer/document/timestamps.rb
71
69
  - lib/no_brainer/document/association/belongs_to.rb
72
- - lib/no_brainer/document/association/core.rb
73
- - lib/no_brainer/document/association/eager_loader.rb
74
- - lib/no_brainer/document/association/has_many_through.rb
75
70
  - lib/no_brainer/document/association/has_many.rb
76
71
  - lib/no_brainer/document/association/has_one.rb
77
72
  - lib/no_brainer/document/association/has_one_through.rb
78
- - lib/no_brainer/document/dirty.rb
73
+ - lib/no_brainer/document/association/has_many_through.rb
74
+ - lib/no_brainer/document/association/eager_loader.rb
75
+ - lib/no_brainer/document/association/core.rb
79
76
  - lib/no_brainer/document/dynamic_attributes.rb
80
- - lib/no_brainer/document/persistance.rb
81
- - lib/no_brainer/document/validation.rb
82
- - lib/no_brainer/document/serialization.rb
83
77
  - lib/no_brainer/document/criteria.rb
84
- - lib/no_brainer/document/index.rb
85
78
  - lib/no_brainer/document/polymorphic.rb
86
79
  - lib/no_brainer/document/store_in.rb
87
- - lib/no_brainer/document/attributes.rb
88
80
  - lib/no_brainer/document/core.rb
81
+ - lib/no_brainer/document/injection_layer.rb
82
+ - lib/no_brainer/document/index.rb
83
+ - lib/no_brainer/document/serialization.rb
89
84
  - lib/no_brainer/document/association.rb
85
+ - lib/no_brainer/document/validation.rb
86
+ - lib/no_brainer/document/timestamps.rb
87
+ - lib/no_brainer/document/dirty.rb
88
+ - lib/no_brainer/document/persistance.rb
89
+ - lib/no_brainer/document/attributes.rb
90
+ - lib/no_brainer/document/types.rb
90
91
  - lib/no_brainer/query_runner/connection.rb
91
92
  - lib/no_brainer/query_runner/database_on_demand.rb
92
93
  - lib/no_brainer/query_runner/logger.rb
@@ -120,11 +121,13 @@ files:
120
121
  - lib/no_brainer/connection.rb
121
122
  - lib/no_brainer/query_runner.rb
122
123
  - lib/no_brainer/error.rb
123
- - lib/no_brainer/document.rb
124
- - lib/no_brainer/config.rb
125
124
  - lib/no_brainer/railtie.rb
125
+ - lib/no_brainer/config.rb
126
126
  - lib/no_brainer/autoload.rb
127
- - lib/no_brainer/version.rb
127
+ - lib/no_brainer/document.rb
128
+ - lib/rails/generators/nobrainer.rb
129
+ - lib/rails/generators/nobrainer/model/model_generator.rb
130
+ - lib/rails/generators/nobrainer/model/templates/model.rb.tt
128
131
  - lib/nobrainer.rb
129
132
  - README.md
130
133
  - LICENSE.md
@@ -1,3 +0,0 @@
1
- module NoBrainer
2
- VERSION = '0.9.0'
3
- end