nobrainer 0.14.0 → 0.15.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 +4 -4
- data/lib/no_brainer/config.rb +17 -1
- data/lib/no_brainer/criteria/count.rb +1 -1
- data/lib/no_brainer/criteria/first.rb +6 -0
- data/lib/no_brainer/criteria/where.rb +11 -5
- data/lib/no_brainer/decorated_symbol.rb +2 -1
- data/lib/no_brainer/document/attributes.rb +5 -3
- data/lib/no_brainer/document/criteria.rb +2 -2
- data/lib/no_brainer/document/persistance.rb +2 -2
- data/lib/no_brainer/document/types.rb +61 -129
- data/lib/no_brainer/document/types/boolean.rb +26 -0
- data/lib/no_brainer/document/types/date.rb +26 -0
- data/lib/no_brainer/document/types/float.rb +20 -0
- data/lib/no_brainer/document/types/integer.rb +18 -0
- data/lib/no_brainer/document/types/string.rb +14 -0
- data/lib/no_brainer/document/types/symbol.rb +21 -0
- data/lib/no_brainer/document/types/time.rb +41 -0
- data/lib/no_brainer/document/validation.rb +1 -0
- data/lib/no_brainer/error.rb +1 -1
- data/lib/nobrainer.rb +4 -0
- data/lib/rails/generators/nobrainer/model/model_generator.rb +2 -1
- data/lib/rails/generators/nobrainer/model/templates/model.rb.tt +7 -2
- metadata +9 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: e97dd4f563340a2c8551ffefb21f22fc628954b2
         | 
| 4 | 
            +
              data.tar.gz: 1fca054e8f966be9e7518f8245c39987d40068c7
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 1cbfebfa6ac2922e733d157522eda4e6c9edf9de9e51601f6892ac2ac2814593ed20807c68441a2ff7391398cf37896eeb739eaf2cdafa0da8e9fe91bb02fce2
         | 
| 7 | 
            +
              data.tar.gz: ace1719ae85c7593145e81818fb1b93f715ad65d4634130c664b0fdd134c637aba0395066163b9fd3aee91f7ec0b27a53338e857ec2c089a4b425e02f2eba00b
         | 
    
        data/lib/no_brainer/config.rb
    CHANGED
    
    | @@ -4,7 +4,8 @@ module NoBrainer::Config | |
| 4 4 | 
             
              class << self
         | 
| 5 5 | 
             
                mattr_accessor :rethinkdb_url, :logger, :warn_on_active_record,
         | 
| 6 6 | 
             
                               :auto_create_databases, :auto_create_tables,
         | 
| 7 | 
            -
                               :max_reconnection_tries, :durability, | 
| 7 | 
            +
                               :max_reconnection_tries, :durability,
         | 
| 8 | 
            +
                               :user_timezone, :db_timezone, :colorize_logger,
         | 
| 8 9 | 
             
                               :distributed_lock_class
         | 
| 9 10 |  | 
| 10 11 | 
             
                def apply_defaults
         | 
| @@ -15,6 +16,8 @@ module NoBrainer::Config | |
| 15 16 | 
             
                  self.auto_create_tables      = true
         | 
| 16 17 | 
             
                  self.max_reconnection_tries  = 10
         | 
| 17 18 | 
             
                  self.durability              = default_durability
         | 
| 19 | 
            +
                  self.user_timezone           = :local
         | 
| 20 | 
            +
                  self.db_timezone             = :utc
         | 
| 18 21 | 
             
                  self.colorize_logger         = true
         | 
| 19 22 | 
             
                  self.distributed_lock_class  = nil
         | 
| 20 23 | 
             
                end
         | 
| @@ -27,6 +30,7 @@ module NoBrainer::Config | |
| 27 30 | 
             
                def configure(&block)
         | 
| 28 31 | 
             
                  apply_defaults unless configured?
         | 
| 29 32 | 
             
                  block.call(self) if block
         | 
| 33 | 
            +
                  assert_valid_options!
         | 
| 30 34 | 
             
                  @configured = true
         | 
| 31 35 |  | 
| 32 36 | 
             
                  NoBrainer.disconnect_if_url_changed
         | 
| @@ -36,6 +40,18 @@ module NoBrainer::Config | |
| 36 40 | 
             
                  !!@configured
         | 
| 37 41 | 
             
                end
         | 
| 38 42 |  | 
| 43 | 
            +
                def assert_valid_options!
         | 
| 44 | 
            +
                  assert_array_in :durability,    [:hard, :soft]
         | 
| 45 | 
            +
                  assert_array_in :user_timezone, [:unchanged, :utc, :local]
         | 
| 46 | 
            +
                  assert_array_in :db_timezone,   [:unchanged, :utc, :local]
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def assert_array_in(name, values)
         | 
| 50 | 
            +
                  unless __send__(name).in?(values)
         | 
| 51 | 
            +
                    raise ArgumentError.new("Unknown configuration for #{name}: #{__send__(name)}. Valid values are: #{values.inspect}")
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 39 55 | 
             
                def default_rethinkdb_url
         | 
| 40 56 | 
             
                  db = ENV['RETHINKDB_DB'] || ENV['RDB_DB']
         | 
| 41 57 | 
             
                  db ||= "#{Rails.application.class.parent_name.underscore}_#{Rails.env}" rescue nil
         | 
| @@ -17,6 +17,12 @@ module NoBrainer::Criteria::First | |
| 17 17 | 
             
                last.tap { |doc| raise NoBrainer::Error::DocumentNotFound unless doc }
         | 
| 18 18 | 
             
              end
         | 
| 19 19 |  | 
| 20 | 
            +
              def sample(n=nil)
         | 
| 21 | 
            +
                result = NoBrainer.run { self.without_ordering.to_rql.sample(n.nil? ? 1 : n) }
         | 
| 22 | 
            +
                result = result.map(&method(:instantiate_doc))
         | 
| 23 | 
            +
                n.nil? ? result.first : result
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 20 26 | 
             
              private
         | 
| 21 27 |  | 
| 22 28 | 
             
              def get_one(criteria)
         | 
| @@ -54,24 +54,26 @@ module NoBrainer::Criteria::Where | |
| 54 54 | 
             
                end
         | 
| 55 55 | 
             
              end
         | 
| 56 56 |  | 
| 57 | 
            -
              class BinaryOperator < Struct.new(:key, :op, :value, :criteria)
         | 
| 57 | 
            +
              class BinaryOperator < Struct.new(:key, :op, :value, :criteria, :casted_values)
         | 
| 58 58 | 
             
                def simplify
         | 
| 59 59 | 
             
                  key = cast_key(self.key)
         | 
| 60 60 | 
             
                  case op
         | 
| 61 61 | 
             
                  when :in then
         | 
| 62 62 | 
             
                    case value
         | 
| 63 | 
            -
                    when Range then BinaryOperator.new(key, :between, (cast_value(value.min)..cast_value(value.max)), criteria)
         | 
| 64 | 
            -
                    when Array then BinaryOperator.new(key, :in, value.map(&method(:cast_value)).uniq, criteria)
         | 
| 63 | 
            +
                    when Range then BinaryOperator.new(key, :between, (cast_value(value.min)..cast_value(value.max)), criteria, true)
         | 
| 64 | 
            +
                    when Array then BinaryOperator.new(key, :in, value.map(&method(:cast_value)).uniq, criteria, true)
         | 
| 65 65 | 
             
                    else raise ArgumentError.new ":in takes an array/range, not #{value}"
         | 
| 66 66 | 
             
                    end
         | 
| 67 | 
            -
                   | 
| 67 | 
            +
                  when :between then BinaryOperator.new(key, :between, (cast_value(value.min)..cast_value(value.max)), criteria, true)
         | 
| 68 | 
            +
                  else BinaryOperator.new(key, op, cast_value(value), criteria, true)
         | 
| 68 69 | 
             
                  end
         | 
| 69 70 | 
             
                end
         | 
| 70 71 |  | 
| 71 72 | 
             
                def to_rql(doc)
         | 
| 72 73 | 
             
                  case op
         | 
| 74 | 
            +
                  when :defined then value ? doc.has_fields(key) : doc.has_fields(key).not
         | 
| 73 75 | 
             
                  when :between then (doc[key] >= value.min) & (doc[key] <= value.max)
         | 
| 74 | 
            -
                  when :in | 
| 76 | 
            +
                  when :in      then RethinkDB::RQL.new.expr(value).contains(doc[key])
         | 
| 75 77 | 
             
                  else doc[key].__send__(op, value)
         | 
| 76 78 | 
             
                  end
         | 
| 77 79 | 
             
                end
         | 
| @@ -87,6 +89,8 @@ module NoBrainer::Criteria::Where | |
| 87 89 | 
             
                end
         | 
| 88 90 |  | 
| 89 91 | 
             
                def cast_value(value)
         | 
| 92 | 
            +
                  return value if casted_values
         | 
| 93 | 
            +
             | 
| 90 94 | 
             
                  case association
         | 
| 91 95 | 
             
                  when NoBrainer::Document::Association::BelongsTo::Metadata
         | 
| 92 96 | 
             
                    target_klass = association.target_klass
         | 
| @@ -99,6 +103,8 @@ module NoBrainer::Criteria::Where | |
| 99 103 | 
             
                end
         | 
| 100 104 |  | 
| 101 105 | 
             
                def cast_key(key)
         | 
| 106 | 
            +
                  return key if casted_values
         | 
| 107 | 
            +
             | 
| 102 108 | 
             
                  case association
         | 
| 103 109 | 
             
                  when NoBrainer::Document::Association::BelongsTo::Metadata
         | 
| 104 110 | 
             
                    association.foreign_key
         | 
| @@ -2,7 +2,8 @@ class NoBrainer::DecoratedSymbol < Struct.new(:symbol, :modifier, :args) | |
| 2 2 | 
             
              MODIFIERS = { :in => :in, :nin => :nin,
         | 
| 3 3 | 
             
                            :eq => :eq, :ne => :ne, :not => :ne,
         | 
| 4 4 | 
             
                            :gt => :gt, :ge => :ge, :gte => :ge,
         | 
| 5 | 
            -
                            :lt => :lt, :le => :le, :lte => :le | 
| 5 | 
            +
                            :lt => :lt, :le => :le, :lte => :le,
         | 
| 6 | 
            +
                            :defined => :defined }
         | 
| 6 7 |  | 
| 7 8 | 
             
              def self.hook
         | 
| 8 9 | 
             
                Symbol.class_eval do
         | 
| @@ -1,7 +1,5 @@ | |
| 1 1 | 
             
            module NoBrainer::Document::Attributes
         | 
| 2 | 
            -
              VALID_FIELD_OPTIONS = [:index, :default, :type, : | 
| 3 | 
            -
                                     :cast_db_to_user, :validates, :required, :unique,
         | 
| 4 | 
            -
                                     :readonly, :primary_key]
         | 
| 2 | 
            +
              VALID_FIELD_OPTIONS = [:index, :default, :type, :validates, :required, :unique, :in, :readonly, :primary_key]
         | 
| 5 3 | 
             
              RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] \
         | 
| 6 4 | 
             
                                      + NoBrainer::DecoratedSymbol::MODIFIERS.keys
         | 
| 7 5 | 
             
              extend ActiveSupport::Concern
         | 
| @@ -64,6 +62,10 @@ module NoBrainer::Document::Attributes | |
| 64 62 | 
             
                Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
         | 
| 65 63 | 
             
              end
         | 
| 66 64 |  | 
| 65 | 
            +
              def to_s
         | 
| 66 | 
            +
                "#<#{self.class} #{self.class.pk_name}: #{self.pk_value.inspect}>"
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
             | 
| 67 69 | 
             
              def inspect
         | 
| 68 70 | 
             
                "#<#{self.class} #{inspectable_attributes.map { |k,v| "#{k}: #{v.inspect}" }.join(', ')}>"
         | 
| 69 71 | 
             
              end
         | 
| @@ -10,7 +10,7 @@ module NoBrainer::Document::Criteria | |
| 10 10 | 
             
              module ClassMethods
         | 
| 11 11 | 
             
                delegate :to_rql,                        # Core
         | 
| 12 12 | 
             
                         :limit, :offset, :skip,         # Limit
         | 
| 13 | 
            -
                         :order_by, :reverse_order, | 
| 13 | 
            +
                         :order_by, :reverse_order, :without_ordering, # OrderBy
         | 
| 14 14 | 
             
                         :scoped, :unscoped,             # Scope
         | 
| 15 15 | 
             
                         :where, :with_index, :without_index, :used_index, :indexed?, # Where
         | 
| 16 16 | 
             
                         :with_cache, :without_cache,    # Cache
         | 
| @@ -18,7 +18,7 @@ module NoBrainer::Document::Criteria | |
| 18 18 | 
             
                         :delete_all, :destroy_all,      # Delete
         | 
| 19 19 | 
             
                         :includes, :preload,            # Preload
         | 
| 20 20 | 
             
                         :each, :to_a,                   # Enumerable
         | 
| 21 | 
            -
                         :first, :last, :first!, :last!, # First
         | 
| 21 | 
            +
                         :first, :last, :first!, :last!, :sample, # First
         | 
| 22 22 | 
             
                         :update_all, :replace_all,      # Update
         | 
| 23 23 | 
             
                         :to => :all
         | 
| 24 24 |  | 
| @@ -28,7 +28,7 @@ module NoBrainer::Document::Persistance | |
| 28 28 |  | 
| 29 29 | 
             
              def _create(options={})
         | 
| 30 30 | 
             
                return false if options[:validate] && !valid?
         | 
| 31 | 
            -
                keys = self.class.insert_all(@_attributes)
         | 
| 31 | 
            +
                keys = self.class.insert_all(self.class.persistable_attributes(@_attributes))
         | 
| 32 32 | 
             
                self.pk_value ||= keys.first
         | 
| 33 33 | 
             
                @new_record = false
         | 
| 34 34 | 
             
                true
         | 
| @@ -50,7 +50,7 @@ module NoBrainer::Document::Persistance | |
| 50 50 | 
             
                  attr = RethinkDB::RQL.new.literal(attr) if attr.is_a?(Hash)
         | 
| 51 51 | 
             
                  [k, attr]
         | 
| 52 52 | 
             
                end]
         | 
| 53 | 
            -
                _update(attrs) if attrs.present?
         | 
| 53 | 
            +
                _update(self.class.persistable_attributes(attrs)) if attrs.present?
         | 
| 54 54 | 
             
                true
         | 
| 55 55 | 
             
              end
         | 
| 56 56 |  | 
| @@ -1,102 +1,7 @@ | |
| 1 1 | 
             
            module NoBrainer::Document::Types
         | 
| 2 2 | 
             
              extend ActiveSupport::Concern
         | 
| 3 3 |  | 
| 4 | 
            -
               | 
| 5 | 
            -
                extend self
         | 
| 6 | 
            -
                InvalidType = NoBrainer::Error::InvalidType
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                def String(value)
         | 
| 9 | 
            -
                  case value
         | 
| 10 | 
            -
                  when Symbol then value.to_s
         | 
| 11 | 
            -
                  else raise InvalidType
         | 
| 12 | 
            -
                  end
         | 
| 13 | 
            -
                end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                def Integer(value)
         | 
| 16 | 
            -
                  case value
         | 
| 17 | 
            -
                  when String
         | 
| 18 | 
            -
                    value = value.strip.gsub(/^\+/, '')
         | 
| 19 | 
            -
                    value.to_i.tap { |new_value| new_value.to_s == value or raise InvalidType }
         | 
| 20 | 
            -
                  when Float
         | 
| 21 | 
            -
                    value.to_i.tap { |new_value| new_value.to_f == value or raise InvalidType }
         | 
| 22 | 
            -
                  else raise InvalidType
         | 
| 23 | 
            -
                  end
         | 
| 24 | 
            -
                end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                def Float(value)
         | 
| 27 | 
            -
                  case value
         | 
| 28 | 
            -
                  when Integer then value.to_f
         | 
| 29 | 
            -
                  when String
         | 
| 30 | 
            -
                    value = value.strip.gsub(/^\+/, '')
         | 
| 31 | 
            -
                    value = value.gsub(/0+$/, '') if value['.']
         | 
| 32 | 
            -
                    value = value.gsub(/\.$/, '')
         | 
| 33 | 
            -
                    value = "#{value}.0" unless value['.']
         | 
| 34 | 
            -
                    value.to_f.tap { |new_value| new_value.to_s == value or raise InvalidType }
         | 
| 35 | 
            -
                  else raise InvalidType
         | 
| 36 | 
            -
                  end
         | 
| 37 | 
            -
                end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                def Boolean(value)
         | 
| 40 | 
            -
                  case value
         | 
| 41 | 
            -
                  when TrueClass  then true
         | 
| 42 | 
            -
                  when FalseClass then false
         | 
| 43 | 
            -
                  when String, Integer
         | 
| 44 | 
            -
                    value = value.to_s.strip.downcase
         | 
| 45 | 
            -
                    return true  if value.in? %w(true yes t 1)
         | 
| 46 | 
            -
                    return false if value.in? %w(false no f 0)
         | 
| 47 | 
            -
                    raise InvalidType
         | 
| 48 | 
            -
                  else raise InvalidType
         | 
| 49 | 
            -
                  end
         | 
| 50 | 
            -
                end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                def Symbol(value)
         | 
| 53 | 
            -
                  case value
         | 
| 54 | 
            -
                  when String
         | 
| 55 | 
            -
                    value = value.strip
         | 
| 56 | 
            -
                    raise InvalidType if value.empty?
         | 
| 57 | 
            -
                    value.to_sym
         | 
| 58 | 
            -
                  else raise InvalidType
         | 
| 59 | 
            -
                  end
         | 
| 60 | 
            -
                end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                def lookup(type)
         | 
| 63 | 
            -
                  public_method(type.to_s)
         | 
| 64 | 
            -
                rescue NameError
         | 
| 65 | 
            -
                  proc { raise InvalidType }
         | 
| 66 | 
            -
                end
         | 
| 67 | 
            -
              end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
              module CastDBToUser
         | 
| 70 | 
            -
                extend self
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                def Symbol(value)
         | 
| 73 | 
            -
                  value.to_sym rescue value
         | 
| 74 | 
            -
                end
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                def lookup(type)
         | 
| 77 | 
            -
                  public_method(type.to_s)
         | 
| 78 | 
            -
                rescue NameError
         | 
| 79 | 
            -
                  nil
         | 
| 80 | 
            -
                end
         | 
| 81 | 
            -
              end
         | 
| 82 | 
            -
             | 
| 83 | 
            -
              included do
         | 
| 84 | 
            -
                # We namespace our fake Boolean class to avoid polluting the global namespace
         | 
| 85 | 
            -
                class_exec do
         | 
| 86 | 
            -
                  class Boolean
         | 
| 87 | 
            -
                    def initialize; raise; end
         | 
| 88 | 
            -
                    def self.inspect; 'Boolean'; end
         | 
| 89 | 
            -
                    def self.to_s; inspect; end
         | 
| 90 | 
            -
                    def self.name; inspect; end
         | 
| 91 | 
            -
                  end
         | 
| 92 | 
            -
                end
         | 
| 93 | 
            -
                before_validation :add_type_errors
         | 
| 94 | 
            -
             | 
| 95 | 
            -
                # Fast access for db->user cast methods for performance when reading from
         | 
| 96 | 
            -
                # the database.
         | 
| 97 | 
            -
                singleton_class.send(:attr_accessor, :cast_db_to_user_fields)
         | 
| 98 | 
            -
                self.cast_db_to_user_fields = Set.new
         | 
| 99 | 
            -
              end
         | 
| 4 | 
            +
              included { before_validation :add_type_errors }
         | 
| 100 5 |  | 
| 101 6 | 
             
              def add_type_errors
         | 
| 102 7 | 
             
                return unless @pending_type_errors
         | 
| @@ -108,49 +13,59 @@ module NoBrainer::Document::Types | |
| 108 13 | 
             
              def assign_attributes(attrs, options={})
         | 
| 109 14 | 
             
                super
         | 
| 110 15 | 
             
                if options[:from_db]
         | 
| 111 | 
            -
                   | 
| 112 | 
            -
                     | 
| 113 | 
            -
             | 
| 114 | 
            -
                    value = @_attributes[attr.to_s]
         | 
| 115 | 
            -
                    unless value.nil? || value.is_a?(type)
         | 
| 116 | 
            -
                      @_attributes[attr.to_s] = field_def[:cast_db_to_user].call(value)
         | 
| 117 | 
            -
                    end
         | 
| 118 | 
            -
                  end
         | 
| 16 | 
            +
                  @_attributes = Hash[@_attributes.map do |k,v|
         | 
| 17 | 
            +
                    [k, self.class.cast_db_to_model_for(k, v)]
         | 
| 18 | 
            +
                  end].with_indifferent_access
         | 
| 119 19 | 
             
                end
         | 
| 120 20 | 
             
              end
         | 
| 121 21 |  | 
| 122 22 | 
             
              module ClassMethods
         | 
| 123 | 
            -
                def  | 
| 124 | 
            -
                   | 
| 125 | 
            -
                  return value if  | 
| 126 | 
            -
                  type | 
| 127 | 
            -
             | 
| 128 | 
            -
                   | 
| 23 | 
            +
                def cast_user_to_model_for(attr, value)
         | 
| 24 | 
            +
                  type = fields[attr.to_sym].try(:[], :type)
         | 
| 25 | 
            +
                  return value if type.nil? || value.nil?
         | 
| 26 | 
            +
                  if type.respond_to?(:nobrainer_cast_user_to_model)
         | 
| 27 | 
            +
                    type.nobrainer_cast_user_to_model(value)
         | 
| 28 | 
            +
                  else
         | 
| 29 | 
            +
                    raise NoBrainer::Error::InvalidType unless value.is_a?(type)
         | 
| 30 | 
            +
                    value
         | 
| 31 | 
            +
                  end
         | 
| 129 32 | 
             
                rescue NoBrainer::Error::InvalidType => error
         | 
| 130 | 
            -
                  error.type =  | 
| 33 | 
            +
                  error.type = type
         | 
| 131 34 | 
             
                  error.value = value
         | 
| 132 35 | 
             
                  error.attr_name = attr
         | 
| 133 36 | 
             
                  raise error
         | 
| 134 37 | 
             
                end
         | 
| 135 38 |  | 
| 136 | 
            -
                def  | 
| 137 | 
            -
                   | 
| 138 | 
            -
                   | 
| 39 | 
            +
                def cast_model_to_db_for(attr, value)
         | 
| 40 | 
            +
                  type = fields[attr.to_sym].try(:[], :type)
         | 
| 41 | 
            +
                  return value if type.nil? || value.nil? || !type.respond_to?(:nobrainer_cast_model_to_db)
         | 
| 42 | 
            +
                  type.nobrainer_cast_model_to_db(value)
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def cast_db_to_model_for(attr, value)
         | 
| 46 | 
            +
                  type = fields[attr.to_sym].try(:[], :type)
         | 
| 47 | 
            +
                  return value if type.nil? || value.nil? || !type.respond_to?(:nobrainer_cast_db_to_model)
         | 
| 48 | 
            +
                  type.nobrainer_cast_db_to_model(value)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def cast_user_to_db_for(attr, value)
         | 
| 52 | 
            +
                  value = cast_user_to_model_for(attr, value)
         | 
| 53 | 
            +
                  cast_model_to_db_for(attr, value)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def persistable_attributes(attrs)
         | 
| 57 | 
            +
                  Hash[attrs.map { |k,v| [k, cast_model_to_db_for(k, v)] }]
         | 
| 139 58 | 
             
                end
         | 
| 140 59 |  | 
| 141 60 | 
             
                def _field(attr, options={})
         | 
| 142 61 | 
             
                  super
         | 
| 143 62 |  | 
| 144 | 
            -
                  if options[: | 
| 145 | 
            -
                    ([self] + descendants).each do |klass|
         | 
| 146 | 
            -
                      klass.cast_db_to_user_fields << attr
         | 
| 147 | 
            -
                    end
         | 
| 148 | 
            -
                  end
         | 
| 63 | 
            +
                  NoBrainer::Document::Types.load_type_extensions(options[:type]) if options[:type]
         | 
| 149 64 |  | 
| 150 65 | 
             
                  inject_in_layer :types do
         | 
| 151 66 | 
             
                    define_method("#{attr}=") do |value|
         | 
| 152 67 | 
             
                      begin
         | 
| 153 | 
            -
                        value = self.class. | 
| 68 | 
            +
                        value = self.class.cast_user_to_model_for(attr, value)
         | 
| 154 69 | 
             
                        @pending_type_errors.try(:delete, attr)
         | 
| 155 70 | 
             
                      rescue NoBrainer::Error::InvalidType => error
         | 
| 156 71 | 
             
                        @pending_type_errors ||= {}
         | 
| @@ -163,21 +78,38 @@ module NoBrainer::Document::Types | |
| 163 78 | 
             
                  end
         | 
| 164 79 | 
             
                end
         | 
| 165 80 |  | 
| 166 | 
            -
                def field(attr, options={})
         | 
| 167 | 
            -
                  if options[:type]
         | 
| 168 | 
            -
                    options = options.merge(
         | 
| 169 | 
            -
                      :cast_user_to_db => NoBrainer::Document::Types::CastUserToDB.lookup(options[:type]),
         | 
| 170 | 
            -
                      :cast_db_to_user => NoBrainer::Document::Types::CastDBToUser.lookup(options[:type]))
         | 
| 171 | 
            -
                  end
         | 
| 172 | 
            -
                  super
         | 
| 173 | 
            -
                end
         | 
| 174 | 
            -
             | 
| 175 81 | 
             
                def _remove_field(attr, options={})
         | 
| 176 82 | 
             
                  super
         | 
| 83 | 
            +
             | 
| 177 84 | 
             
                  inject_in_layer :types do
         | 
| 178 85 | 
             
                    remove_method("#{attr}=")
         | 
| 179 86 | 
             
                    remove_method("#{attr}?") if method_defined?("#{attr}?")
         | 
| 180 87 | 
             
                  end
         | 
| 181 88 | 
             
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def field(attr, options={})
         | 
| 91 | 
            +
                  if options[:type] == Array || options[:type] == Hash
         | 
| 92 | 
            +
                    # XXX For the moment, NoBrainer does not support these complex types
         | 
| 93 | 
            +
                    options.delete(:type)
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                  super
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
              require File.join(File.dirname(__FILE__), 'types', 'boolean')
         | 
| 100 | 
            +
              Boolean = NoBrainer::Boolean
         | 
| 101 | 
            +
             | 
| 102 | 
            +
              class << self
         | 
| 103 | 
            +
                mattr_accessor :loaded_extensions
         | 
| 104 | 
            +
                self.loaded_extensions = Set.new
         | 
| 105 | 
            +
                def load_type_extensions(klass)
         | 
| 106 | 
            +
                  unless loaded_extensions.include?(klass)
         | 
| 107 | 
            +
                    begin
         | 
| 108 | 
            +
                      require File.join(File.dirname(__FILE__), 'types', klass.name.underscore)
         | 
| 109 | 
            +
                    rescue LoadError
         | 
| 110 | 
            +
                    end
         | 
| 111 | 
            +
                    loaded_extensions << klass
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
                end
         | 
| 182 114 | 
             
              end
         | 
| 183 115 | 
             
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # We namespace our fake Boolean class to avoid polluting the global namespace
         | 
| 2 | 
            +
            class NoBrainer::Boolean
         | 
| 3 | 
            +
              def initialize; raise; end
         | 
| 4 | 
            +
              def self.inspect; 'Boolean'; end
         | 
| 5 | 
            +
              def self.to_s; inspect; end
         | 
| 6 | 
            +
              def self.name; inspect; end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              module NoBrainerExtentions
         | 
| 9 | 
            +
                InvalidType = NoBrainer::Error::InvalidType
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def nobrainer_cast_user_to_model(value)
         | 
| 12 | 
            +
                  case value
         | 
| 13 | 
            +
                  when TrueClass  then true
         | 
| 14 | 
            +
                  when FalseClass then false
         | 
| 15 | 
            +
                  when String, Integer
         | 
| 16 | 
            +
                    value = value.to_s.strip.downcase
         | 
| 17 | 
            +
                    return true  if value.in? %w(true yes t 1)
         | 
| 18 | 
            +
                    return false if value.in? %w(false no f 0)
         | 
| 19 | 
            +
                    raise InvalidType
         | 
| 20 | 
            +
                  else raise InvalidType
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
              extend NoBrainerExtentions
         | 
| 25 | 
            +
            end
         | 
| 26 | 
            +
             | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            class Date
         | 
| 2 | 
            +
              module NoBrainerExtentions
         | 
| 3 | 
            +
                InvalidType = NoBrainer::Error::InvalidType
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def nobrainer_cast_user_to_model(value)
         | 
| 6 | 
            +
                  case value
         | 
| 7 | 
            +
                  when Date then value
         | 
| 8 | 
            +
                  when String
         | 
| 9 | 
            +
                    value = value.strip
         | 
| 10 | 
            +
                    date = Date.parse(value) rescue (raise InvalidType)
         | 
| 11 | 
            +
                    raise InvalidType unless date.iso8601 == value
         | 
| 12 | 
            +
                    date
         | 
| 13 | 
            +
                  else raise InvalidType
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def nobrainer_cast_db_to_model(value)
         | 
| 18 | 
            +
                  value.is_a?(Time) ? value.to_date : value
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def nobrainer_cast_model_to_db(value)
         | 
| 22 | 
            +
                  value.is_a?(Date) ? Time.utc(value.year, value.month, value.day) : value
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
              extend NoBrainerExtentions
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            class Float
         | 
| 2 | 
            +
              module NoBrainerExtentions
         | 
| 3 | 
            +
                InvalidType = NoBrainer::Error::InvalidType
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def nobrainer_cast_user_to_model(value)
         | 
| 6 | 
            +
                  case value
         | 
| 7 | 
            +
                  when Float   then value
         | 
| 8 | 
            +
                  when Integer then value.to_f
         | 
| 9 | 
            +
                  when String
         | 
| 10 | 
            +
                    value = value.strip.gsub(/^\+/, '')
         | 
| 11 | 
            +
                    value = value.gsub(/0+$/, '') if value['.']
         | 
| 12 | 
            +
                    value = value.gsub(/\.$/, '')
         | 
| 13 | 
            +
                    value = "#{value}.0" unless value['.']
         | 
| 14 | 
            +
                    value.to_f.tap { |new_value| new_value.to_s == value or raise InvalidType }
         | 
| 15 | 
            +
                  else raise InvalidType
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
              extend NoBrainerExtentions
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            class Integer
         | 
| 2 | 
            +
              module NoBrainerExtentions
         | 
| 3 | 
            +
                InvalidType = NoBrainer::Error::InvalidType
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def nobrainer_cast_user_to_model(value)
         | 
| 6 | 
            +
                  case value
         | 
| 7 | 
            +
                  when Integer then value
         | 
| 8 | 
            +
                  when String
         | 
| 9 | 
            +
                    value = value.strip.gsub(/^\+/, '')
         | 
| 10 | 
            +
                    value.to_i.tap { |new_value| new_value.to_s == value or raise InvalidType }
         | 
| 11 | 
            +
                  when Float
         | 
| 12 | 
            +
                    value.to_i.tap { |new_value| new_value.to_f == value or raise InvalidType }
         | 
| 13 | 
            +
                  else raise InvalidType
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
              extend NoBrainerExtentions
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            class String
         | 
| 2 | 
            +
              module NoBrainerExtentions
         | 
| 3 | 
            +
                InvalidType = NoBrainer::Error::InvalidType
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def nobrainer_cast_user_to_model(value)
         | 
| 6 | 
            +
                  case value
         | 
| 7 | 
            +
                  when String then value
         | 
| 8 | 
            +
                  when Symbol then value.to_s
         | 
| 9 | 
            +
                  else raise InvalidType
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
              extend NoBrainerExtentions
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            class Symbol
         | 
| 2 | 
            +
              module NoBrainerExtentions
         | 
| 3 | 
            +
                InvalidType = NoBrainer::Error::InvalidType
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def nobrainer_cast_user_to_model(value)
         | 
| 6 | 
            +
                  case value
         | 
| 7 | 
            +
                  when Symbol then value
         | 
| 8 | 
            +
                  when String
         | 
| 9 | 
            +
                    value = value.strip
         | 
| 10 | 
            +
                    raise InvalidType if value.empty?
         | 
| 11 | 
            +
                    value.to_sym
         | 
| 12 | 
            +
                  else raise InvalidType
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def nobrainer_cast_db_to_model(value)
         | 
| 17 | 
            +
                  value.to_sym rescue (value.to_s.to_sym rescue value)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
              extend NoBrainerExtentions
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require 'time'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Time
         | 
| 4 | 
            +
              module NoBrainerExtentions
         | 
| 5 | 
            +
                InvalidType = NoBrainer::Error::InvalidType
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def nobrainer_cast_user_to_model(value)
         | 
| 8 | 
            +
                  case value
         | 
| 9 | 
            +
                  when Time then time = value
         | 
| 10 | 
            +
                  when String
         | 
| 11 | 
            +
                    value = value.strip.sub(/Z$/, '+00:00')
         | 
| 12 | 
            +
                    # Using DateTime to preserve the timezone offset
         | 
| 13 | 
            +
                    dt = DateTime.parse(value) rescue (raise InvalidType)
         | 
| 14 | 
            +
                    time = Time.new(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.zone)
         | 
| 15 | 
            +
                    raise InvalidType unless time.iso8601 == value
         | 
| 16 | 
            +
                  else raise InvalidType
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  nobrainer_timezoned(NoBrainer::Config.user_timezone, time)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def nobrainer_cast_db_to_model(value)
         | 
| 23 | 
            +
                  return value unless value.is_a?(Time)
         | 
| 24 | 
            +
                  nobrainer_timezoned(NoBrainer::Config.user_timezone, value)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def nobrainer_cast_model_to_db(value)
         | 
| 28 | 
            +
                  return value unless value.is_a?(Time)
         | 
| 29 | 
            +
                  nobrainer_timezoned(NoBrainer::Config.db_timezone, value)
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def nobrainer_timezoned(tz, value)
         | 
| 33 | 
            +
                  case tz
         | 
| 34 | 
            +
                  when :local     then value.getlocal
         | 
| 35 | 
            +
                  when :utc       then value.getutc
         | 
| 36 | 
            +
                  when :unchanged then value
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
              extend NoBrainerExtentions
         | 
| 41 | 
            +
            end
         | 
| @@ -18,6 +18,7 @@ module NoBrainer::Document::Validation | |
| 18 18 | 
             
                  super
         | 
| 19 19 | 
             
                  validates(attr, { :presence => options[:required] }) if options.has_key?(:required)
         | 
| 20 20 | 
             
                  validates(attr, { :uniqueness => options[:unique] }) if options.has_key?(:unique)
         | 
| 21 | 
            +
                  validates(attr, { :inclusion => {:in => options[:in]} }) if options.has_key?(:in)
         | 
| 21 22 | 
             
                  validates(attr, options[:validates]) if options[:validates]
         | 
| 22 23 | 
             
                end
         | 
| 23 24 | 
             
              end
         | 
    
        data/lib/no_brainer/error.rb
    CHANGED
    
    
    
        data/lib/nobrainer.rb
    CHANGED
    
    
| @@ -2,7 +2,8 @@ require "rails/generators/nobrainer" | |
| 2 2 |  | 
| 3 3 | 
             
            module NoBrainer::Generators
         | 
| 4 4 | 
             
              class ModelGenerator < Base
         | 
| 5 | 
            -
                argument | 
| 5 | 
            +
                argument(:attributes, :type => :array, default: [],
         | 
| 6 | 
            +
                         banner: "field[:type][:index] ... field[:type][:index]")
         | 
| 6 7 |  | 
| 7 8 | 
             
                check_class_collision
         | 
| 8 9 |  | 
| @@ -2,12 +2,17 @@ | |
| 2 2 | 
             
            class <%= class_name %><%= " < #{options[:parent].classify}" if options[:parent] %>
         | 
| 3 3 | 
             
            <% unless options[:parent] -%>
         | 
| 4 4 | 
             
              include NoBrainer::Document
         | 
| 5 | 
            +
              include NoBrainer::Document::Timestamps
         | 
| 6 | 
            +
             | 
| 5 7 | 
             
            <% end -%>
         | 
| 6 8 | 
             
            <% attributes.reject(&:reference?).each do |attribute| -%>
         | 
| 7 | 
            -
              field :<%= attribute.name  | 
| 9 | 
            +
              field :<%= attribute.name -%>
         | 
| 10 | 
            +
            <%= puts attribute; ", :type => #{attribute.type.to_s.classify}" if attribute.type != :object -%>
         | 
| 11 | 
            +
            <%= ", :index => true" if attribute.has_index? %>
         | 
| 8 12 | 
             
            <% end -%>
         | 
| 9 13 | 
             
            <% attributes.select(&:reference?).each do |attribute| -%>
         | 
| 10 | 
            -
              belongs_to :<%= attribute.name  | 
| 14 | 
            +
              belongs_to :<%= attribute.name -%>
         | 
| 15 | 
            +
            <%= ", :index => true" if attribute.has_index? %>
         | 
| 11 16 | 
             
            <% end -%>
         | 
| 12 17 | 
             
            end
         | 
| 13 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. | 
| 4 | 
            +
              version: 0.15.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- | 
| 11 | 
            +
            date: 2014-07-27 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rethinkdb
         | 
| @@ -119,6 +119,13 @@ files: | |
| 119 119 | 
             
            - lib/no_brainer/document/store_in.rb
         | 
| 120 120 | 
             
            - lib/no_brainer/document/timestamps.rb
         | 
| 121 121 | 
             
            - lib/no_brainer/document/types.rb
         | 
| 122 | 
            +
            - lib/no_brainer/document/types/boolean.rb
         | 
| 123 | 
            +
            - lib/no_brainer/document/types/date.rb
         | 
| 124 | 
            +
            - lib/no_brainer/document/types/float.rb
         | 
| 125 | 
            +
            - lib/no_brainer/document/types/integer.rb
         | 
| 126 | 
            +
            - lib/no_brainer/document/types/string.rb
         | 
| 127 | 
            +
            - lib/no_brainer/document/types/symbol.rb
         | 
| 128 | 
            +
            - lib/no_brainer/document/types/time.rb
         | 
| 122 129 | 
             
            - lib/no_brainer/document/uniqueness.rb
         | 
| 123 130 | 
             
            - lib/no_brainer/document/validation.rb
         | 
| 124 131 | 
             
            - lib/no_brainer/error.rb
         |