adept_dynamoid 0.5.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Dynamoid.gemspec +193 -0
  4. data/Gemfile +23 -0
  5. data/Gemfile.lock +86 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.markdown +265 -0
  8. data/Rakefile +62 -0
  9. data/VERSION +1 -0
  10. data/doc/.nojekyll +0 -0
  11. data/doc/Dynamoid.html +312 -0
  12. data/doc/Dynamoid/Adapter.html +1385 -0
  13. data/doc/Dynamoid/Adapter/AwsSdk.html +1585 -0
  14. data/doc/Dynamoid/Adapter/Local.html +1574 -0
  15. data/doc/Dynamoid/Associations.html +131 -0
  16. data/doc/Dynamoid/Associations/Association.html +794 -0
  17. data/doc/Dynamoid/Associations/BelongsTo.html +158 -0
  18. data/doc/Dynamoid/Associations/ClassMethods.html +723 -0
  19. data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +164 -0
  20. data/doc/Dynamoid/Associations/HasMany.html +164 -0
  21. data/doc/Dynamoid/Associations/HasOne.html +158 -0
  22. data/doc/Dynamoid/Associations/ManyAssociation.html +1640 -0
  23. data/doc/Dynamoid/Associations/SingleAssociation.html +598 -0
  24. data/doc/Dynamoid/Components.html +204 -0
  25. data/doc/Dynamoid/Config.html +395 -0
  26. data/doc/Dynamoid/Config/Options.html +609 -0
  27. data/doc/Dynamoid/Criteria.html +131 -0
  28. data/doc/Dynamoid/Criteria/Chain.html +1063 -0
  29. data/doc/Dynamoid/Criteria/ClassMethods.html +98 -0
  30. data/doc/Dynamoid/Document.html +666 -0
  31. data/doc/Dynamoid/Document/ClassMethods.html +937 -0
  32. data/doc/Dynamoid/Errors.html +118 -0
  33. data/doc/Dynamoid/Errors/DocumentNotValid.html +210 -0
  34. data/doc/Dynamoid/Errors/Error.html +130 -0
  35. data/doc/Dynamoid/Errors/InvalidField.html +133 -0
  36. data/doc/Dynamoid/Errors/MissingRangeKey.html +133 -0
  37. data/doc/Dynamoid/Fields.html +669 -0
  38. data/doc/Dynamoid/Fields/ClassMethods.html +309 -0
  39. data/doc/Dynamoid/Finders.html +128 -0
  40. data/doc/Dynamoid/Finders/ClassMethods.html +516 -0
  41. data/doc/Dynamoid/Indexes.html +308 -0
  42. data/doc/Dynamoid/Indexes/ClassMethods.html +353 -0
  43. data/doc/Dynamoid/Indexes/Index.html +1104 -0
  44. data/doc/Dynamoid/Persistence.html +651 -0
  45. data/doc/Dynamoid/Persistence/ClassMethods.html +670 -0
  46. data/doc/Dynamoid/Validations.html +399 -0
  47. data/doc/_index.html +461 -0
  48. data/doc/class_list.html +47 -0
  49. data/doc/css/common.css +1 -0
  50. data/doc/css/full_list.css +55 -0
  51. data/doc/css/style.css +322 -0
  52. data/doc/file.LICENSE.html +66 -0
  53. data/doc/file.README.html +312 -0
  54. data/doc/file_list.html +52 -0
  55. data/doc/frames.html +13 -0
  56. data/doc/index.html +312 -0
  57. data/doc/js/app.js +205 -0
  58. data/doc/js/full_list.js +173 -0
  59. data/doc/js/jquery.js +16 -0
  60. data/doc/method_list.html +1238 -0
  61. data/doc/top-level-namespace.html +105 -0
  62. data/lib/dynamoid.rb +47 -0
  63. data/lib/dynamoid/adapter.rb +177 -0
  64. data/lib/dynamoid/adapter/aws_sdk.rb +223 -0
  65. data/lib/dynamoid/associations.rb +106 -0
  66. data/lib/dynamoid/associations/association.rb +105 -0
  67. data/lib/dynamoid/associations/belongs_to.rb +44 -0
  68. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +40 -0
  69. data/lib/dynamoid/associations/has_many.rb +39 -0
  70. data/lib/dynamoid/associations/has_one.rb +39 -0
  71. data/lib/dynamoid/associations/many_association.rb +191 -0
  72. data/lib/dynamoid/associations/single_association.rb +69 -0
  73. data/lib/dynamoid/components.rb +36 -0
  74. data/lib/dynamoid/config.rb +57 -0
  75. data/lib/dynamoid/config/options.rb +78 -0
  76. data/lib/dynamoid/criteria.rb +29 -0
  77. data/lib/dynamoid/criteria/chain.rb +243 -0
  78. data/lib/dynamoid/dirty.rb +41 -0
  79. data/lib/dynamoid/document.rb +184 -0
  80. data/lib/dynamoid/errors.rb +28 -0
  81. data/lib/dynamoid/fields.rb +130 -0
  82. data/lib/dynamoid/finders.rb +131 -0
  83. data/lib/dynamoid/identity_map.rb +96 -0
  84. data/lib/dynamoid/indexes.rb +69 -0
  85. data/lib/dynamoid/indexes/index.rb +103 -0
  86. data/lib/dynamoid/middleware/identity_map.rb +16 -0
  87. data/lib/dynamoid/persistence.rb +247 -0
  88. data/lib/dynamoid/validations.rb +36 -0
  89. data/spec/app/models/address.rb +10 -0
  90. data/spec/app/models/camel_case.rb +24 -0
  91. data/spec/app/models/magazine.rb +11 -0
  92. data/spec/app/models/message.rb +9 -0
  93. data/spec/app/models/sponsor.rb +8 -0
  94. data/spec/app/models/subscription.rb +12 -0
  95. data/spec/app/models/tweet.rb +12 -0
  96. data/spec/app/models/user.rb +26 -0
  97. data/spec/dynamoid/adapter/aws_sdk_spec.rb +186 -0
  98. data/spec/dynamoid/adapter_spec.rb +117 -0
  99. data/spec/dynamoid/associations/association_spec.rb +194 -0
  100. data/spec/dynamoid/associations/belongs_to_spec.rb +71 -0
  101. data/spec/dynamoid/associations/has_and_belongs_to_many_spec.rb +47 -0
  102. data/spec/dynamoid/associations/has_many_spec.rb +42 -0
  103. data/spec/dynamoid/associations/has_one_spec.rb +45 -0
  104. data/spec/dynamoid/associations_spec.rb +16 -0
  105. data/spec/dynamoid/config_spec.rb +27 -0
  106. data/spec/dynamoid/criteria/chain_spec.rb +140 -0
  107. data/spec/dynamoid/criteria_spec.rb +72 -0
  108. data/spec/dynamoid/dirty_spec.rb +49 -0
  109. data/spec/dynamoid/document_spec.rb +118 -0
  110. data/spec/dynamoid/fields_spec.rb +127 -0
  111. data/spec/dynamoid/finders_spec.rb +135 -0
  112. data/spec/dynamoid/identity_map_spec.rb +45 -0
  113. data/spec/dynamoid/indexes/index_spec.rb +104 -0
  114. data/spec/dynamoid/indexes_spec.rb +25 -0
  115. data/spec/dynamoid/persistence_spec.rb +176 -0
  116. data/spec/dynamoid/validations_spec.rb +36 -0
  117. data/spec/dynamoid_spec.rb +9 -0
  118. data/spec/spec_helper.rb +50 -0
  119. metadata +376 -0
@@ -0,0 +1,41 @@
1
+ module Dynamoid
2
+ module Dirty
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Dirty
5
+
6
+ module ClassMethods
7
+ def from_database(*)
8
+ super.tap { |d| d.changed_attributes.clear }
9
+ end
10
+ end
11
+
12
+ def save(*)
13
+ clear_changes { super }
14
+ end
15
+
16
+ def reload
17
+ super.tap { clear_changes }
18
+ end
19
+
20
+ def clear_changes
21
+ previous = changes
22
+ (block_given? ? yield : true).tap do |result|
23
+ unless result == false #failed validation; nil is OK.
24
+ @previously_changed = previous
25
+ changed_attributes.clear
26
+ end
27
+ end
28
+ end
29
+
30
+ def write_attribute(name, value)
31
+ attribute_will_change!(name) unless self.read_attribute(name) == value
32
+ super
33
+ end
34
+
35
+ protected
36
+
37
+ def attribute_method?(attr)
38
+ super || self.class.attributes.has_key?(attr.to_sym)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,184 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # This is the base module for all domain objects that need to be persisted to
5
+ # the database as documents.
6
+ module Document
7
+ extend ActiveSupport::Concern
8
+ include Dynamoid::Components
9
+
10
+ included do
11
+ class_attribute :options, :read_only_attributes
12
+ self.options = {}
13
+ self.read_only_attributes = []
14
+
15
+ Dynamoid::Config.included_models << self
16
+ end
17
+
18
+ module ClassMethods
19
+ # Set up table options, including naming it whatever you want, setting the id key, and manually overriding read and
20
+ # write capacity.
21
+ #
22
+ # @param [Hash] options options to pass for this table
23
+ # @option options [Symbol] :name the name for the table; this still gets namespaced
24
+ # @option options [Symbol] :id id column for the table
25
+ # @option options [Integer] :read_capacity set the read capacity for the table; does not work on existing tables
26
+ # @option options [Integer] :write_capacity set the write capacity for the table; does not work on existing tables
27
+ #
28
+ # @since 0.4.0
29
+ def table(options = {})
30
+ self.options = options
31
+ end
32
+
33
+ def attr_readonly(*read_only_attributes)
34
+ self.read_only_attributes.concat read_only_attributes.map(&:to_s)
35
+ end
36
+
37
+ # Returns the read_capacity for this table.
38
+ #
39
+ # @since 0.4.0
40
+ def read_capacity
41
+ options[:read_capacity] || Dynamoid::Config.read_capacity
42
+ end
43
+
44
+ # Returns the write_capacity for this table.
45
+ #
46
+ # @since 0.4.0
47
+ def write_capacity
48
+ options[:write_capacity] || Dynamoid::Config.write_capacity
49
+ end
50
+
51
+ # Returns the id field for this class.
52
+ #
53
+ # @since 0.4.0
54
+ def hash_key
55
+ options[:key] || :id
56
+ end
57
+
58
+ # Initialize a new object and immediately save it to the database.
59
+ #
60
+ # @param [Hash] attrs Attributes with which to create the object.
61
+ #
62
+ # @return [Dynamoid::Document] the saved document
63
+ #
64
+ # @since 0.2.0
65
+ def create(attrs = {})
66
+ new(attrs).tap(&:save)
67
+ end
68
+
69
+ # Initialize a new object and immediately save it to the database. Raise an exception if persistence failed.
70
+ #
71
+ # @param [Hash] attrs Attributes with which to create the object.
72
+ #
73
+ # @return [Dynamoid::Document] the saved document
74
+ #
75
+ # @since 0.2.0
76
+ def create!(attrs = {})
77
+ new(attrs).tap(&:save!)
78
+ end
79
+
80
+ # Initialize a new object.
81
+ #
82
+ # @param [Hash] attrs Attributes with which to create the object.
83
+ #
84
+ # @return [Dynamoid::Document] the new document
85
+ #
86
+ # @since 0.2.0
87
+ def build(attrs = {})
88
+ new(attrs)
89
+ end
90
+
91
+ # Does this object exist?
92
+ #
93
+ # @param [String] id the id of the object
94
+ #
95
+ # @return [Boolean] true/false
96
+ #
97
+ # @since 0.2.0
98
+ def exists?(id)
99
+ !! find(id)
100
+ end
101
+ end
102
+
103
+ # Initialize a new object.
104
+ #
105
+ # @param [Hash] attrs Attributes with which to create the object.
106
+ #
107
+ # @return [Dynamoid::Document] the new document
108
+ #
109
+ # @since 0.2.0
110
+ def initialize(attrs = {})
111
+ run_callbacks :initialize do
112
+ self.class.send(:field, self.class.hash_key) unless self.respond_to?(self.class.hash_key)
113
+
114
+ @new_record = true
115
+ @attributes ||= {}
116
+ @associations ||= {}
117
+
118
+ load(attrs)
119
+ end
120
+ end
121
+
122
+ def load(attrs)
123
+ self.class.undump(attrs).each {|key, value| send "#{key}=", value }
124
+ end
125
+
126
+ # An object is equal to another object if their ids are equal.
127
+ #
128
+ # @since 0.2.0
129
+ def ==(other)
130
+ if self.class.identity_map_on?
131
+ super
132
+ else
133
+ return false if other.nil?
134
+ other.respond_to?(:hash_key) && other.hash_key == self.hash_key
135
+ end
136
+ end
137
+
138
+ # Reload an object from the database -- if you suspect the object has changed in the datastore and you need those
139
+ # changes to be reflected immediately, you would call this method.
140
+ #
141
+ # @return [Dynamoid::Document] the document this method was called on
142
+ #
143
+ # @since 0.2.0
144
+ def reload
145
+ self.attributes = self.class.find(hash_key, :range_key => range_value).attributes
146
+ @associations.values.each(&:reset)
147
+ self
148
+ end
149
+
150
+ # Return an object's hash key, regardless of what it might be called to the object.
151
+ #
152
+ # @since 0.4.0
153
+ def hash_key
154
+ self.send(self.class.hash_key)
155
+ end
156
+
157
+ # Assign an object's hash key, regardless of what it might be called to the object.
158
+ #
159
+ # @since 0.4.0
160
+ def hash_key=(value)
161
+ self.send("#{self.class.hash_key}=", value)
162
+ end
163
+
164
+ def range_value
165
+ if range_key = self.class.range_key
166
+ self.send(range_key)
167
+ end
168
+ end
169
+
170
+ def range_value=(value)
171
+ self.send("#{self.class.range_key}=", value)
172
+ end
173
+
174
+ def range_value
175
+ if range_key = self.class.range_key
176
+ self.send(range_key)
177
+ end
178
+ end
179
+
180
+ def range_value=(value)
181
+ self.send("#{self.class.range_key}=", value)
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ module Dynamoid
3
+
4
+ # All the error specific to Dynamoid.
5
+ module Errors
6
+
7
+ # Generic error class.
8
+ class Error < StandardError; end
9
+
10
+ # InvalidField is raised when an attribute is specified for an index, but the attribute does not exist.
11
+ class InvalidField < Error; end
12
+
13
+ # MissingRangeKey is raised when a table that requires a range key is quieried without one.
14
+ class MissingRangeKey < Error; end
15
+
16
+ # raised when the conditional check failed during update operation
17
+ class ConditionalCheckFailedException < Error; end
18
+
19
+ # DocumentNotValid is raised when the document fails validation.
20
+ class DocumentNotValid < Error
21
+ def initialize(document)
22
+ super("Validation failed: #{document.errors.full_messages.join(", ")}")
23
+ end
24
+ end
25
+
26
+ class InvalidQuery < Error; end
27
+ end
28
+ end
@@ -0,0 +1,130 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # All fields on a Dynamoid::Document must be explicitly defined -- if you have fields in the database that are not
5
+ # specified with field, then they will be ignored.
6
+ module Fields
7
+ extend ActiveSupport::Concern
8
+
9
+ # Initialize the attributes we know the class has, in addition to our magic attributes: id, created_at, and updated_at.
10
+ included do
11
+ class_attribute :attributes
12
+ class_attribute :range_key
13
+
14
+ self.attributes = {}
15
+ field :created_at, :datetime
16
+ field :updated_at, :datetime
17
+ end
18
+
19
+ module ClassMethods
20
+
21
+ # Specify a field for a document. Its type determines how it is coerced when read in and out of the datastore:
22
+ # default is string, but you can also specify :integer, :float, :set, :array, :datetime, and :serialized.
23
+ #
24
+ # @param [Symbol] name the name of the field
25
+ # @param [Symbol] type the type of the field (one of :integer, :float, :set, :array, :datetime, or :serialized)
26
+ # @param [Hash] options any additional options for the field
27
+ #
28
+ # @since 0.2.0
29
+ def field(name, type = :string, options = {})
30
+ named = name.to_s
31
+ self.attributes[name] = {:type => type}.merge(options)
32
+
33
+ define_method(named) { read_attribute(named) }
34
+ define_method("#{named}?") { !read_attribute(named).nil? }
35
+ define_method("#{named}=") {|value| write_attribute(named, value) }
36
+ end
37
+
38
+ def range(name, type = :string)
39
+ field(name, type)
40
+ self.range_key = name
41
+ end
42
+ end
43
+
44
+ # You can access the attributes of an object directly on its attributes method, which is by default an empty hash.
45
+ attr_accessor :attributes
46
+ alias :raw_attributes :attributes
47
+
48
+ # Write an attribute on the object. Also marks the previous value as dirty.
49
+ #
50
+ # @param [Symbol] name the name of the field
51
+ # @param [Object] value the value to assign to that field
52
+ #
53
+ # @since 0.2.0
54
+ def write_attribute(name, value)
55
+ if (size = value.to_s.size) > MAX_ITEM_SIZE
56
+ Dynamoid.logger.warn "DynamoDB can't store items larger than #{MAX_ITEM_SIZE} and the #{name} field has a length of #{size}."
57
+ end
58
+
59
+ if association = @associations[name]
60
+ association.reset
61
+ end
62
+
63
+ attributes[name.to_sym] = value
64
+ end
65
+ alias :[]= :write_attribute
66
+
67
+ # Read an attribute from an object.
68
+ #
69
+ # @param [Symbol] name the name of the field
70
+ #
71
+ # @since 0.2.0
72
+ def read_attribute(name)
73
+ attributes[name.to_sym]
74
+ end
75
+ alias :[] :read_attribute
76
+
77
+ # Updates multiple attibutes at once, saving the object once the updates are complete.
78
+ #
79
+ # @param [Hash] attributes a hash of attributes to update
80
+ #
81
+ # @since 0.2.0
82
+ def update_attributes(attributes)
83
+ attributes.each {|attribute, value| self.write_attribute(attribute, value)}
84
+ if self.new_record # if never saved save.
85
+ save
86
+ else # update attributes if we have saved.
87
+ # next if self.read_only_attributes.include? attribute.to_s put this back in.
88
+ run_callbacks(:save) do
89
+ update! do |u|
90
+ attributes.each do |attribute, value|
91
+ u.set attribute => dump_field(
92
+ self.read_attribute(attribute),
93
+ self.class.attributes[attribute.to_sym]
94
+ )
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ # Update a single attribute, saving the object afterwards.
102
+ #
103
+ # @param [Symbol] attribute the attribute to update
104
+ # @param [Object] value the value to assign it
105
+ #
106
+ # @since 0.2.0
107
+ def update_attribute(attribute, value)
108
+ write_attribute(attribute, value)
109
+ save
110
+ end
111
+
112
+ private
113
+
114
+ # Automatically called during the created callback to set the created_at time.
115
+ #
116
+ # @since 0.2.0
117
+ def set_created_at
118
+ self.created_at = DateTime.now
119
+ end
120
+
121
+ # Automatically called during the save callback to set the updated_at time.
122
+ #
123
+ # @since 0.2.0
124
+ def set_updated_at
125
+ self.updated_at = DateTime.now
126
+ end
127
+
128
+ end
129
+
130
+ end
@@ -0,0 +1,131 @@
1
+ # encoding: utf-8
2
+ module Dynamoid
3
+
4
+ # This module defines the finder methods that hang off the document at the
5
+ # class level, like find, find_by_id, and the method_missing style finders.
6
+ module Finders
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+
11
+ # Find one or many objects, specified by one id or an array of ids.
12
+ #
13
+ # @param [Array/String] *id an array of ids or one single id
14
+ #
15
+ # @return [Dynamoid::Document] one object or an array of objects, depending on whether the input was an array or not
16
+ #
17
+ # @since 0.2.0
18
+ def find(*ids)
19
+
20
+ options = if ids.last.is_a? Hash
21
+ ids.slice!(-1)
22
+ else
23
+ {}
24
+ end
25
+
26
+ ids = Array(ids.flatten.uniq)
27
+ if ids.count == 1
28
+ self.find_by_id(ids.first, options)
29
+ else
30
+ find_all(ids)
31
+ end
32
+ end
33
+
34
+ # Find all object by hash key or hash and range key
35
+ #
36
+ # @param [Array<ID>] ids
37
+ #
38
+ # @example
39
+ # find all the user with hash key
40
+ # User.find_all(['1', '2', '3'])
41
+ #
42
+ # find all the tweets using hash key and range key
43
+ # Tweet.find_all([['1', 'red'], ['1', 'green'])
44
+ def find_all(ids)
45
+ items = Dynamoid::Adapter.read(self.table_name, ids, options)
46
+ items[self.table_name].collect{|i| from_database(i) }
47
+ end
48
+
49
+ # Find one object directly by id.
50
+ #
51
+ # @param [String] id the id of the object to find
52
+ #
53
+ # @return [Dynamoid::Document] the found object, or nil if nothing was found
54
+ #
55
+ # @since 0.2.0
56
+ def find_by_id(id, options = {})
57
+ if item = Dynamoid::Adapter.read(self.table_name, id, options)
58
+ from_database(item)
59
+ else
60
+ nil
61
+ end
62
+ end
63
+
64
+ # Find one object directly by hash and range keys
65
+ #
66
+ # @param [String] hash_key of the object to find
67
+ # @param [String/Integer/Float] range_key of the object to find
68
+ #
69
+ def find_by_composite_key(hash_key, range_key, options = {})
70
+ find_by_id(hash_key, options.merge({:range_key => range_key}))
71
+ end
72
+
73
+ # Find all objects by hash and range keys.
74
+ #
75
+ # @example find all ChamberTypes whose level is greater than 1
76
+ # class ChamberType
77
+ # include Dynamoid::Document
78
+ # field :chamber_type, :string
79
+ # range :level, :integer
80
+ # table :key => :chamber_type
81
+ # end
82
+ # ChamberType.find_all_by_composite_key('DustVault', range_greater_than: 1)
83
+ #
84
+ # @param [String] hash_key of the objects to find
85
+ # @param [Hash] options the options for the range key
86
+ # @option options [Range] :range_value find the range key within this range
87
+ # @option options [Number] :range_greater_than find range keys greater than this
88
+ # @option options [Number] :range_less_than find range keys less than this
89
+ # @option options [Number] :range_gte find range keys greater than or equal to this
90
+ # @option options [Number] :range_lte find range keys less than or equal to this
91
+ #
92
+ # @return [Array] an array of all matching items
93
+ #
94
+ def find_all_by_composite_key(hash_key, options = {})
95
+ Dynamoid::Adapter.query(self.table_name, options.merge({hash_value: hash_key})).collect do |item|
96
+ from_database(item)
97
+ end
98
+ end
99
+
100
+ # Find using exciting method_missing finders attributes. Uses criteria chains under the hood to accomplish this neatness.
101
+ #
102
+ # @example find a user by a first name
103
+ # User.find_by_first_name('Josh')
104
+ #
105
+ # @example find all users by first and last name
106
+ # User.find_all_by_first_name_and_last_name('Josh', 'Symonds')
107
+ #
108
+ # @return [Dynamoid::Document/Array] the found object, or an array of found objects if all was somewhere in the method
109
+ #
110
+ # @since 0.2.0
111
+ def method_missing(method, *args)
112
+ if method =~ /find/
113
+ finder = method.to_s.split('_by_').first
114
+ attributes = method.to_s.split('_by_').last.split('_and_')
115
+
116
+ chain = Dynamoid::Criteria::Chain.new(self)
117
+ chain.query = Hash.new.tap {|h| attributes.each_with_index {|attr, index| h[attr.to_sym] = args[index]}}
118
+
119
+ if finder =~ /all/
120
+ return chain.all
121
+ else
122
+ return chain.first
123
+ end
124
+ else
125
+ super
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ end