tiny_dyno 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.simplecov +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Guardfile +70 -0
- data/LICENSE +41 -0
- data/README.md +121 -0
- data/Rakefile +6 -0
- data/bin/console +8 -0
- data/bin/fastcheck +25 -0
- data/bin/setup +7 -0
- data/bin/tracer +19 -0
- data/ci/tests.sh +52 -0
- data/lib/config/locales/en.yml +168 -0
- data/lib/tiny_dyno.rb +44 -0
- data/lib/tiny_dyno/adapter.rb +48 -0
- data/lib/tiny_dyno/adapter/items.rb +27 -0
- data/lib/tiny_dyno/adapter/tables.rb +103 -0
- data/lib/tiny_dyno/attributes.rb +157 -0
- data/lib/tiny_dyno/attributes/readonly.rb +56 -0
- data/lib/tiny_dyno/changeable.rb +68 -0
- data/lib/tiny_dyno/document.rb +100 -0
- data/lib/tiny_dyno/document_composition.rb +49 -0
- data/lib/tiny_dyno/errors.rb +4 -0
- data/lib/tiny_dyno/errors/attribute_errors.rb +25 -0
- data/lib/tiny_dyno/errors/hash_key_errors.rb +78 -0
- data/lib/tiny_dyno/errors/tiny_dyno_error.rb +85 -0
- data/lib/tiny_dyno/extensions.rb +1 -0
- data/lib/tiny_dyno/extensions/module.rb +28 -0
- data/lib/tiny_dyno/fields.rb +299 -0
- data/lib/tiny_dyno/fields/standard.rb +64 -0
- data/lib/tiny_dyno/hash_keys.rb +134 -0
- data/lib/tiny_dyno/loggable.rb +69 -0
- data/lib/tiny_dyno/persistable.rb +88 -0
- data/lib/tiny_dyno/range_attributes.rb +15 -0
- data/lib/tiny_dyno/stateful.rb +95 -0
- data/lib/tiny_dyno/tables.rb +98 -0
- data/lib/tiny_dyno/version.rb +3 -0
- data/tiny_dyno.gemspec +41 -0
- metadata +226 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TinyDyno
|
3
|
+
module Fields
|
4
|
+
class Standard
|
5
|
+
|
6
|
+
# Defines the behaviour for defined fields in the document.
|
7
|
+
# Set readers for the instance variables.
|
8
|
+
attr_accessor :default_val, :label, :name, :options
|
9
|
+
|
10
|
+
# Create the new field with a name and optional additional options.
|
11
|
+
#
|
12
|
+
# @example Create the new field.
|
13
|
+
# Field.new(:name, :type => String)
|
14
|
+
#
|
15
|
+
# @param [ Hash ] options The field options.
|
16
|
+
#
|
17
|
+
# @option options [ Class ] :type The class of the field.
|
18
|
+
# @option options [ Object ] :default The default value for the field.
|
19
|
+
# @option options [ String ] :label The field's label.
|
20
|
+
#
|
21
|
+
# @since 3.0.0
|
22
|
+
def initialize(name, options = {})
|
23
|
+
@name = name
|
24
|
+
@options = options
|
25
|
+
@label = options[:label]
|
26
|
+
@default_val = options[:default]
|
27
|
+
@type = options[:type]
|
28
|
+
end
|
29
|
+
|
30
|
+
# # Get the type of this field - inferred from the class name.
|
31
|
+
# #
|
32
|
+
# # @example Get the type.
|
33
|
+
# # field.type
|
34
|
+
# #
|
35
|
+
# # @return [ Class ] The name of the class.
|
36
|
+
# #
|
37
|
+
# # @since 2.1.0
|
38
|
+
# def type
|
39
|
+
# @type = options[:type]
|
40
|
+
# end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Is the field included in the fields that were returned from the
|
45
|
+
# database? We can apply the default if:
|
46
|
+
# 1. The field is included in an only limitation (field: 1)
|
47
|
+
# 2. The field is not excluded in a without limitation (field: 0)
|
48
|
+
#
|
49
|
+
# @example Is the field included?
|
50
|
+
# field.included?(fields)
|
51
|
+
#
|
52
|
+
# @param [ Hash ] fields The field limitations.
|
53
|
+
#
|
54
|
+
# @return [ true, false ] If the field was included.
|
55
|
+
#
|
56
|
+
# @since 2.4.4
|
57
|
+
def included?(fields)
|
58
|
+
(fields.values.first == 1 && fields[name.to_s] == 1) ||
|
59
|
+
(fields.values.first == 0 && !fields.has_key?(name.to_s))
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module TinyDyno
|
2
|
+
module HashKeys
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :attribute_definitions, :key_schema, :hash_keys
|
7
|
+
|
8
|
+
# TODO :local_secondary_indexes, :global_secondary_indexes
|
9
|
+
self.attribute_definitions = []
|
10
|
+
self.key_schema = []
|
11
|
+
self.hash_keys = []
|
12
|
+
@hash_key_fields = []
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
# return all defined hash keys on an instantiated object
|
17
|
+
# for further use in DynamoDB queries
|
18
|
+
#
|
19
|
+
def hash_key_as_selector
|
20
|
+
selector = {}
|
21
|
+
self.class.hash_keys.each { |hk| selector[hk[:attr]] = attributes[hk[:attr]] }
|
22
|
+
selector
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
|
27
|
+
# Return true/false, depending on whether the provided argument
|
28
|
+
# matches a defined hash key for this document model
|
29
|
+
# @example Hash key is defined?
|
30
|
+
# Person.hash_key_is_defined?(:id)
|
31
|
+
#
|
32
|
+
# @param [ String ] name, the name of the hash key
|
33
|
+
# @return [ Boolean ] True, False
|
34
|
+
def hash_key_is_defined?(arg = nil)
|
35
|
+
@hash_key_fields.include?(arg)
|
36
|
+
end
|
37
|
+
|
38
|
+
# return the attribute_type as stored in the attribute_definitions
|
39
|
+
def lookup_attribute_type(attribute_name)
|
40
|
+
type = attribute_definitions.collect {|a| a[:attribute_type] if a[:attribute_name] == attribute_name }
|
41
|
+
type.first
|
42
|
+
end
|
43
|
+
|
44
|
+
# Defines all the fields that are accessible on the Document
|
45
|
+
# For each field that is defined, a getter and setter will be
|
46
|
+
# added as an instance method to the Document.
|
47
|
+
#
|
48
|
+
# @example Define a field.
|
49
|
+
# field :score, :type => Integer, :default => 0
|
50
|
+
#
|
51
|
+
# @param [ Symbol ] name The name of the field.
|
52
|
+
# @param [ Hash ] options The options to pass to the field.
|
53
|
+
#
|
54
|
+
# @option options [ Class ] :type The type of the field.
|
55
|
+
# @option options [ String ] :label The label for the field.
|
56
|
+
# @option options [ Object, Proc ] :default The field's default
|
57
|
+
#
|
58
|
+
# @return [ Field ] The generated field
|
59
|
+
def hash_key(name, options = {})
|
60
|
+
named = name.to_s
|
61
|
+
attribute_definition = build_attribute_definition(named,options[:type])
|
62
|
+
key_schema = build_key_schema(named)
|
63
|
+
unless attribute_definition_meets_spec?(attribute_definition)
|
64
|
+
raise InvalidHashKey.new(self.class, name)
|
65
|
+
end
|
66
|
+
# we need the accessors as well
|
67
|
+
add_field(named, options)
|
68
|
+
self.attribute_definitions << attribute_definition
|
69
|
+
self.key_schema << key_schema
|
70
|
+
# TODO
|
71
|
+
# should separate that out
|
72
|
+
self.hash_keys << {
|
73
|
+
attr: attribute_definition[:attribute_name],
|
74
|
+
attr_type: attribute_definition[:attribute_type],
|
75
|
+
key_type: key_schema[:key_type],
|
76
|
+
}
|
77
|
+
@hash_key_fields << attribute_definition[:attribute_name]
|
78
|
+
end
|
79
|
+
|
80
|
+
# convert a hash key into a format as expected by
|
81
|
+
# put_item and update_item request
|
82
|
+
def as_item_entry(hash_key)
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Return true or false, depending on whether the attribute_definitions on the model
|
89
|
+
# meet the specification of the Aws Sdk
|
90
|
+
# This is a syntax, not a logic check
|
91
|
+
def attribute_definitions_meet_spec?
|
92
|
+
attribute_definitions.each do |definition|
|
93
|
+
return false unless attribute_definition_meets_spec?(definition)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def attribute_definition_meets_spec?(definition)
|
98
|
+
return (definition.has_key?(:attribute_name) && \
|
99
|
+
definition.has_key?(:attribute_type) && \
|
100
|
+
definition[:attribute_name].class == String && \
|
101
|
+
definition[:attribute_type].class == String && \
|
102
|
+
['S','N', 'B'].include?(definition[:attribute_type]))
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_attribute_definition(name, key_type)
|
106
|
+
{
|
107
|
+
attribute_name: name,
|
108
|
+
attribute_type: hash_key_type(key_type)
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def hash_key_type(key_type = nil)
|
113
|
+
return 'S' if key_type == String
|
114
|
+
return 'N' if key_type == Fixnum or key_type == Integer
|
115
|
+
return nil
|
116
|
+
end
|
117
|
+
|
118
|
+
def build_key_schema(name)
|
119
|
+
{
|
120
|
+
attribute_name: name,
|
121
|
+
key_type: 'HASH'
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
# convert values in queries to DynamoDB
|
126
|
+
# into types as expected by DynamoDB
|
127
|
+
def dyno_typed_key(key:, val:)
|
128
|
+
typed_class = self.fields[key].options[:type]
|
129
|
+
return (document_typed(klass: typed_class, value: val))
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TinyDyno
|
3
|
+
|
4
|
+
# Contains logging behaviour.
|
5
|
+
module Loggable
|
6
|
+
|
7
|
+
# Get the logger.
|
8
|
+
#
|
9
|
+
# @note Will try to grab Rails' logger first before creating a new logger
|
10
|
+
# with stdout.
|
11
|
+
#
|
12
|
+
# @example Get the logger.
|
13
|
+
# Loggable.logger
|
14
|
+
#
|
15
|
+
# @return [ Logger ] The logger.
|
16
|
+
#
|
17
|
+
# @since 3.0.0
|
18
|
+
def logger
|
19
|
+
return @logger if defined?(@logger)
|
20
|
+
@logger = rails_logger || default_logger
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set the logger.
|
24
|
+
#
|
25
|
+
# @example Set the logger.
|
26
|
+
# Loggable.logger = Logger.new($stdout)
|
27
|
+
#
|
28
|
+
# @param [ Logger ] The logger to set.
|
29
|
+
#
|
30
|
+
# @return [ Logger ] The new logger.
|
31
|
+
#
|
32
|
+
# @since 3.0.0
|
33
|
+
def logger=(logger)
|
34
|
+
@logger = logger
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Gets the default TinyDyno logger - stdout.
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
#
|
43
|
+
# @example Get the default logger.
|
44
|
+
# Loggable.default_logger
|
45
|
+
#
|
46
|
+
# @return [ Logger ] The default logger.
|
47
|
+
#
|
48
|
+
# @since 3.0.0
|
49
|
+
def default_logger
|
50
|
+
logger = Logger.new($stdout)
|
51
|
+
logger.level = Logger::INFO
|
52
|
+
logger
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get the Rails logger if it's defined.
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
#
|
59
|
+
# @example Get Rails' logger.
|
60
|
+
# Loggable.rails_logger
|
61
|
+
#
|
62
|
+
# @return [ Logger ] The Rails logger.
|
63
|
+
#
|
64
|
+
# @since 3.0.0
|
65
|
+
def rails_logger
|
66
|
+
defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module TinyDyno
|
2
|
+
module Persistable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def save(options = {})
|
6
|
+
if new_record?
|
7
|
+
request_put_item(options)
|
8
|
+
else
|
9
|
+
request_update_item(options)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def request_put_item(options)
|
16
|
+
request = build_put_item_request(options)
|
17
|
+
if TinyDyno::Adapter.put_item(put_item_request: request)
|
18
|
+
@new_record = false
|
19
|
+
@changed_attributes = {}
|
20
|
+
return true
|
21
|
+
else
|
22
|
+
return false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# The target structure as per
|
27
|
+
# http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method
|
28
|
+
#
|
29
|
+
# resp = client.put_item({
|
30
|
+
# table_name: "TableName", # required
|
31
|
+
# item: { # required
|
32
|
+
# "AttributeName" => "value", # value <Hash,Array,String,Numeric,Boolean,IO,Set,nil>
|
33
|
+
# },
|
34
|
+
# expected: {
|
35
|
+
# "AttributeName" => {
|
36
|
+
# value: "value", # value <Hash,Array,String,Numeric,Boolean,IO,Set,nil>
|
37
|
+
# exists: true,
|
38
|
+
# comparison_operator: "EQ", # accepts EQ, NE, IN, LE, LT, GE, GT, BETWEEN, NOT_NULL, NULL, CONTAINS, NOT_CONTAINS, BEGINS_WITH
|
39
|
+
# attribute_value_list: ["value"], # value <Hash,Array,String,Numeric,Boolean,IO,Set,nil>
|
40
|
+
# },
|
41
|
+
# },
|
42
|
+
# return_values: "NONE", # accepts NONE, ALL_OLD, UPDATED_OLD, ALL_NEW, UPDATED_NEW
|
43
|
+
# return_consumed_capacity: "INDEXES", # accepts INDEXES, TOTAL, NONE
|
44
|
+
# return_item_collection_metrics: "SIZE", # accepts SIZE, NONE
|
45
|
+
# conditional_operator: "AND", # accepts AND, OR
|
46
|
+
# condition_expression: "ConditionExpression",
|
47
|
+
# expression_attribute_names: {
|
48
|
+
# "ExpressionAttributeNameVariable" => "AttributeName",
|
49
|
+
# },
|
50
|
+
# expression_attribute_values: {
|
51
|
+
# "ExpressionAttributeValueVariable" => "value", # value <Hash,Array,String,Numeric,Boolean,IO,Set,nil>
|
52
|
+
# },
|
53
|
+
# })
|
54
|
+
def build_put_item_request(options)
|
55
|
+
{
|
56
|
+
table_name: self.class.table_name,
|
57
|
+
item: build_item_request_entries
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_item_request_entries
|
62
|
+
item_entries = {}
|
63
|
+
attributes.each do |k,v|
|
64
|
+
item_entries[k] = v
|
65
|
+
end
|
66
|
+
item_entries
|
67
|
+
end
|
68
|
+
|
69
|
+
def request_update_item(options)
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
module ClassMethods
|
74
|
+
|
75
|
+
# Prepare a request to be sent to the aws-sdk
|
76
|
+
# in compliance with
|
77
|
+
# http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method
|
78
|
+
|
79
|
+
def create(attributes = nil, &block)
|
80
|
+
doc = new(attributes, &block)
|
81
|
+
doc.save
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TinyDyno
|
3
|
+
|
4
|
+
# This module contains the behaviour for getting the various states a
|
5
|
+
# document can transition through.
|
6
|
+
module Stateful
|
7
|
+
|
8
|
+
attr_writer :destroyed, :flagged_for_destroy, :new_record
|
9
|
+
|
10
|
+
# Returns true if the +Document+ has not been persisted to the database,
|
11
|
+
# false if it has. This is determined by the variable @new_record
|
12
|
+
# and NOT if the object has an id.
|
13
|
+
#
|
14
|
+
# @example Is the document new?
|
15
|
+
# person.new_record?
|
16
|
+
#
|
17
|
+
# @return [ true, false ] True if new, false if not.
|
18
|
+
def new_record?
|
19
|
+
@new_record ||= false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Checks if the document has been saved to the database. Returns false
|
23
|
+
# if the document has been destroyed.
|
24
|
+
#
|
25
|
+
# @example Is the document persisted?
|
26
|
+
# person.persisted?
|
27
|
+
#
|
28
|
+
# @return [ true, false ] True if persisted, false if not.
|
29
|
+
def persisted?
|
30
|
+
!new_record? && !destroyed?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns whether or not the document has been flagged for deletion, but
|
34
|
+
# not destroyed yet. Used for atomic pulls of child documents.
|
35
|
+
#
|
36
|
+
# @example Is the document flagged?
|
37
|
+
# document.flagged_for_destroy?
|
38
|
+
#
|
39
|
+
# @return [ true, false ] If the document is flagged.
|
40
|
+
#
|
41
|
+
# @since 2.3.2
|
42
|
+
def flagged_for_destroy?
|
43
|
+
@flagged_for_destroy ||= false
|
44
|
+
end
|
45
|
+
alias :marked_for_destruction? :flagged_for_destroy?
|
46
|
+
|
47
|
+
# Returns true if the +Document+ has been succesfully destroyed, and false
|
48
|
+
# if it hasn't. This is determined by the variable @destroyed and NOT
|
49
|
+
# by checking the database.
|
50
|
+
#
|
51
|
+
# @example Is the document destroyed?
|
52
|
+
# person.destroyed?
|
53
|
+
#
|
54
|
+
# @return [ true, false ] True if destroyed, false if not.
|
55
|
+
def destroyed?
|
56
|
+
@destroyed ||= false
|
57
|
+
end
|
58
|
+
|
59
|
+
# Is the document readonly?
|
60
|
+
#
|
61
|
+
# @example Is the document readonly?
|
62
|
+
# document.readonly?
|
63
|
+
#
|
64
|
+
# @return [ true, false ] If the document is readonly.
|
65
|
+
#
|
66
|
+
# @since 4.0.0
|
67
|
+
def readonly?
|
68
|
+
__selected_fields != nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Determine if the document can be set.
|
72
|
+
#
|
73
|
+
# @example Is this settable?
|
74
|
+
# person.settable?
|
75
|
+
#
|
76
|
+
# @return [ true, false ] Is this document a new embeds one?
|
77
|
+
#
|
78
|
+
# @since 2.1.0
|
79
|
+
def settable?
|
80
|
+
new_record?
|
81
|
+
end
|
82
|
+
|
83
|
+
# Is the document updateable?
|
84
|
+
#
|
85
|
+
# @example Is the document updateable?
|
86
|
+
# person.updateable?
|
87
|
+
#
|
88
|
+
# @return [ true, false ] If the document is changed and persisted.
|
89
|
+
#
|
90
|
+
# @since 2.1.0
|
91
|
+
def updateable?
|
92
|
+
persisted? && changed?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|