nobrainer 0.9.1 → 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: a973d430bb1e593bac8d5d072cea1a815335b9c0
4
- data.tar.gz: 002f64fd3e97cb4b987c906a53bb0048b60fe120
3
+ metadata.gz: 0fa1301301888d07f8c607cfd1102555aad40a92
4
+ data.tar.gz: f05a189ae40c4e6b1f2d420dd84a4928a6f65203
5
5
  SHA512:
6
- metadata.gz: e5098627bcc261dd22e3c12f2984b98f59a38948ce671881b7656e1ed0dd268b321bc18d89304f1395e38e00976fbd9407b4cb639a79a56ad470011130ce4079
7
- data.tar.gz: 78ba3dcc4ae4efd55c0291dee1b88ae8216bcb7e9b0694fb875296392c5b82abebb1da411a6dea0677865930c7a5abd6f1a4b3351c59d0851fc3f1fad370292a
6
+ metadata.gz: 1bfa4718cb043f4847f972d46b2728c772475b8d07683a591d38d6ec039d06c09508d2df855791e935bbaa85f5d11780a367a383e9ebae0c19dce278dab7c83d
7
+ data.tar.gz: 4584d38e6646c426672cea00c0f9aa58f05a6df4778586ae2d699662d927984b1f32ba3b87ebc3b787d4ad9181b19e9e4ecf3f4cfafdbc116f85920566534ed0
@@ -1,5 +1,3 @@
1
- require 'active_support/dependencies/autoload'
2
-
3
1
  module NoBrainer::Autoload
4
2
  include ActiveSupport::Autoload
5
3
 
@@ -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
 
@@ -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
@@ -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
 
@@ -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,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
@@ -2,3 +2,4 @@ en:
2
2
  errors:
3
3
  messages:
4
4
  taken: "is already taken"
5
+ invalid_type: "should be a %{type}"
data/lib/nobrainer.rb CHANGED
@@ -2,12 +2,10 @@ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('1.9')
2
2
  raise 'Please use Ruby 1.9 or later'
3
3
  end
4
4
 
5
- # Load only what we need from ActiveSupport
6
- require 'active_support/concern'
7
- require 'active_support/lazy_load_hooks'
8
- %w(module/delegation module/attribute_accessors class/attribute object/blank
9
- object/inclusion object/duplicable hash/keys hash/reverse_merge array/extract_options)
10
- .each { |dep| require "active_support/core_ext/#{dep}" }
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}" }
11
9
 
12
10
  module NoBrainer
13
11
  require 'no_brainer/autoload'
@@ -40,14 +38,14 @@ module NoBrainer
40
38
  end
41
39
  end
42
40
 
43
- # Not using modules to extend, it's nicer to see the NoBrainer module API here.
44
41
  delegate :db_create, :db_drop, :db_list,
45
42
  :table_create, :table_drop, :table_list,
46
43
  :drop!, :purge!, :to => :connection
47
- delegate :run, :to => 'NoBrainer::QueryRunner'
48
- 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'
49
48
  delegate :with, :with_database, :to => 'NoBrainer::QueryRunner::RunOptions'
50
- delegate :configure, :logger, :to => 'NoBrainer::Config'
51
49
 
52
50
  def jruby?
53
51
  RUBY_PLATFORM == 'java'
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.1
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-06 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,10 +121,10 @@ 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
124
  - lib/no_brainer/railtie.rb
125
- - lib/no_brainer/autoload.rb
126
125
  - lib/no_brainer/config.rb
126
+ - lib/no_brainer/autoload.rb
127
+ - lib/no_brainer/document.rb
127
128
  - lib/rails/generators/nobrainer.rb
128
129
  - lib/rails/generators/nobrainer/model/model_generator.rb
129
130
  - lib/rails/generators/nobrainer/model/templates/model.rb.tt