goldiloader 0.0.4 → 0.0.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 856cc3b8807810dbf9e6b357bbc460d4c0bec3ac
4
- data.tar.gz: b8cd9a6f87ffa7a9427dd089ad7fcab7580361da
3
+ metadata.gz: 3912274cc2f34e5dafb2a3b1d8ee8e3f6521885e
4
+ data.tar.gz: 72e93497e463d924c5c06c49df9d10cb7427170a
5
5
  SHA512:
6
- metadata.gz: 020f3bb39360cf4253f738592bb06fc222d83c71d6a6b343ca7e4df36741bf937f6e5bd62d7faee8c83638a6b020e2f950176184b757ac0b11a0a88687057c17
7
- data.tar.gz: 4d3f56fd3c361608314c2c057cf0cbb9620513db6ecbc0595ac9e8f95a72a115c04339f678877c83142ed278d52f03a503a66ff651958deb39bfe038d1fe44cf
6
+ metadata.gz: 80396bbcaddfe170d4fd0db9a199839ef07c65d4fbfe8947de40dd31ab0500c86db372c11b105f3efaa980506402582af656da202088d7cd559c1f2d0dc10521
7
+ data.tar.gz: 060d399492cbbcd68cdb053fbc08c91781826404678e694287768d770c42736b39e5ad8e06cbad4600c0b2262fee4af4df469232fd973b6ccadb562c969d34ec
data/CHANGELOG.md CHANGED
@@ -1,11 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ### 0.0.5
4
+
5
+ * Fix ActiveRecord and ActiveSupport dependencies to work with versions greater than 4.1.0. Thanks for the pull
6
+ requests [Alexey Volodkin](https://github.com/miraks) and [Philip Claren](https://github.com/DerKobe).
7
+ * Workaround [issue 13](https://github.com/salsify/goldiloader/issues/13) by not auto-eager loading associations
8
+ that use `unscope`. This workaround will be removed when the underlying
9
+ [bug 11036](https://github.com/rails/rails/issues/11036) in the Rails eager loader is fixed.
10
+ * Workaround [issue 11](https://github.com/salsify/goldiloader/issues/11) by not auto-eager loading associations
11
+ that use `joins`. This workaround will be removed when the underlying
12
+ [bug 11518](https://github.com/rails/rails/pull/11518) in the Rails eager loader is fixed.
13
+ * Fix [issue 15](https://github.com/salsify/goldiloader/issues/15) - Don't auto eager load associations
14
+ with finder_sql in Rails 4.0. Previously this was only done for Rails 3.2.
15
+
3
16
  ### 0.0.4
4
17
 
5
- * Fix [issue 3](https://github.com/salsify/goldiloader/issues/3) - `exists?` method should take an argument
6
- thanks for reporting [Bert Goethals](https://github.com/Bertg)
18
+ * Fix [issue 3](https://github.com/salsify/goldiloader/issues/3) - `exists?` method should take an argument.
19
+ Thanks for reporting [Bert Goethals](https://github.com/Bertg)
7
20
  * Fix [issue 4](https://github.com/salsify/goldiloader/issues/4) - Associations couldn't be loaded in after
8
- destroy callbacks thanks for reporting [Bert Goethals](https://github.com/Bertg)
21
+ destroy callbacks. Thanks for reporting [Bert Goethals](https://github.com/Bertg)
9
22
  * Fix [issue 6](https://github.com/salsify/goldiloader/issues/6) - Models in read only associations weren't
10
23
  being marked as read only
11
24
  * Fix [issue 7](https://github.com/salsify/goldiloader/issues/7) - Don't attempt to eager load associations that
data/goldiloader.gemspec CHANGED
@@ -17,8 +17,8 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = Dir.glob('spec/**/*')
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.add_dependency 'activerecord', ENV.fetch('RAILS_VERSION', ['>= 3.2', '<= 4.1'])
21
- spec.add_dependency 'activesupport', ENV.fetch('RAILS_VERSION', ['>= 3.2', '<= 4.1'])
20
+ spec.add_dependency 'activerecord', ENV.fetch('RAILS_VERSION', ['>= 3.2', '< 4.2'])
21
+ spec.add_dependency 'activesupport', ENV.fetch('RAILS_VERSION', ['>= 3.2', '< 4.2'])
22
22
 
23
23
  spec.add_development_dependency 'coveralls'
24
24
  spec.add_development_dependency 'database_cleaner', '>= 1.2'
data/lib/goldiloader.rb CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  require 'active_support/all'
4
4
  require 'active_record'
5
+ require 'goldiloader/compatibility'
5
6
  require 'goldiloader/auto_include_context'
6
7
  require 'goldiloader/association_info'
7
8
  require 'goldiloader/association_options'
8
9
  require 'goldiloader/association_loader'
9
- require 'goldiloader/model_registry'
10
10
  require 'goldiloader/active_record_patches'
@@ -14,7 +14,12 @@ module Goldiloader
14
14
  end
15
15
 
16
16
  def auto_include_context
17
- @auto_include_context ||= Goldiloader::AutoIncludeContext.create_empty.register_model(self)
17
+ @auto_include_context ||= Goldiloader::AutoIncludeContext.new.register_model(self)
18
+ end
19
+
20
+ def reload(*)
21
+ @auto_include_context = nil
22
+ super
18
23
  end
19
24
  end
20
25
  end
@@ -28,7 +33,7 @@ ActiveRecord::Relation.class_eval do
28
33
 
29
34
  models = exec_queries_without_auto_include
30
35
  # Add all loaded models to the same AutoIncludeContext
31
- auto_include_context = Goldiloader::AutoIncludeContext.create_empty
36
+ auto_include_context = Goldiloader::AutoIncludeContext.new
32
37
  auto_include_context.register_models(models)
33
38
  models
34
39
  end
@@ -52,27 +57,26 @@ ActiveRecord::Associations::Association.class_eval do
52
57
  !loaded? && options.fetch(:fully_load) { self.class.default_fully_load }
53
58
  end
54
59
 
55
- def auto_include_context
56
- @auto_include_context ||= Goldiloader::AutoIncludeContext.new(owner.auto_include_context.model_registry,
57
- owner.auto_include_context.association_path + [reflection.name])
58
- end
60
+ private
59
61
 
60
62
  def eager_loadable?
61
63
  association_info = Goldiloader::AssociationInfo.new(self)
62
64
  !association_info.limit? &&
63
65
  !association_info.offset? &&
64
66
  !association_info.group? &&
65
- !association_info.from?
67
+ !association_info.from? &&
68
+ !association_info.finder_sql? &&
69
+ # Joins not properly eager loaded - See https://github.com/salsify/goldiloader/issues/11
70
+ !association_info.joins? &&
71
+ # Unscope not properly eager loaded - https://github.com/salsify/goldiloader/issues/13
72
+ !association_info.unscope?
66
73
  end
67
74
 
68
- private
69
-
70
75
  def load_with_auto_include(load_method, *args)
71
76
  if loaded? && !stale_target?
72
77
  target
73
78
  elsif auto_include?
74
- Goldiloader::AssociationLoader.load(auto_include_context.model_registry, owner,
75
- auto_include_context.association_path)
79
+ Goldiloader::AssociationLoader.load(owner, reflection.name)
76
80
  target
77
81
  else
78
82
  send("#{load_method}_without_auto_include", *args)
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Goldiloader
2
4
  class AssociationInfo
3
5
 
@@ -5,6 +7,16 @@ module Goldiloader
5
7
  @association = association
6
8
  end
7
9
 
10
+ def unscope?
11
+ Goldiloader::Compatibility.unscope_query_method_enabled? &&
12
+ @association.association_scope.unscope_values.present?
13
+ end
14
+
15
+ def finder_sql?
16
+ Goldiloader::Compatibility.association_finder_sql_enabled? &&
17
+ @association.options[:finder_sql].present?
18
+ end
19
+
8
20
  if ActiveRecord::VERSION::MAJOR >= 4
9
21
  def read_only?
10
22
  @association.association_scope.readonly_value.present?
@@ -25,6 +37,23 @@ module Goldiloader
25
37
  def group?
26
38
  @association.association_scope.group_values.present?
27
39
  end
40
+
41
+ def joins?
42
+ # Yuck - Through associations will always have a join for *each* 'through' table
43
+ (@association.association_scope.joins_values.size - num_through_joins) > 0
44
+ end
45
+
46
+ private
47
+
48
+ def num_through_joins
49
+ association = @association
50
+ count = 0
51
+ while association.is_a?(ActiveRecord::Associations::ThroughAssociation)
52
+ count += 1
53
+ association = association.owner.association(association.through_reflection.name)
54
+ end
55
+ count
56
+ end
28
57
  else
29
58
  def read_only?
30
59
  @association.options[:readonly].present?
@@ -39,12 +68,17 @@ module Goldiloader
39
68
  end
40
69
 
41
70
  def from?
42
- @association.options[:finder_sql].present?
71
+ false
43
72
  end
44
73
 
45
74
  def group?
46
75
  @association.options[:group].present?
47
76
  end
77
+
78
+ def joins?
79
+ # Rails 3 didn't support joins for associations
80
+ false
81
+ end
48
82
  end
49
83
 
50
84
  end
@@ -4,19 +4,18 @@ module Goldiloader
4
4
  module AssociationLoader
5
5
  extend self
6
6
 
7
- def load(model_registry, model, association_path)
8
- *model_path, association_name = *association_path
9
- models = model_registry.peers(model, model_path).select do |peer|
7
+ def load(model, association_name)
8
+ models = model.auto_include_context.models.select do |peer|
10
9
  load?(peer, association_name)
11
10
  end
12
11
 
13
12
  eager_load(models, association_name)
14
13
 
15
- associated_models = associated_models(models, association_name)
16
14
  # Workaround Rails #15853 by setting models read only
17
- mark_read_only(associated_models) if read_only?(models, association_name)
18
- auto_include_context = Goldiloader::AutoIncludeContext.new(model_registry, association_path)
19
- auto_include_context.register_models(associated_models)
15
+ if read_only?(models, association_name)
16
+ associated_models = associated_models(models, association_name)
17
+ mark_read_only(associated_models)
18
+ end
20
19
  end
21
20
 
22
21
  private
@@ -1,15 +1,17 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Goldiloader
4
- class AutoIncludeContext < Struct.new(:model_registry, :association_path)
5
- def self.create_empty
6
- Goldiloader::AutoIncludeContext.new(Goldiloader::ModelRegistry.new, [])
4
+ class AutoIncludeContext
5
+ attr_reader :models
6
+
7
+ def initialize
8
+ @models = []
7
9
  end
8
10
 
9
11
  def register_models(models)
10
12
  Array.wrap(models).each do |model|
11
13
  model.auto_include_context = self
12
- model_registry.register(model, association_path)
14
+ self.models << model
13
15
  end
14
16
  self
15
17
  end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ module Goldiloader
4
+ module Compatibility
5
+
6
+ def self.mass_assignment_security_enabled?
7
+ ::ActiveRecord::VERSION::MAJOR < 4 || defined?(::ActiveRecord::MassAssignmentSecurity)
8
+ end
9
+
10
+ def self.association_finder_sql_enabled?
11
+ ::Gem::Version.new(::ActiveRecord::VERSION::STRING) < ::Gem::Version.new('4.1')
12
+ end
13
+
14
+ def self.unscope_query_method_enabled?
15
+ ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new('4.1')
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Goldiloader
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.5'
5
5
  end
data/spec/db/schema.rb CHANGED
@@ -67,17 +67,27 @@ class Blog < ActiveRecord::Base
67
67
  has_many :grouped_posts, -> { group(:blog_id) }, class_name: 'Post'
68
68
  has_many :offset_posts, -> { offset(2) }, class_name: 'Post'
69
69
  has_many :from_posts, -> { from('(select distinct blog_id from posts) as posts') }, class_name: 'Post'
70
+
71
+ has_many :posts_ordered_by_author, -> { joins(:author).order('users.name') }, class_name: 'Post'
72
+
73
+ has_many :authors_with_join, -> { joins(:address).order('addresses.city') }, through: :posts, source: :author
70
74
  else
71
75
  has_many :read_only_posts, readonly: true, class_name: 'Post'
72
76
  has_many :limited_posts, limit: 2, class_name: 'Post'
73
77
  has_many :grouped_posts, group: :blog_id, class_name: 'Post'
74
78
  has_many :offset_posts, offset: 2, class_name: 'Post'
75
- has_many :from_posts, finder_sql: Proc.new { "select distinct blog_id from posts where blog_id = #{self.id}" },
79
+
80
+ has_many :posts_ordered_by_author, include: :author, order: 'users.name', class_name: 'Post'
81
+ end
82
+
83
+ if Goldiloader::Compatibility.association_finder_sql_enabled?
84
+ has_many :finder_sql_posts, finder_sql: Proc.new { "select distinct blog_id from posts where blog_id = #{self.id}" },
76
85
  class_name: 'Post'
77
86
  end
78
87
 
79
88
  has_many :posts_overridden, class_name: 'Post'
80
89
  has_many :authors, through: :posts
90
+ has_many :addresses, through: :authors
81
91
 
82
92
  if Goldiloader::Compatibility.mass_assignment_security_enabled?
83
93
  attr_accessible :name
@@ -118,6 +128,10 @@ class User < ActiveRecord::Base
118
128
  has_one :address
119
129
  has_one :address_without_auto_include, auto_include: false, class_name: 'Address'
120
130
 
131
+ if Goldiloader::Compatibility.unscope_query_method_enabled?
132
+ has_one :scoped_address_with_default_scope_remove, -> { unscope(where: :city) }, class_name: 'ScopedAddress'
133
+ end
134
+
121
135
  if Goldiloader::Compatibility.mass_assignment_security_enabled?
122
136
  attr_accessible :name
123
137
  end
@@ -131,6 +145,16 @@ class Address < ActiveRecord::Base
131
145
  end
132
146
  end
133
147
 
148
+ class ScopedAddress < ActiveRecord::Base
149
+ self.table_name = 'addresses'
150
+ default_scope { where(city: ['Philadelphia'])}
151
+ belongs_to :user
152
+
153
+ if Goldiloader::Compatibility.mass_assignment_security_enabled?
154
+ attr_accessible :city
155
+ end
156
+ end
157
+
134
158
  class Group < ActiveRecord::Base
135
159
  has_many :tags, as: :owner
136
160
 
@@ -150,6 +150,19 @@ describe Goldiloader do
150
150
  expect(User).to have_received(:find_by_sql).once
151
151
  end
152
152
 
153
+ it "auto eager loads nested has_many through associations" do
154
+ blogs = Blog.order(:name).to_a
155
+ blogs.first.addresses.to_a
156
+
157
+ blogs.each do |blog|
158
+ expect(blog.association(:addresses)).to be_loaded
159
+ end
160
+
161
+ expect(blogs.first.addresses).to match_array([author1, author2].map(&:address))
162
+ expect(blogs.second.addresses).to match_array([author3, author1].map(&:address))
163
+ expect(Address).to have_received(:find_by_sql).once
164
+ end
165
+
153
166
  it "auto eager loads associations when the model is loaded via find" do
154
167
  blog = Blog.find(blog1.id)
155
168
  blog.posts.to_a.first.author
@@ -235,8 +248,8 @@ describe Goldiloader do
235
248
  let(:blogs) { Blog.order(:name).to_a }
236
249
 
237
250
  before do
238
- blog1.posts.create!(title: 'blog1-post3')
239
- blog2.posts.create!(title: 'blog2-post3')
251
+ blog1.posts.create!(title: 'blog1-post3', author: author1)
252
+ blog2.posts.create!(title: 'blog2-post3', author: author1)
240
253
  end
241
254
 
242
255
  shared_examples "it doesn't auto eager the association" do |association_name|
@@ -283,16 +296,81 @@ describe Goldiloader do
283
296
  it_behaves_like "it doesn't auto eager the association", :offset_posts
284
297
  end
285
298
 
286
- context "associations with an overridden from" do
287
- before do
288
- blogs.first.from_posts.to_a
299
+ if ActiveRecord::VERSION::MAJOR >= 4
300
+ context "associations with an overridden from" do
301
+ before do
302
+ blogs.first.from_posts.to_a
303
+ end
304
+
305
+ it "applies the from correctly" do
306
+ expect(blogs.first.from_posts.to_a.size).to eq 1
307
+ end
308
+
309
+ it_behaves_like "it doesn't auto eager the association", :from_posts
289
310
  end
311
+ end
312
+
313
+ if Goldiloader::Compatibility.association_finder_sql_enabled?
314
+ context "associations with finder_sql" do
315
+ before do
316
+ blogs.first.finder_sql_posts.to_a
317
+ end
318
+
319
+ it "applies the finder_sql correctly" do
320
+ expect(blogs.first.finder_sql_posts.to_a.size).to eq 1
321
+ end
290
322
 
291
- it "applies the from correctly" do
292
- expect(blogs.first.from_posts.to_a.size).to eq 1
323
+ it_behaves_like "it doesn't auto eager the association", :finder_sql_posts
293
324
  end
325
+ end
294
326
 
295
- it_behaves_like "it doesn't auto eager the association", :from_posts
327
+ if ActiveRecord::VERSION::MAJOR >= 4
328
+ context "associations with a join" do
329
+ before do
330
+ blogs.first.posts_ordered_by_author.to_a
331
+ end
332
+
333
+ it "applies the join correctly" do
334
+ expect(blogs.first.posts_ordered_by_author.to_a.size).to eq 3
335
+ end
336
+
337
+ it_behaves_like "it doesn't auto eager the association", :posts_ordered_by_author
338
+ end
339
+
340
+ context "associations with a join in a has_many_through" do
341
+ before do
342
+ blogs.first.authors_with_join.to_a
343
+ end
344
+
345
+ it "applies the join correctly" do
346
+ expect(blogs.first.authors_with_join.to_a.size).to eq 3
347
+ end
348
+
349
+ it_behaves_like "it doesn't auto eager the association", :authors_with_join
350
+ end
351
+ end
352
+
353
+ if Goldiloader::Compatibility.unscope_query_method_enabled?
354
+ context "associations with an unscoped" do
355
+ let(:authors) { User.order(:id).to_a }
356
+
357
+ before do
358
+ author1.address.update_attributes!(city: 'Boston')
359
+ author2.address.update_attributes!(city: 'Philadelphia')
360
+ author3.address.update_attributes!(city: 'Philadelphia')
361
+ authors.first.scoped_address_with_default_scope_remove
362
+ end
363
+
364
+ it "applies the unscope correctly" do
365
+ expect(authors.first.scoped_address_with_default_scope_remove).to be_present
366
+ end
367
+
368
+ it "doesn't auto eager the association" do
369
+ authors.drop(1).each do |author|
370
+ expect(author.association(:scoped_address_with_default_scope_remove)).to_not be_loaded
371
+ end
372
+ end
373
+ end
296
374
  end
297
375
  end
298
376
 
data/spec/spec_helper.rb CHANGED
@@ -16,9 +16,6 @@ require 'database_cleaner'
16
16
  require 'goldiloader'
17
17
  require 'yaml'
18
18
 
19
- spec_dir = File.dirname(__FILE__)
20
- Dir["#{spec_dir}/support/**/*.rb"].sort.each { |f| require f }
21
-
22
19
  FileUtils.makedirs('log')
23
20
 
24
21
  ActiveRecord::Base.logger = Logger.new('log/test.log')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: goldiloader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Turkel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-23 00:00:00.000000000 Z
11
+ date: 2014-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -17,9 +17,9 @@ dependencies:
17
17
  - - '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: '3.2'
20
- - - <=
20
+ - - <
21
21
  - !ruby/object:Gem::Version
22
- version: '4.1'
22
+ version: '4.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +27,9 @@ dependencies:
27
27
  - - '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '3.2'
30
- - - <=
30
+ - - <
31
31
  - !ruby/object:Gem::Version
32
- version: '4.1'
32
+ version: '4.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: activesupport
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -37,9 +37,9 @@ dependencies:
37
37
  - - '>='
38
38
  - !ruby/object:Gem::Version
39
39
  version: '3.2'
40
- - - <=
40
+ - - <
41
41
  - !ruby/object:Gem::Version
42
- version: '4.1'
42
+ version: '4.2'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -47,9 +47,9 @@ dependencies:
47
47
  - - '>='
48
48
  - !ruby/object:Gem::Version
49
49
  version: '3.2'
50
- - - <=
50
+ - - <
51
51
  - !ruby/object:Gem::Version
52
- version: '4.1'
52
+ version: '4.2'
53
53
  - !ruby/object:Gem::Dependency
54
54
  name: coveralls
55
55
  requirement: !ruby/object:Gem::Requirement
@@ -156,13 +156,12 @@ files:
156
156
  - lib/goldiloader/association_loader.rb
157
157
  - lib/goldiloader/association_options.rb
158
158
  - lib/goldiloader/auto_include_context.rb
159
- - lib/goldiloader/model_registry.rb
159
+ - lib/goldiloader/compatibility.rb
160
160
  - lib/goldiloader/version.rb
161
161
  - spec/db/database.yml
162
162
  - spec/db/schema.rb
163
163
  - spec/goldiloader/goldiloader_spec.rb
164
164
  - spec/spec_helper.rb
165
- - spec/support/compatibility.rb
166
165
  homepage: https://github.com/salsify/goldiloader
167
166
  licenses:
168
167
  - MIT
@@ -192,4 +191,3 @@ test_files:
192
191
  - spec/db/schema.rb
193
192
  - spec/goldiloader/goldiloader_spec.rb
194
193
  - spec/spec_helper.rb
195
- - spec/support/compatibility.rb
@@ -1,26 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module Goldiloader
4
- class ModelRegistry
5
- def initialize
6
- @registry = {}
7
- end
8
-
9
- def register(record, association_path)
10
- key = registry_key(record, association_path)
11
- @registry[key] ||= []
12
- @registry[key] << record
13
- end
14
-
15
- # Returns all models with the same base class loaded from the same association path
16
- def peers(record, association_path)
17
- @registry.fetch(registry_key(record, association_path), [])
18
- end
19
-
20
- private
21
-
22
- def registry_key(record, association_path)
23
- [record.class.base_class, association_path]
24
- end
25
- end
26
- end
@@ -1,14 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'active_support/version'
4
- require 'active_record/version'
5
-
6
- module Goldiloader
7
- module Compatibility
8
-
9
- def self.mass_assignment_security_enabled?
10
- ::ActiveRecord::VERSION::MAJOR < 4 || defined?(::ActiveRecord::MassAssignmentSecurity)
11
- end
12
-
13
- end
14
- end