hanami-model 0.0.0 → 0.6.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +145 -0
  3. data/EXAMPLE.md +212 -0
  4. data/LICENSE.md +22 -0
  5. data/README.md +600 -7
  6. data/hanami-model.gemspec +17 -12
  7. data/lib/hanami-model.rb +1 -0
  8. data/lib/hanami/entity.rb +298 -0
  9. data/lib/hanami/entity/dirty_tracking.rb +74 -0
  10. data/lib/hanami/model.rb +204 -2
  11. data/lib/hanami/model/adapters/abstract.rb +281 -0
  12. data/lib/hanami/model/adapters/file_system_adapter.rb +288 -0
  13. data/lib/hanami/model/adapters/implementation.rb +111 -0
  14. data/lib/hanami/model/adapters/memory/collection.rb +132 -0
  15. data/lib/hanami/model/adapters/memory/command.rb +113 -0
  16. data/lib/hanami/model/adapters/memory/query.rb +653 -0
  17. data/lib/hanami/model/adapters/memory_adapter.rb +179 -0
  18. data/lib/hanami/model/adapters/null_adapter.rb +24 -0
  19. data/lib/hanami/model/adapters/sql/collection.rb +287 -0
  20. data/lib/hanami/model/adapters/sql/command.rb +73 -0
  21. data/lib/hanami/model/adapters/sql/console.rb +33 -0
  22. data/lib/hanami/model/adapters/sql/consoles/mysql.rb +49 -0
  23. data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +48 -0
  24. data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +26 -0
  25. data/lib/hanami/model/adapters/sql/query.rb +788 -0
  26. data/lib/hanami/model/adapters/sql_adapter.rb +296 -0
  27. data/lib/hanami/model/coercer.rb +74 -0
  28. data/lib/hanami/model/config/adapter.rb +116 -0
  29. data/lib/hanami/model/config/mapper.rb +45 -0
  30. data/lib/hanami/model/configuration.rb +275 -0
  31. data/lib/hanami/model/error.rb +7 -0
  32. data/lib/hanami/model/mapper.rb +124 -0
  33. data/lib/hanami/model/mapping.rb +48 -0
  34. data/lib/hanami/model/mapping/attribute.rb +85 -0
  35. data/lib/hanami/model/mapping/coercers.rb +314 -0
  36. data/lib/hanami/model/mapping/collection.rb +490 -0
  37. data/lib/hanami/model/mapping/collection_coercer.rb +79 -0
  38. data/lib/hanami/model/migrator.rb +324 -0
  39. data/lib/hanami/model/migrator/adapter.rb +170 -0
  40. data/lib/hanami/model/migrator/connection.rb +133 -0
  41. data/lib/hanami/model/migrator/mysql_adapter.rb +72 -0
  42. data/lib/hanami/model/migrator/postgres_adapter.rb +119 -0
  43. data/lib/hanami/model/migrator/sqlite_adapter.rb +110 -0
  44. data/lib/hanami/model/version.rb +4 -1
  45. data/lib/hanami/repository.rb +872 -0
  46. metadata +100 -16
  47. data/.gitignore +0 -9
  48. data/Gemfile +0 -4
  49. data/Rakefile +0 -2
  50. data/bin/console +0 -14
  51. data/bin/setup +0 -8
@@ -4,20 +4,25 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'hanami/model/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "hanami-model"
7
+ spec.name = 'hanami-model'
8
8
  spec.version = Hanami::Model::VERSION
9
- spec.authors = ["Luca Guidi"]
10
- spec.email = ["me@lucaguidi.com"]
9
+ spec.authors = ['Luca Guidi', 'Trung Lê', 'Alfonso Uceda']
10
+ spec.email = ['me@lucaguidi.com', 'trung.le@ruby-journal.com', 'uceda73@gmail.com']
11
+ spec.summary = %q{A persistence layer for Hanami}
12
+ spec.description = %q{A persistence framework with entities, repositories, data mapper and query objects}
13
+ spec.homepage = 'http://hanamirb.org'
14
+ spec.license = 'MIT'
11
15
 
12
- spec.summary = %q{The web, with simplicity}
13
- spec.description = %q{Hanami is a web framework for Ruby}
14
- spec.homepage = "http://hanamirb.org"
16
+ spec.files = `git ls-files -z -- lib/* CHANGELOG.md EXAMPLE.md LICENSE.md README.md hanami-model.gemspec`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+ spec.required_ruby_version = '>= 2.0.0'
15
21
 
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
22
+ spec.add_runtime_dependency 'hanami-utils', '~> 0.7'
23
+ spec.add_runtime_dependency 'sequel', '~> 4.9'
20
24
 
21
- spec.add_development_dependency "bundler", "~> 1.11"
22
- spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency 'bundler', '~> 1.6'
26
+ spec.add_development_dependency 'minitest', '~> 5'
27
+ spec.add_development_dependency 'rake', '~> 10'
23
28
  end
@@ -0,0 +1 @@
1
+ require 'hanami/model'
@@ -0,0 +1,298 @@
1
+ require 'hanami/utils/kernel'
2
+ require 'hanami/utils/attributes'
3
+
4
+ module Hanami
5
+ # An object that is defined by its identity.
6
+ # See "Domain Driven Design" by Eric Evans.
7
+ #
8
+ # An entity is the core of an application, where the part of the domain
9
+ # logic is implemented. It's a small, cohesive object that expresses coherent
10
+ # and meaningful behaviors.
11
+ #
12
+ # It deals with one and only one responsibility that is pertinent to the
13
+ # domain of the application, without caring about details such as persistence
14
+ # or validations.
15
+ #
16
+ # This simplicity of design allows developers to focus on behaviors, or
17
+ # message passing if you will, which is the quintessence of Object Oriented
18
+ # Programming.
19
+ #
20
+ # @example With Hanami::Entity
21
+ # require 'hanami/model'
22
+ #
23
+ # class Person
24
+ # include Hanami::Entity
25
+ # attributes :name, :age
26
+ # end
27
+ #
28
+ # When a class includes `Hanami::Entity` it receives the following interface:
29
+ #
30
+ # * #id
31
+ # * #id=
32
+ # * #initialize(attributes = {})
33
+ #
34
+ # `Hanami::Entity` also provides the `.attributes=` for defining attribute accessors for the given names.
35
+ #
36
+ # If we expand the code above in **pure Ruby**, it would be:
37
+ #
38
+ # @example Pure Ruby
39
+ # class Person
40
+ # attr_accessor :id, :name, :age
41
+ #
42
+ # def initialize(attributes = {})
43
+ # @id, @name, @age = attributes.values_at(:id, :name, :age)
44
+ # end
45
+ # end
46
+ #
47
+ # **Hanami::Model** ships `Hanami::Entity` for developers's convenience.
48
+ #
49
+ # **Hanami::Model** depends on a narrow and well-defined interface for an
50
+ # Entity - `#id`, `#id=`, `#initialize(attributes={})`.If your object
51
+ # implements that interface then that object can be used as an Entity in the
52
+ # **Hanami::Model** framework.
53
+ #
54
+ # However, we suggest to implement this interface by including
55
+ # `Hanami::Entity`, in case that future versions of the framework will expand
56
+ # it.
57
+ #
58
+ # See Dependency Inversion Principle for more on interfaces.
59
+ #
60
+ # @since 0.1.0
61
+ #
62
+ # @see Hanami::Repository
63
+ module Entity
64
+ # Inject the public API into the hosting class.
65
+ #
66
+ # @since 0.1.0
67
+ #
68
+ # @example With Object
69
+ # require 'hanami/model'
70
+ #
71
+ # class User
72
+ # include Hanami::Entity
73
+ # end
74
+ #
75
+ # @example With Struct
76
+ # require 'hanami/model'
77
+ #
78
+ # User = Struct.new(:id, :name) do
79
+ # include Hanami::Entity
80
+ # end
81
+ def self.included(base)
82
+ base.class_eval do
83
+ extend ClassMethods
84
+ attributes :id
85
+ end
86
+ end
87
+
88
+ module ClassMethods
89
+ # (Re)defines getters, setters and initialization for the given attributes.
90
+ #
91
+ # These attributes can match the database columns, but this isn't a
92
+ # requirement. The mapper used by the relative repository will translate
93
+ # these names automatically.
94
+ #
95
+ # An entity can work with attributes not configured in the mapper, but
96
+ # of course they will be ignored when the entity will be persisted.
97
+ #
98
+ # Please notice that the required `id` attribute is automatically defined
99
+ # and can be omitted in the arguments.
100
+ #
101
+ # @param attrs [Array<Symbol>] a set of arbitrary attribute names
102
+ #
103
+ # @since 0.2.0
104
+ #
105
+ # @see Hanami::Repository
106
+ # @see Hanami::Model::Mapper
107
+ #
108
+ # @example
109
+ # require 'hanami/model'
110
+ #
111
+ # class User
112
+ # include Hanami::Entity
113
+ # attributes :name, :age
114
+ # end
115
+ # User.attributes => #<Set: {:id, :name, :age}>
116
+ #
117
+ # @example Given params is array of attributes
118
+ # require 'hanami/model'
119
+ #
120
+ # class User
121
+ # include Hanami::Entity
122
+ # attributes [:name, :age]
123
+ # end
124
+ # User.attributes => #<Set: {:id, :name, :age}>
125
+ #
126
+ # @example Extend entity
127
+ # require 'hanami/model'
128
+ #
129
+ # class User
130
+ # include Hanami::Entity
131
+ # attributes :name
132
+ # end
133
+ #
134
+ # class DeletedUser < User
135
+ # include Hanami::Entity
136
+ # attributes :deleted_at
137
+ # end
138
+ #
139
+ # User.attributes => #<Set: {:id, :name}>
140
+ # DeletedUser.attributes => #<Set: {:id, :name, :deleted_at}>
141
+ #
142
+ def attributes(*attrs)
143
+ return @attributes ||= Set.new unless attrs.any?
144
+
145
+ Hanami::Utils::Kernel.Array(attrs).each do |attr|
146
+ if allowed_attribute_name?(attr)
147
+ define_attr_accessor(attr)
148
+ self.attributes << attr
149
+ end
150
+ end
151
+ end
152
+
153
+ # Define setter/getter methods for attributes.
154
+ #
155
+ # @param attr [Symbol] an attribute name
156
+ #
157
+ # @since 0.3.1
158
+ # @api private
159
+ def define_attr_accessor(attr)
160
+ attr_accessor(attr)
161
+ end
162
+
163
+ # Check if attr_reader define the given attribute
164
+ #
165
+ # @since 0.5.1
166
+ # @api private
167
+ def allowed_attribute_name?(name)
168
+ !instance_methods.include?(name)
169
+ end
170
+
171
+ protected
172
+
173
+ # @see Class#inherited
174
+ def inherited(subclass)
175
+ subclass.attributes(*attributes)
176
+ super
177
+ end
178
+ end
179
+
180
+ # Defines a generic, inefficient initializer, in case that the attributes
181
+ # weren't explicitly defined with `.attributes=`.
182
+ #
183
+ # @param attributes [Hash] a set of attribute names and values
184
+ #
185
+ # @raise NoMethodError in case the given attributes are trying to set unknown
186
+ # or private methods.
187
+ #
188
+ # @since 0.1.0
189
+ #
190
+ # @see .attributes
191
+ def initialize(attributes = {})
192
+ attributes.each do |k, v|
193
+ setter = "#{ k }="
194
+ public_send(setter, v) if respond_to?(setter)
195
+ end
196
+ end
197
+
198
+ # Overrides the equality Ruby operator
199
+ #
200
+ # Two entities are considered equal if they are instances of the same class
201
+ # and if they have the same #id.
202
+ #
203
+ # @since 0.1.0
204
+ def ==(other)
205
+ self.class == other.class &&
206
+ self.id == other.id
207
+ end
208
+
209
+ # Return the hash of attributes
210
+ #
211
+ # @since 0.2.0
212
+ #
213
+ # @example
214
+ # require 'hanami/model'
215
+ # class User
216
+ # include Hanami::Entity
217
+ # attributes :name
218
+ # end
219
+ #
220
+ # user = User.new(id: 23, name: 'Luca')
221
+ # user.to_h # => { :id => 23, :name => "Luca" }
222
+ def to_h
223
+ Hash[attribute_names.map { |a| [a, read_attribute(a)] }]
224
+ end
225
+
226
+ # Return the set of attribute names
227
+ #
228
+ # @since 0.5.1
229
+ #
230
+ # @example
231
+ # require 'hanami/model'
232
+ # class User
233
+ # include Hanami::Entity
234
+ # attributes :name
235
+ # end
236
+ #
237
+ # user = User.new(id: 23, name: 'Luca')
238
+ # user.attribute_names # #<Set: {:id, :name}>
239
+ def attribute_names
240
+ self.class.attributes
241
+ end
242
+
243
+ # Return the contents of the entity as a nicely formatted string.
244
+ #
245
+ # Display all attributes of the entity for inspection (even if they are nil)
246
+ #
247
+ # @since 0.5.1
248
+ #
249
+ # @example
250
+ # require 'hanami/model'
251
+ # class User
252
+ # include Hanami::Entity
253
+ # attributes :name, :email
254
+ # end
255
+ #
256
+ # user = User.new(id: 23, name: 'Luca')
257
+ # user.inspect # #<User:0x007fa7eefe0b58 @id=nil @name="Luca" @email=nil>
258
+ def inspect
259
+ attr_list = attribute_names.inject([]) do |res, name|
260
+ res << "@#{name}=#{read_attribute(name).inspect}"
261
+ end.join(' ')
262
+
263
+ "#<#{self.class.name}:0x00#{(__id__ << 1).to_s(16)} #{attr_list}>"
264
+ end
265
+
266
+ alias_method :to_s, :inspect
267
+
268
+ # Set attributes for entity
269
+ #
270
+ # @since 0.2.0
271
+ #
272
+ # @example
273
+ # require 'hanami/model'
274
+ # class User
275
+ # include Hanami::Entity
276
+ # attributes :name
277
+ # end
278
+ #
279
+ # user = User.new(name: 'Lucca')
280
+ # user.update(name: 'Luca')
281
+ # user.name # => 'Luca'
282
+ def update(attributes={})
283
+ attributes.each do |attribute, value|
284
+ public_send("#{attribute}=", value)
285
+ end
286
+ end
287
+
288
+ private
289
+
290
+ # Return the value by attribute name
291
+ #
292
+ # @since 0.5.1
293
+ # @api private
294
+ def read_attribute(attr_name)
295
+ public_send(attr_name)
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,74 @@
1
+ module Hanami
2
+ module Entity
3
+ # Dirty tracking for entities
4
+ #
5
+ # @since 0.3.1
6
+ #
7
+ # @example Dirty tracking
8
+ # require 'hanami/model'
9
+ #
10
+ # class User
11
+ # include Hanami::Entity
12
+ # include Hanami::Entity::DirtyTracking
13
+ #
14
+ # attributes :name
15
+ # end
16
+ #
17
+ # article = Article.new(title: 'Generation P')
18
+ # article.changed? # => false
19
+ #
20
+ # article.title = 'Master and Margarita'
21
+ # article.changed? # => true
22
+ #
23
+ # article.changed_attributes # => {:title => "Generation P"}
24
+ module DirtyTracking
25
+ # Override initialize process.
26
+ #
27
+ # @param attributes [Hash] a set of attribute names and values
28
+ #
29
+ # @since 0.3.1
30
+ #
31
+ # @see Hanami::Entity#initialize
32
+ def initialize(attributes = {})
33
+ super
34
+ @_initial_state = Utils::Hash.new(to_h).deep_dup
35
+ end
36
+
37
+ # Getter for hash of changed attributes.
38
+ # Return empty hash, if there is no changes
39
+ # Getter for hash of changed attributes. Value in it is the previous one.
40
+ #
41
+ # @return [::Hash] the changed attributes
42
+ #
43
+ # @since 0.3.1
44
+ #
45
+ # @example
46
+ # require 'hanami/model'
47
+ #
48
+ # class Article
49
+ # include Hanami::Entity
50
+ # include Hanami::Entity::DirtyTracking
51
+ #
52
+ # attributes :title
53
+ # end
54
+ #
55
+ # article = Article.new(title: 'The crime and punishment')
56
+ # article.changed_attributes # => {}
57
+ #
58
+ # article.title = 'Master and Margarita'
59
+ # article.changed_attributes # => {:title => "The crime and punishment"}
60
+ def changed_attributes
61
+ Hash[@_initial_state.to_a - to_h.to_a]
62
+ end
63
+
64
+ # Checks if the attributes were changed
65
+ #
66
+ # @return [TrueClass, FalseClass] the result of the check
67
+ #
68
+ # @since 0.3.1
69
+ def changed?
70
+ changed_attributes.any?
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,7 +1,209 @@
1
- require "hanami/model/version"
1
+ require 'hanami/model/version'
2
+ require 'hanami/entity'
3
+ require 'hanami/entity/dirty_tracking'
4
+ require 'hanami/repository'
5
+ require 'hanami/model/mapper'
6
+ require 'hanami/model/configuration'
7
+ require 'hanami/model/error'
2
8
 
3
9
  module Hanami
10
+ # Model
11
+ #
12
+ # @since 0.1.0
4
13
  module Model
5
- # Your code goes here...
14
+ # Error for non persisted entity
15
+ # It's raised when we try to update or delete a non persisted entity.
16
+ #
17
+ # @since 0.1.0
18
+ #
19
+ # @see Hanami::Repository.update
20
+ class NonPersistedEntityError < Hanami::Model::Error
21
+ end
22
+
23
+ # Error for invalid mapper configuration
24
+ # It's raised when mapping is not configured correctly
25
+ #
26
+ # @since 0.2.0
27
+ #
28
+ # @see Hanami::Configuration#mapping
29
+ class InvalidMappingError < Hanami::Model::Error
30
+ end
31
+
32
+ # Error for invalid raw command syntax
33
+ #
34
+ # @since 0.5.0
35
+ class InvalidCommandError < Hanami::Model::Error
36
+ def initialize(message = "Invalid command")
37
+ super
38
+ end
39
+ end
40
+
41
+ # Error for invalid raw query syntax
42
+ #
43
+ # @since 0.3.1
44
+ class InvalidQueryError < Hanami::Model::Error
45
+ def initialize(message = "Invalid query")
46
+ super
47
+ end
48
+ end
49
+
50
+ include Utils::ClassAttribute
51
+
52
+ # Framework configuration
53
+ #
54
+ # @since 0.2.0
55
+ # @api private
56
+ class_attribute :configuration
57
+ self.configuration = Configuration.new
58
+
59
+ # Configure the framework.
60
+ # It yields the given block in the context of the configuration
61
+ #
62
+ # @param blk [Proc] the configuration block
63
+ #
64
+ # @since 0.2.0
65
+ #
66
+ # @see Hanami::Model
67
+ #
68
+ # @example
69
+ # require 'hanami/model'
70
+ #
71
+ # Hanami::Model.configure do
72
+ # adapter type: :sql, uri: 'postgres://localhost/database'
73
+ #
74
+ # mapping do
75
+ # collection :users do
76
+ # entity User
77
+ #
78
+ # attribute :id, Integer
79
+ # attribute :name, String
80
+ # end
81
+ # end
82
+ # end
83
+ #
84
+ # Adapter MUST follow the convention in which adapter class is inflection of adapter name
85
+ # The above example has name :sql, thus derived class will be `Hanami::Model::Adapters::SqlAdapter`
86
+ def self.configure(&blk)
87
+ configuration.instance_eval(&blk)
88
+ self
89
+ end
90
+
91
+ # Load the framework
92
+ #
93
+ # @since 0.2.0
94
+ # @api private
95
+ def self.load!
96
+ configuration.load!
97
+ end
98
+
99
+ # Unload the framework
100
+ #
101
+ # @since 0.2.0
102
+ # @api private
103
+ def self.unload!
104
+ configuration.unload!
105
+ end
106
+
107
+ # Duplicate Hanami::Model in order to create a new separated instance
108
+ # of the framework.
109
+ #
110
+ # The new instance of the framework will be completely decoupled from the
111
+ # original. It will inherit the configuration, but all the changes that
112
+ # happen after the duplication, won't be reflected on the other copies.
113
+ #
114
+ # @return [Module] a copy of Hanami::Model
115
+ #
116
+ # @since 0.2.0
117
+ # @api private
118
+ #
119
+ # @example Basic usage
120
+ # require 'hanami/model'
121
+ #
122
+ # module MyApp
123
+ # Model = Hanami::Model.dupe
124
+ # end
125
+ #
126
+ # MyApp::Model == Hanami::Model # => false
127
+ #
128
+ # MyApp::Model.configuration ==
129
+ # Hanami::Model.configuration # => false
130
+ #
131
+ # @example Inheriting configuration
132
+ # require 'hanami/model'
133
+ #
134
+ # Hanami::Model.configure do
135
+ # adapter type: :sql, uri: 'sqlite3://uri'
136
+ # end
137
+ #
138
+ # module MyApp
139
+ # Model = Hanami::Model.dupe
140
+ # end
141
+ #
142
+ # module MyApi
143
+ # Model = Hanami::Model.dupe
144
+ # Model.configure do
145
+ # adapter type: :sql, uri: 'postgresql://uri'
146
+ # end
147
+ # end
148
+ #
149
+ # Hanami::Model.configuration.adapter_config.uri # => 'sqlite3://uri'
150
+ # MyApp::Model.configuration.adapter_config.uri # => 'sqlite3://uri'
151
+ # MyApi::Model.configuration.adapter_config.uri # => 'postgresql://uri'
152
+ def self.dupe
153
+ dup.tap do |duplicated|
154
+ duplicated.configuration = Configuration.new
155
+ end
156
+ end
157
+
158
+ # Duplicate the framework and generate modules for the target application
159
+ #
160
+ # @param mod [Module] the Ruby namespace of the application
161
+ # @param blk [Proc] an optional block to configure the framework
162
+ #
163
+ # @return [Module] a copy of Hanami::Model
164
+ #
165
+ # @since 0.2.0
166
+ #
167
+ # @see Hanami::Model#dupe
168
+ # @see Hanami::Model::Configuration
169
+ #
170
+ # @example Basic usage
171
+ # require 'hanami/model'
172
+ #
173
+ # module MyApp
174
+ # Model = Hanami::Model.dupe
175
+ # end
176
+ #
177
+ # # It will:
178
+ # #
179
+ # # 1. Generate MyApp::Model
180
+ # # 2. Generate MyApp::Entity
181
+ # # 3. Generate MyApp::Repository
182
+ #
183
+ # MyApp::Model == Hanami::Model # => false
184
+ # MyApp::Repository == Hanami::Repository # => false
185
+ #
186
+ # @example Block usage
187
+ # require 'hanami/model'
188
+ #
189
+ # module MyApp
190
+ # Model = Hanami::Model.duplicate(self) do
191
+ # adapter type: :memory, uri: 'memory://localhost'
192
+ # end
193
+ # end
194
+ #
195
+ # Hanami::Model.configuration.adapter_config # => nil
196
+ # MyApp::Model.configuration.adapter_config # => #<Hanami::Model::Config::Adapter:0x007ff0ff0244f8 @type=:memory, @uri="memory://localhost", @class_name="MemoryAdapter">
197
+ def self.duplicate(mod, &blk)
198
+ dupe.tap do |duplicated|
199
+ mod.module_eval %{
200
+ Entity = Hanami::Entity.dup
201
+ Repository = Hanami::Repository.dup
202
+ }
203
+
204
+ duplicated.configure(&blk) if block_given?
205
+ end
206
+ end
207
+
6
208
  end
7
209
  end