rod 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/.travis.yml +1 -1
  2. data/README.rdoc +38 -10
  3. data/Rakefile +20 -9
  4. data/changelog.txt +25 -0
  5. data/contributors.txt +1 -0
  6. data/data/backward/0.7.0/_join_element.dat +0 -0
  7. data/data/backward/0.7.0/_polymorphic_join_element.dat +0 -0
  8. data/data/backward/0.7.0/char.dat +0 -0
  9. data/data/backward/0.7.0/database.yml +58 -0
  10. data/data/backward/0.7.0/rod_test__automobile.dat +0 -0
  11. data/data/backward/0.7.0/rod_test__caveman.dat +0 -0
  12. data/data/backward/0.7.0/rod_test__dog.dat +0 -0
  13. data/data/backward/0.7.0/rod_test__test_model.dat +0 -0
  14. data/data/portability/_join_element.dat +0 -0
  15. data/data/portability/_polymorphic_join_element.dat +0 -0
  16. data/data/portability/char.dat +0 -0
  17. data/data/portability/database.yml +49 -0
  18. data/data/portability/rod_test__automobile.dat +0 -0
  19. data/data/portability/rod_test__caveman.dat +0 -0
  20. data/data/portability/rod_test__dog.dat +0 -0
  21. data/data/portability/rod_test__test_model.dat +0 -0
  22. data/features/backward.feature +33 -0
  23. data/features/basic.feature +3 -0
  24. data/features/collection_proxy.feature +95 -0
  25. data/features/flat_indexing.feature +44 -2
  26. data/features/hash_indexing.feature +63 -9
  27. data/features/portability.feature +72 -0
  28. data/features/segmented_indexing.feature +45 -2
  29. data/features/steps/collection_proxy.rb +1 -1
  30. data/features/steps/model.rb +48 -5
  31. data/features/steps/rod.rb +15 -16
  32. data/lib/rod.rb +11 -1
  33. data/lib/rod/abstract_database.rb +52 -42
  34. data/lib/rod/berkeley/collection_proxy.rb +96 -0
  35. data/lib/rod/berkeley/database.rb +337 -0
  36. data/lib/rod/berkeley/environment.rb +209 -0
  37. data/lib/rod/berkeley/sequence.rb +222 -0
  38. data/lib/rod/berkeley/transaction.rb +233 -0
  39. data/lib/rod/collection_proxy.rb +76 -1
  40. data/lib/rod/constants.rb +3 -2
  41. data/lib/rod/database.rb +127 -14
  42. data/lib/rod/index/base.rb +12 -3
  43. data/lib/rod/index/hash_index.rb +295 -70
  44. data/lib/rod/index/segmented_index.rb +3 -0
  45. data/lib/rod/model.rb +154 -531
  46. data/lib/rod/property/base.rb +190 -0
  47. data/lib/rod/property/field.rb +258 -0
  48. data/lib/rod/property/plural_association.rb +145 -0
  49. data/lib/rod/property/singular_association.rb +139 -0
  50. data/rod.gemspec +6 -4
  51. data/spec/berkeley/database.rb +83 -0
  52. data/spec/berkeley/environment.rb +58 -0
  53. data/spec/berkeley/sequence.rb +101 -0
  54. data/spec/berkeley/transaction.rb +92 -0
  55. data/spec/collection_proxy.rb +38 -0
  56. data/spec/database.rb +36 -0
  57. data/spec/model.rb +26 -0
  58. data/spec/property/base.rb +73 -0
  59. data/spec/property/field.rb +244 -0
  60. data/spec/property/plural_association.rb +67 -0
  61. data/spec/property/singular_association.rb +65 -0
  62. data/tests/class_compatibility_create.rb +2 -2
  63. data/tests/eff1_test.rb +1 -1
  64. data/tests/eff2_test.rb +1 -1
  65. data/tests/full_runs.rb +1 -1
  66. data/tests/generate_classes_create.rb +14 -14
  67. data/tests/migration_create.rb +47 -47
  68. data/tests/migration_verify.rb +1 -1
  69. data/tests/missing_class_create.rb +6 -6
  70. data/tests/properties_order_create.rb +4 -4
  71. data/tests/read_on_create.rb +33 -34
  72. data/tests/save_struct.rb +40 -39
  73. data/tests/unit/database.rb +1 -1
  74. data/tests/unit/model_tests.rb +73 -65
  75. metadata +71 -15
  76. data/tests/unit/model.rb +0 -36
@@ -0,0 +1,190 @@
1
+ require 'rod/exception'
2
+ require 'rod/constants'
3
+
4
+ module Rod
5
+ module Property
6
+ # This class defines the properties which are used in Rod::Model.
7
+ # These might be:
8
+ # * fields
9
+ # * has_one associations
10
+ # * has_many associations
11
+ # It provides basic data concerning these properties, such as name,
12
+ # options, etc.
13
+ class Base
14
+ # The name of the property.
15
+ attr_reader :name
16
+
17
+ # The options of the property.
18
+ attr_reader :options
19
+
20
+ # Initializes the property associated with +klass+
21
+ # with its +name+ and +options+.
22
+ def initialize(klass,name,options)
23
+ check_class(klass)
24
+ @klass = klass
25
+ check_name(name)
26
+ @name = name
27
+ check_options(options)
28
+ @options = options.dup.freeze
29
+ end
30
+
31
+ # Checks the difference in options between this and the +other+
32
+ # property. The prefix of legacy module is removed from the
33
+ # values of the options.
34
+ def difference(other)
35
+ self_options = {}
36
+ self.options.each{|k,v| self_options[k] = v.to_s.sub(LEGACY_RE,"")}
37
+ other_options = {}
38
+ other.options.each{|k,v| other_options[k] = v.to_s.sub(LEGACY_RE,"")}
39
+ differences = {}
40
+ self_options.each do |option,value|
41
+ if other_options[option] != value
42
+ differences[option] = [value,other_options[option]]
43
+ end
44
+ end
45
+ other_options.each do |option,value|
46
+ if self_options[option] != value && !differences.has_key?(option)
47
+ differences[option] = [self_options[option],value]
48
+ end
49
+ end
50
+ differences
51
+ end
52
+
53
+ # Returns the index associated with the property.
54
+ def index
55
+ @index ||= Index::Base.create(path(@klass.database.path),@klass,@options)
56
+ end
57
+
58
+ # Returns true if the property has an index.
59
+ def has_index?
60
+ !@options[:index].nil?
61
+ end
62
+
63
+ # Get rid of the index that is associated with this property.
64
+ def reset_index
65
+ @index = nil
66
+ end
67
+
68
+ # Creates a copy of the property with a new +klass+.
69
+ def copy(klass)
70
+ self.class.new(klass,@name,@options)
71
+ end
72
+
73
+ # Defines finders (+find_by+ and +find_all_by+) for indexed property.
74
+ def define_finders
75
+ return unless has_index?
76
+ # optimization
77
+ name = @name.to_s
78
+ property = self
79
+ (class << @klass; self; end).class_eval do
80
+ # Find all objects with given +value+ of the +property+.
81
+ define_method("find_all_by_#{name}") do |value|
82
+ property.index[value]
83
+ end
84
+
85
+ # Find first object with given +value+ of the +property+.
86
+ define_method("find_by_#{name}") do |value|
87
+ property.index[value][0]
88
+ end
89
+ end
90
+ end
91
+
92
+ protected
93
+ # The name of the file or directory (for given +relative_path+), where
94
+ # the data of the property (e.g. index) is stored.
95
+ def path(relative_path)
96
+ "#{relative_path}#{@klass.model_path}_#{@name}"
97
+ end
98
+
99
+ # Checks if the property +name+ is valid.
100
+ def check_name(name)
101
+ if !name.is_a?(Symbol) || name.to_s.empty? || INVALID_NAMES.has_key?(name)
102
+ raise InvalidArgument.new(name,"property name")
103
+ end
104
+ end
105
+
106
+ # Checks if the +klass+ is valid class for the property.
107
+ def check_class(klass)
108
+ raise InvalidArgument.new(klass,"class") if klass.nil?
109
+ end
110
+
111
+ # Reads the value of a field +name+ of the C struct +struct_name+
112
+ # that corresponds to given Ruby object. The C +result_type+
113
+ # of the result has to be specified.
114
+ def field_reader(name,struct_name,result_type,builder)
115
+ str =<<-END
116
+ |#{result_type} _#{name}(unsigned long object_rod_id){
117
+ | VALUE klass;
118
+ | #{struct_name} * pointer;
119
+ | uint64_t as_uint;
120
+ | uint64_t result_swapped;
121
+ |
122
+ | if(object_rod_id == 0){
123
+ | rb_raise(rodException(), "Invalid object rod_id (0)");
124
+ | }
125
+ | klass = rb_funcall(self,rb_intern("class"),0);
126
+ | pointer = (#{struct_name} *)
127
+ | NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
128
+ |#ifdef __BYTE_ORDER
129
+ |# if __BYTE_ORDER == __BIG_ENDIAN
130
+ | // This code assumes that all values are 64 bit wide. This is not true
131
+ | // on 32-bit systems but is addressed in #221
132
+ | as_uint = (pointer + object_rod_id - 1)->#{name};
133
+ | result_swapped = bswap_64(*((uint64_t *)((char *)&as_uint)));
134
+ | return *(#{result_type} *)((char *)&result_swapped);
135
+ |# else
136
+ | return (pointer + object_rod_id - 1)->#{name};
137
+ |# endif
138
+ |#else
139
+ | return (pointer + object_rod_id - 1)->#{name};
140
+ |#endif
141
+ |}
142
+ END
143
+ builder.c(str.margin)
144
+ end
145
+
146
+ # Writes the value of a field +name+ of the C struct +struct_name+
147
+ # that corresponds to given Ruby object. The C +arg_type+
148
+ # of the argument has to be specified.
149
+ def field_writer(name,struct_name,arg_type,builder)
150
+ str =<<-END
151
+ |void _#{name}_equals(unsigned long object_rod_id,#{arg_type} value){
152
+ | VALUE klass;
153
+ | #{struct_name} * pointer;
154
+ | uint64_t value_swapped;
155
+ |
156
+ | if(object_rod_id == 0){
157
+ | rb_raise(rodException(), "Invalid object rod_id (0)");
158
+ | }
159
+ | klass = rb_funcall(self,rb_intern("class"),0);
160
+ | pointer = (#{struct_name} *)
161
+ | NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
162
+ |#ifdef __BYTE_ORDER
163
+ |# if __BYTE_ORDER == __BIG_ENDIAN
164
+ | // TODO #220 #221
165
+ | value_swapped = bswap_64(*((uint64_t *)((char *)&value)));
166
+ | (pointer + object_rod_id - 1)->#{name} = value_swapped;
167
+ |# else
168
+ | (pointer + object_rod_id - 1)->#{name} = value;
169
+ |# endif
170
+ |#else
171
+ | (pointer + object_rod_id - 1)->#{name} = value;
172
+ |#endif
173
+ |}
174
+ END
175
+ builder.c(str.margin)
176
+ end
177
+
178
+ # Returns the size of the C type.
179
+ def sizeof(type)
180
+ # TODO implement
181
+ 0
182
+ end
183
+
184
+ # Returns the C type for given Rod type.
185
+ def c_type(type)
186
+ TYPE_MAPPING[type]
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,258 @@
1
+ require 'rod/property/base'
2
+ require 'rod/constants'
3
+ require 'json'
4
+
5
+ module Rod
6
+ module Property
7
+ # This class defines the field property.
8
+ # A field has to define its +name+ and its +type+.
9
+ class Field < Base
10
+ # The type of the property.
11
+ attr_reader :type
12
+
13
+ # The valid types of fields.
14
+ VALID_TYPES = [:string, :integer, :float, :ulong, :object, :json]
15
+
16
+ # The fields with variable size.
17
+ VARIABLE_TYPES = [:string, :object, :json]
18
+
19
+ # The name of the field used to identify the objects.
20
+ IDENTIFIER = :rod_id
21
+
22
+ # Initialize the field associated with the +klass+ with +name+, +type+ and +options+.
23
+ # The type should be one of:
24
+ # * +:integer+
25
+ # * +:ulong+
26
+ # * +:float+
27
+ # * +:string+
28
+ # * +:object+ (value is marshaled durign storage, and unmarshaled during read)
29
+ # * +:json+ (value is dumped in JSON format during storage, and loaded during read.
30
+ # Note: some Ruby types are not unified during conversion, e.g. String and Symbol)
31
+ # The valid options are:
32
+ # * +:index+ builds an index for the field and might be:
33
+ # ** +:flat+ simple hash index (+true+ works as well for backwards compatiblity)
34
+ # ** +:segmented+ index split for 1001 pieces for shorter load times (only
35
+ # one piece is loaded on one look-up)
36
+ def initialize(klass,name,type,options={})
37
+ super(klass,name,options)
38
+ check_type(type)
39
+ @type = type
40
+ end
41
+
42
+ # Creates a copy of the field with a new +klass+.
43
+ def copy(klass)
44
+ self.class.new(klass,@name,@type,@options)
45
+ end
46
+
47
+ # Predicate indicating that this property is a field.
48
+ def field?
49
+ true
50
+ end
51
+
52
+ # Predicate indicating that this property is not an association.
53
+ def association?
54
+ false
55
+ end
56
+
57
+ # Returns the default value for given type of field.
58
+ def default_value
59
+ case @type
60
+ when :integer
61
+ 0
62
+ when :ulong
63
+ 0
64
+ when :float
65
+ 0.0
66
+ when :string
67
+ ''
68
+ when :object, :json
69
+ nil
70
+ end
71
+ end
72
+
73
+ # Returns true if the field has a variable size.
74
+ def variable_size?
75
+ VARIABLE_TYPES.include?(@type)
76
+ end
77
+
78
+ # Dumps the +value+ of the field according to its type.
79
+ def dump(value)
80
+ case @type
81
+ when :object
82
+ Marshal.dump(value)
83
+ when :json
84
+ JSON.dump([value])
85
+ when :string
86
+ # TODO the encoding should be stored in the DB
87
+ # or configured globally
88
+ value.encode("utf-8")
89
+ when :ulong
90
+ raise InvalidArgument.new(value,"ulong") if value < 0
91
+ value
92
+ else
93
+ value
94
+ end
95
+ end
96
+
97
+ # Loads the +value+ of the field according to its type.
98
+ def load(value)
99
+ return value unless variable_size?
100
+ case @type
101
+ when :object
102
+ value.force_encoding("ascii-8bit")
103
+ value = Marshal.load(value) rescue nil
104
+ when :json
105
+ value.force_encoding("ascii-8bit")
106
+ value = JSON.load(value).first rescue nil
107
+ when :string
108
+ value.force_encoding("utf-8")
109
+ end
110
+ value
111
+ end
112
+
113
+ # Returns true if the field is used to identify the objects.
114
+ def identifier?
115
+ @name == IDENTIFIER
116
+ end
117
+
118
+ # Returns the metadata of the field in form of a hash.
119
+ def metadata
120
+ @options.merge({:type => @type})
121
+ end
122
+
123
+ # Converts the field to fields in a C struct.
124
+ def to_c_struct
125
+ unless variable_size?
126
+ str = <<-SUBEND
127
+ |#ifdef __BYTE_ORDER
128
+ |# if __BYTE_ORDER == __BIG_ENDIAN
129
+ | uint64_t #{@name};
130
+ |# else
131
+ | #{c_type(@type)} #{@name};
132
+ |# endif
133
+ |#else
134
+ | #{c_type(@type)} #{@name};
135
+ |#endif
136
+ SUBEND
137
+ str.margin
138
+ else
139
+ " #{c_type(:ulong)} #{@name}_length;\n" +
140
+ " #{c_type(:ulong)} #{@name}_offset;\n"
141
+ end
142
+ end
143
+
144
+ # Defines the accessor of the field's constituents
145
+ # (C struct field/fields that hold the field data).
146
+ def define_c_accessors(builder)
147
+ unless variable_size?
148
+ field_reader(@name,@klass.struct_name,c_type(@type),builder)
149
+ field_writer(@name,@klass.struct_name,c_type(@type),builder)
150
+ else
151
+ field_reader("#{@name}_length",@klass.struct_name,c_type(:ulong),builder)
152
+ field_reader("#{@name}_offset",@klass.struct_name,c_type(:ulong),builder)
153
+ field_writer("#{@name}_length",@klass.struct_name,c_type(:ulong),builder)
154
+ field_writer("#{@name}_offset",@klass.struct_name,c_type(:ulong),builder)
155
+ end
156
+ end
157
+
158
+ # Make the C accessors private.
159
+ def seal_c_accessors
160
+ unless variable_size?
161
+ @klass.send(:private,"_#{@name}")
162
+ @klass.send(:private,"_#{@name}=")
163
+ else
164
+ @klass.send(:private,"_#{@name}_length")
165
+ @klass.send(:private,"_#{@name}_length=")
166
+ @klass.send(:private,"_#{@name}_offset")
167
+ @klass.send(:private,"_#{@name}_offset=")
168
+ end
169
+ end
170
+
171
+ # Defines the getter of the Ruby class which corresponds to this field.
172
+ def define_getter
173
+ field = @name.to_s
174
+ unless variable_size?
175
+ @klass.send(:define_method,field) do
176
+ value = instance_variable_get("@#{field}")
177
+ if value.nil?
178
+ if self.new?
179
+ value = nil
180
+ else
181
+ value = send("_#{field}",@rod_id)
182
+ end
183
+ instance_variable_set("@#{field}",value)
184
+ end
185
+ value
186
+ end
187
+ else
188
+ is_object = @type != :string
189
+ type = @type
190
+ property = self
191
+ database = @klass.database
192
+ @klass.send(:define_method,field) do
193
+ value = instance_variable_get("@#{field}")
194
+ if value.nil? # first call
195
+ if self.new?
196
+ return (is_object ? nil : "")
197
+ else
198
+ length = send("_#{field}_length", @rod_id)
199
+ if length == 0
200
+ return (is_object ? nil : "")
201
+ end
202
+ offset = send("_#{field}_offset", @rod_id)
203
+ read_options = {}
204
+ if is_object
205
+ read_options[:skip_encoding] = true
206
+ end
207
+ value = database.read_string(length, offset)
208
+ value = property.load(value)
209
+ # caching Ruby representation
210
+ # don't use setter - avoid change tracking
211
+ instance_variable_set("@#{field}",value)
212
+ end
213
+ end
214
+ value
215
+ end
216
+ end
217
+ end
218
+
219
+ # Defines the settor of the Ruby class which corresponds to this field.
220
+ def define_setter
221
+ # optimization
222
+ field = @name.to_s
223
+ @klass.send(:define_method,"#{field}=") do |value|
224
+ old_value = send(field)
225
+ send("#{field}_will_change!") unless old_value == value
226
+ instance_variable_set("@#{field}",value)
227
+ value
228
+ end
229
+ end
230
+
231
+ # Returns the memory layout of the C struct fields that
232
+ # correspond to this field.
233
+ def layout
234
+ unless variable_size?
235
+ "#{@name}[value:#{sizeof(@type)}]"
236
+ else
237
+ "#{@name}[length:#{sizeof(:ulong)}+" +
238
+ "offset:#{sizeof(:ulong)}]"
239
+ end
240
+ end
241
+
242
+ protected
243
+ # Check if the property has a valid type.
244
+ # An exceptions is raised if the type is invalid.
245
+ def check_type(type)
246
+ unless VALID_TYPES.include?(type)
247
+ raise InvalidArgument.new(type,"field type")
248
+ end
249
+ end
250
+
251
+ # Check if the property has valid options.
252
+ # An exceptions is raised if they are not.
253
+ def check_options(options)
254
+ #TODO implement
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,145 @@
1
+ require 'rod/property/base'
2
+
3
+ module Rod
4
+ module Property
5
+ # This class defines the +has_many+ (plural association) property.
6
+ # A +has_many+ property has to define its +name+.
7
+ class PluralAssociation < Base
8
+ # Creates new plural association associated with +klass+
9
+ # with given +name+ and +options+.
10
+ def initialize(klass,name,options={})
11
+ super(klass,name,options)
12
+ end
13
+
14
+ # Predicate indicating that this property is a field.
15
+ def field?
16
+ false
17
+ end
18
+
19
+ # Predicate indicating that this property is an association.
20
+ def association?
21
+ true
22
+ end
23
+
24
+ # Predicate indicating that this property is not a singular association.
25
+ def singular?
26
+ false
27
+ end
28
+
29
+ # Predicate indicating that this property is a plural association.
30
+ def plural?
31
+ true
32
+ end
33
+
34
+ # Predicate indicating that this property is polymorphic.
35
+ def polymorphic?
36
+ @options[:polymorphic]
37
+ end
38
+
39
+ # Returns the metadata of the association in form of a hash.
40
+ def metadata
41
+ @options.dup
42
+ end
43
+
44
+ # Converts the association to fields in a C struct.
45
+ def to_c_struct
46
+ " #{c_type(:ulong)} #{@name}_offset;\n" +
47
+ " #{c_type(:ulong)} #{@name}_count;\n"
48
+ end
49
+
50
+ # Defines the accessor of the association's constituents
51
+ # (C struct field/fields that hold the association data).
52
+ def define_c_accessors(builder)
53
+ field_reader("#{@name}_count",@klass.struct_name,c_type(:ulong),builder)
54
+ field_reader("#{@name}_offset",@klass.struct_name,c_type(:ulong),builder)
55
+ field_writer("#{@name}_count",@klass.struct_name,c_type(:ulong),builder)
56
+ field_writer("#{@name}_offset",@klass.struct_name,c_type(:ulong),builder)
57
+ end
58
+
59
+ # Make the C accessors private.
60
+ def seal_c_accessors
61
+ @klass.private "_#{@name}_count"
62
+ @klass.private "_#{@name}_count="
63
+ @klass.private "_#{@name}_offset"
64
+ @klass.private "_#{@name}_offset="
65
+ end
66
+
67
+ # Make the C accessors private.
68
+ def seal_c_accessors
69
+ @klass.send(:private,"_#{@name}_count")
70
+ @klass.send(:private,"_#{@name}_count=")
71
+ @klass.send(:private,"_#{@name}_offset")
72
+ @klass.send(:private,"_#{@name}_offset=")
73
+ end
74
+
75
+ # Defines the getter of the Ruby class which corresponds to this association.
76
+ def define_getter
77
+ # optimization
78
+ name = @name.to_s
79
+ class_name =
80
+ if options[:class_name]
81
+ options[:class_name]
82
+ else
83
+ "#{@klass.scope_name}::#{::English::Inflect.singular(name).camelcase}"
84
+ end
85
+ klass = options[:polymorphic] ? nil : class_name.constantize
86
+ database = @klass.database
87
+ @klass.send(:define_method,"#{name}") do
88
+ proxy = instance_variable_get("@#{name}")
89
+ if proxy.nil?
90
+ if self.new?
91
+ count = 0
92
+ offset = 0
93
+ else
94
+ count = self.send("_#{name}_count",@rod_id)
95
+ offset = self.send("_#{name}_offset",@rod_id)
96
+ end
97
+ proxy = CollectionProxy.new(count,database,offset,klass)
98
+ instance_variable_set("@#{name}", proxy)
99
+ end
100
+ proxy
101
+ end
102
+ # count getter
103
+ @klass.send(:define_method,"#{name}_count") do
104
+ if (instance_variable_get("@#{name}") != nil)
105
+ return instance_variable_get("@#{name}").count
106
+ else
107
+ if self.new?
108
+ return 0
109
+ else
110
+ return send("_#{name}_count",@rod_id)
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ # Defines the settor of the Ruby class which corresponds to this association.
117
+ def define_setter
118
+ # optimization
119
+ name = @name.to_s
120
+ @klass.send(:define_method,"#{name}=") do |value|
121
+ proxy = send(name)
122
+ proxy.clear
123
+ value.each do |object|
124
+ proxy << object
125
+ end
126
+ proxy
127
+ end
128
+ end
129
+
130
+ # Returns the memory layout of the C struct fields that
131
+ # correspond to this association.
132
+ def layout
133
+ "#{@name}[offset:#{sizeof(:ulong)}+" +
134
+ "count:#{sizeof(:ulong)}]"
135
+ end
136
+
137
+ protected
138
+ # Check if the property has valid options.
139
+ # An exceptions is raised if they are not.
140
+ def check_options(options)
141
+ #TODO implement
142
+ end
143
+ end
144
+ end
145
+ end