tiny_dyno 0.1.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 +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
|