tiny_dyno 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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