tiny_dyno 0.1.15 → 0.1.17

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: c36f72ed6a7268d1dcb055c82ec8dcf92c87d88e
4
- data.tar.gz: 357330a97dab4c2bc0fb145636e0001187febf58
3
+ metadata.gz: 9f6eb1c54e92c87fa263c8e6f0b771aa92c2ccb4
4
+ data.tar.gz: d0c96f8ac8c67684f30cf13480284194cec2f2de
5
5
  SHA512:
6
- metadata.gz: 3b208b2b409cb4153e0b0fa4030d2d61660579ca643b716df65e443662a057650f2ce20b0738802b128eef7cb4f48c4e990bc3a5dae59bc189ef3bc66a8f9d71
7
- data.tar.gz: 29286db01b34b9ad78ce1a2a21714646a407d23e851fd93d31caa481e68469f318c6a3d3c296313c0ae32bc93c3a5b6bcd1d14b0732a2fa25b3e6837d45f24bb
6
+ metadata.gz: e04e399d3bcd7b91c83279c72b55d0a6b4245e8ccc8fc4fe5239a16f191aac9722b80eaa11248e7276930873da570adef5b83a0915a7ed31bfd3f2b5d24ff213
7
+ data.tar.gz: e99ac9852f5a771203ec483218ac4675bb6028fd057ce665e52f84e7848b89f60507699494aadcde229f0a225cf9f7aaca36dc1859684f4b33e98d7fdeea62e2
data/CHANGES.md CHANGED
@@ -1,3 +1,22 @@
1
+ 0.1.17 (2015-09-08)
2
+ -------------------
3
+
4
+ * Use simple_attributes: false by default and enforce/ coerce attributes to be of specified Class as per model
5
+
6
+ 0.1.16 (2015-09-04)
7
+
8
+ * New - Add boolean field type support and introduce type checking on setting field values to assert correct type coercion, when setting values
9
+
10
+ 0.1.15 (2015-09-04)
11
+ -------------------
12
+
13
+ * New - add query proxy method, to support arbitrary queries to dynamodb
14
+
15
+ 0.1.14 (2015-08-31)
16
+ -------------------
17
+
18
+ * Fix - TinyDyno::Document.create now returns either the persisted document or nil
19
+
1
20
  0.1.13 (2015-07-06)
2
21
  -------------------
3
22
 
data/bin/tracer CHANGED
@@ -13,7 +13,6 @@ class SmallPerson
13
13
 
14
14
  end
15
15
 
16
- require 'tracer'
17
- Tracer.on
18
16
  sm = SmallPerson.new(first_name: 'peter_parker')
19
-
17
+ sm.age = '27'
18
+ sm.age = 'foobar'
@@ -159,6 +159,12 @@ en:
159
159
  since the child could potentially reference a nonexistant parent."
160
160
  resolution: "Make sure to only use create or create! when the parent
161
161
  document %{base} is persisted."
162
+ value_not_typecasted:
163
+ message: "Value %{value} is not the correct type in field %{name}"
164
+ summary: "You can not assign values of this type, to a field of type
165
+ %{name}"
166
+ resolution: "Only assign supported values to field %{name} or change
167
+ the field type."
162
168
  validations:
163
169
  message: "Validation of %{document} failed."
164
170
  summary: "The following errors were found: %{errors}"
@@ -0,0 +1,161 @@
1
+ require 'bigdecimal'
2
+ require 'stringio'
3
+ require 'set'
4
+
5
+ module TinyDyno
6
+ module Adapter
7
+
8
+ class AttributeValue
9
+ # utilize the same type coercions as in the ruby aws-sdk
10
+ # ( https://github.com/aws/aws-sdk-ruby/blob/master/aws-sdk-core/lib/aws-sdk-core/dynamodb/attribute_value.rb )
11
+ # however in a more deterministic fashion
12
+ # the type conversion employed by the simple attribute feature
13
+ # is purely based on the value field
14
+ # tiny_dyno enforces coercion into the designated target data type
15
+ # or raises an error on mismatch
16
+
17
+ def initialize
18
+ @marshaler = Marshaler.new
19
+ @unmarshaler = Unmarshaler.new
20
+ end
21
+
22
+ def marshal(type:, value:)
23
+ @marshaler.format(type: type, obj: value)
24
+ end
25
+
26
+ def unmarshal(value)
27
+ @unmarshaler.format(value)
28
+ end
29
+
30
+ class Marshaler
31
+
32
+ def format(type: 'auto', obj:)
33
+ type = obj.class.to_s if type == 'auto'
34
+ case type.to_s
35
+ when 'Hash'
36
+ obj.each.with_object(m:{}) do |(key, value), map|
37
+ map[:m][key.to_s] = format(type: value.class, obj: value)
38
+ end
39
+ when 'Array'
40
+ obj.each.with_object(l:[]) do |value, list|
41
+ list[:l] << format(type: value.class, obj: value)
42
+ end
43
+ when 'String'
44
+ if obj.nil?
45
+ { null: true }
46
+ else
47
+ { s: obj }
48
+ end
49
+ when 'Symbol' then { s: obj.to_s }
50
+ when 'Numeric', 'Fixnum', 'Float', 'Integer'
51
+ if obj.to_i != 0 and obj != '0'
52
+ { n: obj.to_s }
53
+ elsif obj.to_i === 0 and obj == '0'
54
+ { n: obj.to_s }
55
+ elsif obj.is_a?(Integer)
56
+ { n: obj.to_s }
57
+ elsif obj.nil?
58
+ { n: nil }
59
+ else
60
+ raise TinyDyno::Errors::InvalidValueType.new(klass: self.class, name: type, value: obj)
61
+ end
62
+ when 'StringIO', 'IO' then { b: obj }
63
+ when 'Set' then format_set(obj)
64
+ when 'TrueClass', 'FalseClass', 'TinyDyno::Boolean'
65
+ # ToDo, how can we initialize a boolean field into a valid state?
66
+ raise TinyDyno::Errors::InvalidValueType.new(klass: self.class, name: type, value: obj) unless [true,false,nil].include?(obj)
67
+ { bool: obj }
68
+ when 'NilClass' then { null: true }
69
+ else
70
+ msg = "unsupported type, expected Hash, Array, Set, String, Numeric, "
71
+ msg << "IO, true, false, or nil, got #{obj.class.name}"
72
+ raise ArgumentError, msg
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def format_set(set)
79
+ case set.first
80
+ when String, Symbol then { ss: set.map(&:to_s) }
81
+ when Numeric then { ns: set.map(&:to_s) }
82
+ when StringIO, IO then { bs: set.to_a }
83
+ else
84
+ msg = "set types only support String, Numeric, or IO objects"
85
+ raise ArgumentError, msg
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ class Unmarshaler
92
+
93
+ def format(obj)
94
+ type, value = extract_type_and_value(obj)
95
+ case type
96
+ when :m
97
+ value.each.with_object({}) do |(k, v), map|
98
+ map[k] = format(v)
99
+ end
100
+ when :l then value.map { |v| format(v) }
101
+ when :s then value
102
+ when :n then BigDecimal.new(value)
103
+ when :b then StringIO.new(value)
104
+ when :null then nil
105
+ when :bool then value
106
+ when :ss then Set.new(value)
107
+ when :ns then Set.new(value.map { |n| BigDecimal.new(n) })
108
+ when :bs then Set.new(value.map { |b| StringIO.new(b) })
109
+ else
110
+ raise ArgumentError, "unhandled type #{type.inspect}"
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def extract_type_and_value(obj)
117
+ case obj
118
+ when Hash then obj.to_a.first
119
+ when Struct
120
+ obj.members.each do |key|
121
+ value = obj[key]
122
+ return [key, value] unless value.nil?
123
+ end
124
+ else
125
+ raise ArgumentError, "unhandled type #{obj.inspect}"
126
+ end
127
+ end
128
+
129
+ end
130
+ end #class AttributeValue
131
+
132
+ extend self
133
+
134
+ def aws_attribute(field_type:, value:)
135
+ av = TinyDyno::Adapter::AttributeValue.new
136
+ av.marshal(type: field_type, value: value)
137
+ end
138
+
139
+ def doc_attribute(value)
140
+ av = TinyDyno::Adapter::AttributeValue.new
141
+ av.unmarshal(value)
142
+ end
143
+
144
+ def simple_attribute(field_type:, value:)
145
+ raw_attribute = aws_attribute(field_type: field_type, value: value)
146
+
147
+ case field_type.to_s
148
+ when 'Fixnum', 'Integer'
149
+ simple_value = doc_attribute(raw_attribute).to_i
150
+ when 'Float'
151
+ simple_value = doc_attribute(raw_attribute).to_f
152
+ when 'Numeric', 'String', 'Array', 'Hash', 'TinyDyno::Boolean' then
153
+ simple_value = doc_attribute(raw_attribute)
154
+ else
155
+ raise ArgumentError, "unhandled type #{ field_type.inspect }"
156
+ end
157
+ simple_value
158
+ end #simple_attribute
159
+
160
+ end
161
+ end
@@ -13,7 +13,9 @@ module TinyDyno
13
13
  def get_item(get_item_request:)
14
14
  resp = connection.get_item(get_item_request)
15
15
  if resp.respond_to?(:item)
16
- resp.item
16
+ typed_attributes = {}
17
+ resp.item.each {|k,v| typed_attributes[k] = TinyDyno::Adapter.doc_attribute(v) }
18
+ typed_attributes
17
19
  else
18
20
  nil
19
21
  end
@@ -2,6 +2,7 @@ require 'aws-sdk'
2
2
 
3
3
  require 'tiny_dyno/adapter/tables'
4
4
  require 'tiny_dyno/adapter/items'
5
+ require 'tiny_dyno/adapter/attributes'
5
6
 
6
7
  module TinyDyno
7
8
 
@@ -42,7 +43,7 @@ module TinyDyno
42
43
  def connection
43
44
  unless @connection
44
45
  TinyDyno.logger.info 'setting up new connection ... ' if TinyDyno.logger
45
- @connection = Aws::DynamoDB::Client.new
46
+ @connection = Aws::DynamoDB::Client.new(simple_attributes: false)
46
47
  update_table_cache
47
48
  end
48
49
  @connection
@@ -1,7 +1,8 @@
1
1
  require 'active_model/attribute_methods'
2
-
3
2
  require 'tiny_dyno/attributes/readonly'
4
3
 
4
+ require 'pry'
5
+
5
6
  module TinyDyno
6
7
  module Attributes
7
8
 
@@ -115,10 +116,8 @@ module TinyDyno
115
116
  #
116
117
  # @since 1.0.0
117
118
  def typed_value_for(key, value)
118
- # raise MissingAttributeError if fields[key].nil? and hash_keys.find_index { |a| a[:attr] == key }.nil?
119
119
  raise MissingAttributeError if fields[key].nil?
120
- typed_class = self.fields[key].options[:type]
121
- return (self.class.document_typed(klass: typed_class, value: value))
120
+ TinyDyno::Adapter.simple_attribute(field_type: self.fields[key].options[:type], value: value)
122
121
  end
123
122
 
124
123
  # Determine if the attribute is missing from the document, due to loading
@@ -136,22 +135,5 @@ module TinyDyno
136
135
  return (!self.fields.keys.include?(name))
137
136
  end
138
137
 
139
- private
140
-
141
-
142
- module ClassMethods
143
-
144
- # convert to the type used on the Document
145
- def document_typed(klass:, value: )
146
- if klass == String
147
- value.blank? ? nil : value.to_s
148
- elsif (klass == Integer or klass == Fixnum )
149
- value.to_i
150
- else
151
- value
152
- end
153
- end
154
- end
155
-
156
138
  end
157
139
  end
@@ -49,3 +49,4 @@ module TinyDyno
49
49
 
50
50
  end
51
51
  end
52
+
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module TinyDyno
3
+ module Errors
4
+
5
+ # This error is raised, when a query is performed with fields specified
6
+ # that are not HashKeys, which would result in a table scan
7
+ class InvalidValueType < TinyDynoError
8
+
9
+ # Create the new error.
10
+ #
11
+ # @example Instantiate the error.
12
+ # InvalidSelector.new(Person, "gender")
13
+ #
14
+ # @param [ Class ] klass The model class.
15
+ # @param [ String, Symbol ] name The name of the attribute.
16
+ #
17
+ # @since 3.0.0
18
+ def initialize(klass:, name:, value:)
19
+ super(
20
+ compose_message("value_not_typecasted", { klass: klass.name, name: name, value: value })
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -1,3 +1,4 @@
1
1
  require 'tiny_dyno/errors/tiny_dyno_error'
2
2
  require 'tiny_dyno/errors/attribute_errors'
3
- require 'tiny_dyno/errors/hash_key_errors'
3
+ require 'tiny_dyno/errors/hash_key_errors'
4
+ require 'tiny_dyno/errors/invalid_type_error'
@@ -0,0 +1,4 @@
1
+ module ::TinyDyno::Boolean; end
2
+
3
+ class TrueClass; include ::TinyDyno::Boolean; end
4
+ class FalseClass; include ::TinyDyno::Boolean; end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module TinyDyno
3
+ module Extensions
4
+ module FalseClass
5
+
6
+ # Is the passed value a boolean?
7
+ #
8
+ # @example Is the value a boolean type?
9
+ # false.is_a?(Boolean)
10
+ #
11
+ # @param [ Class ] other The class to check.
12
+ #
13
+ # @return [ true, false ] If the other is a boolean.
14
+ #
15
+ # @since 1.0.0
16
+ def is_a?(other)
17
+ if other == ::TinyDyno::Boolean || other.class == ::TinyDyno::Boolean
18
+ return true
19
+ end
20
+ super(other)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ ::FalseClass.__send__(:include, TinyDyno::Extensions::FalseClass)
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module TinyDyno
3
+ module Extensions
4
+ module TrueClass
5
+
6
+ # Is the passed value a boolean?
7
+ #
8
+ # @example Is the value a boolean type?
9
+ # true.is_a?(Boolean)
10
+ #
11
+ # @param [ Class ] other The class to check.
12
+ #
13
+ # @return [ true, false ] If the other is a boolean.
14
+ #
15
+ # @since 1.0.0
16
+ def is_a?(other)
17
+ if other == ::TinyDyno::Boolean || other.class == ::TinyDyno::Boolean
18
+ return true
19
+ end
20
+ super(other)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ ::TrueClass.__send__(:include, TinyDyno::Extensions::TrueClass)
@@ -1 +1,5 @@
1
- require 'tiny_dyno/extensions/module'
1
+ require 'tiny_dyno/extensions/module'
2
+ require 'tiny_dyno/extensions/boolean'
3
+ require 'tiny_dyno/extensions/false_class'
4
+ require 'tiny_dyno/extensions/true_class'
5
+
@@ -26,10 +26,6 @@ module TinyDyno
26
26
  @type = options[:type]
27
27
  end
28
28
 
29
- def as_selector
30
- binding.pry
31
- end
32
-
33
29
  end
34
30
  end
35
31
  end
@@ -1,26 +1,11 @@
1
1
  require 'tiny_dyno/fields/standard'
2
2
  require 'tiny_dyno/fields/range_key'
3
+ require 'tracer'
3
4
 
4
5
  module TinyDyno
5
6
  module Fields
6
7
  extend ActiveSupport::Concern
7
8
 
8
- TYPE_MAPPINGS= {
9
- # binary_blob: 'B',
10
- # bool: Boolean,
11
- # binary_set: Array,
12
- list: Array,
13
- map: Hash,
14
- number: Integer,
15
- number_set: Array,
16
- # null: Null,
17
- string: String,
18
- string_set: Array,
19
- time: Time,
20
- }
21
-
22
- SUPPORTED_FIELD_TYPES = [Array, Hash, Integer, Array, String, Time].freeze
23
-
24
9
  included do
25
10
  class_attribute :fields
26
11
 
@@ -249,6 +234,7 @@ module TinyDyno
249
234
  end
250
235
  end
251
236
  end
237
+
252
238
  # Create the setter method for the provided field.
253
239
  #
254
240
  # @example Create the setter.
@@ -260,8 +246,8 @@ module TinyDyno
260
246
  def create_field_setter(name, meth, field)
261
247
  generated_methods.module_eval do
262
248
  re_define_method("#{meth}=") do |value|
263
- val = write_attribute(name, value)
264
- val
249
+ typed_value = TinyDyno::Adapter.simple_attribute(field_type: field.options[:type], value: value)
250
+ write_attribute(name, typed_value)
265
251
  end
266
252
  end
267
253
  end
@@ -15,7 +15,8 @@ module TinyDyno
15
15
  # for further use in DynamoDB queries, i.e. to look up an object
16
16
  #
17
17
  def hash_key_as_selector
18
- { "#{ self.class.primary_key[:attr] }": attributes[self.class.primary_key[:attr]] }
18
+ key_field = self.class.primary_key[:attr]
19
+ { "#{ self.class.primary_key[:attr] }": TinyDyno::Adapter.aws_attribute(field_type: fields[key_field].options[:type], value: attributes[key_field]) }
19
20
  end
20
21
 
21
22
  module ClassMethods
@@ -94,8 +95,8 @@ module TinyDyno
94
95
  # convert values in queries to DynamoDB
95
96
  # into types as expected by DynamoDB
96
97
  def dyno_typed_key(key:, val:)
97
- typed_class = self.fields[key].options[:type]
98
- return (document_typed(klass: typed_class, value: val))
98
+ field_type = self.fields[key].options[:type]
99
+ return (TinyDyno::Adapter.aws_attribute(field_type: field_type, value: val))
99
100
  end
100
101
 
101
102
  end
@@ -50,7 +50,7 @@ module TinyDyno
50
50
 
51
51
  def build_item_request_entries
52
52
  item_entries = {}
53
- attributes.each { |k,v| item_entries[k] = v }
53
+ attributes.each { |k,v| item_entries[k] = TinyDyno::Adapter.aws_attribute(field_type: fields[k].options[:type], value: v) }
54
54
  item_entries
55
55
  end
56
56
 
@@ -1,3 +1,3 @@
1
1
  module TinyDyno
2
- VERSION = '0.1.15'
2
+ VERSION = '0.1.17'
3
3
  end
data/lib/tiny_dyno.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'active_model'
2
+ require 'active_support/core_ext/hash/indifferent_access'
2
3
  require 'aws-sdk'
3
4
 
4
5
  require 'tiny_dyno/extensions'
data/tiny_dyno.gemspec CHANGED
@@ -34,8 +34,8 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency 'guard-rspec', '~> 4.5'
35
35
  spec.add_development_dependency 'fabrication', '~> 2.13'
36
36
  spec.add_development_dependency 'faker', '~> 1.4'
37
- # spec.add_development_dependency 'awesome_print'
38
- # spec.add_development_dependency 'pry'
37
+ spec.add_development_dependency 'awesome_print'
38
+ spec.add_development_dependency 'pry'
39
39
 
40
40
  spec.add_dependency 'aws-sdk', '~> 2.1.2'
41
41
  spec.add_dependency 'activemodel', '~> 4.2.3'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tiny_dyno
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.15
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Gerschner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-01 00:00:00.000000000 Z
11
+ date: 2015-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -122,6 +122,34 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '1.4'
125
+ - !ruby/object:Gem::Dependency
126
+ name: awesome_print
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
125
153
  - !ruby/object:Gem::Dependency
126
154
  name: aws-sdk
127
155
  requirement: !ruby/object:Gem::Requirement
@@ -175,6 +203,7 @@ files:
175
203
  - lib/config/locales/en.yml
176
204
  - lib/tiny_dyno.rb
177
205
  - lib/tiny_dyno/adapter.rb
206
+ - lib/tiny_dyno/adapter/attributes.rb
178
207
  - lib/tiny_dyno/adapter/items.rb
179
208
  - lib/tiny_dyno/adapter/tables.rb
180
209
  - lib/tiny_dyno/attributes.rb
@@ -186,10 +215,14 @@ files:
186
215
  - lib/tiny_dyno/errors.rb
187
216
  - lib/tiny_dyno/errors/attribute_errors.rb
188
217
  - lib/tiny_dyno/errors/hash_key_errors.rb
218
+ - lib/tiny_dyno/errors/invalid_type_error.rb
189
219
  - lib/tiny_dyno/errors/tiny_dyno_error.rb
190
220
  - lib/tiny_dyno/expected.rb
191
221
  - lib/tiny_dyno/extensions.rb
222
+ - lib/tiny_dyno/extensions/boolean.rb
223
+ - lib/tiny_dyno/extensions/false_class.rb
192
224
  - lib/tiny_dyno/extensions/module.rb
225
+ - lib/tiny_dyno/extensions/true_class.rb
193
226
  - lib/tiny_dyno/fields.rb
194
227
  - lib/tiny_dyno/fields/range_key.rb
195
228
  - lib/tiny_dyno/fields/standard.rb