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.
@@ -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,15 @@
1
+ module TinyDyno
2
+ module RangeAttributes
3
+ extend ActiveSupport::Concern
4
+
5
+ attr_reader :range_attributes
6
+
7
+ included do
8
+ class_attribute :range_attributes
9
+
10
+ self.range_attributes = []
11
+
12
+ end
13
+
14
+ end
15
+ 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