tiny_dyno 0.1.15 → 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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