ruby-trello 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -10
  3. data/lib/trello.rb +47 -35
  4. data/lib/trello/action.rb +4 -2
  5. data/lib/trello/association_builder/has_many.rb +17 -0
  6. data/lib/trello/association_builder/has_one.rb +16 -0
  7. data/lib/trello/association_fetcher/has_many.rb +26 -0
  8. data/lib/trello/association_fetcher/has_many/fetch.rb +47 -0
  9. data/lib/trello/association_fetcher/has_many/params.rb +56 -0
  10. data/lib/trello/association_fetcher/has_one.rb +25 -0
  11. data/lib/trello/association_fetcher/has_one/fetch.rb +49 -0
  12. data/lib/trello/association_fetcher/has_one/params.rb +36 -0
  13. data/lib/trello/association_infer_tool.rb +13 -0
  14. data/lib/trello/association_proxy.rb +1 -1
  15. data/lib/trello/basic_data.rb +7 -48
  16. data/lib/trello/board.rb +1 -1
  17. data/lib/trello/card.rb +88 -36
  18. data/lib/trello/custom_field.rb +26 -9
  19. data/lib/trello/custom_field_item.rb +32 -12
  20. data/lib/trello/label.rb +24 -7
  21. data/lib/trello/member.rb +1 -1
  22. data/lib/trello/register_attributes.rb +54 -0
  23. data/spec/action_spec.rb +1 -1
  24. data/spec/card_spec.rb +98 -22
  25. data/spec/cassettes/can_add_a_file_from_url_on_a_card.yml +189 -0
  26. data/spec/cassettes/can_add_a_file_on_a_card.yml +200 -0
  27. data/spec/cassettes/can_add_a_member_to_a_card.yml +190 -0
  28. data/spec/cassettes/can_add_label_on_a_card.yml +281 -0
  29. data/spec/cassettes/can_close_bong_a_card.yml +189 -0
  30. data/spec/cassettes/can_get_actions.yml +196 -0
  31. data/spec/cassettes/can_get_attachments.yml +187 -0
  32. data/spec/cassettes/can_get_comments.yml +193 -0
  33. data/spec/cassettes/can_move_a_card_to_another_board_with_specific_list.yml +373 -0
  34. data/spec/cassettes/can_move_a_card_to_another_board_without_specific_list.yml +281 -0
  35. data/spec/cassettes/can_move_a_card_to_another_list.yml +281 -0
  36. data/spec/cassettes/can_remove_a_member_from_a_card.yml +185 -0
  37. data/spec/cassettes/can_remove_an_attachment_on_a_card.yml +277 -0
  38. data/spec/cassettes/can_remove_an_upvote_on_a_card.yml +465 -0
  39. data/spec/cassettes/can_remove_label_on_a_card.yml +279 -0
  40. data/spec/cassettes/can_success_add_comment_to_a_card.yml +196 -0
  41. data/spec/cassettes/can_success_copy_checklist_to_a_card.yml +281 -0
  42. data/spec/cassettes/can_success_copy_checklist_to_a_card_without_name_pos.yml +281 -0
  43. data/spec/cassettes/can_success_create_a_card.yml +99 -0
  44. data/spec/cassettes/can_success_create_new_checklist_to_a_card.yml +189 -0
  45. data/spec/cassettes/can_success_delete_card.yml +185 -0
  46. data/spec/cassettes/can_success_upate_a_card.yml +189 -0
  47. data/spec/cassettes/can_success_update_bong_a_card.yml +191 -0
  48. data/spec/cassettes/can_upvote_on_a_card.yml +469 -0
  49. data/spec/cassettes/card_find_with_id_and_get_all_fields.yml +95 -0
  50. data/spec/cassettes/card_find_with_id_and_get_specific_fields.yml +96 -0
  51. data/spec/cassettes/custom_field_item_save_1.yml +97 -0
  52. data/spec/cassettes/custom_field_item_save_2.yml +281 -0
  53. data/spec/cassettes/get_board_of_card.yml +187 -0
  54. data/spec/cassettes/get_check_item_states_of_card.yml +188 -0
  55. data/spec/cassettes/get_checklists_of_card.yml +187 -0
  56. data/spec/cassettes/get_cover_image_of_card.yml +187 -0
  57. data/spec/cassettes/get_custom_field_items_of_card.yml +187 -0
  58. data/spec/cassettes/get_list_of_card.yml +188 -0
  59. data/spec/cassettes/get_lists.yml +187 -0
  60. data/spec/cassettes/get_members_of_card.yml +189 -0
  61. data/spec/cassettes/get_plugin_data_of_card.yml +187 -0
  62. data/spec/cassettes/get_voters_of_card.yml +188 -0
  63. data/spec/cassettes/remove_upvote_on_a_card_when_have_not_voted.yml +366 -0
  64. data/spec/cassettes/revote_on_a_card.yml +464 -0
  65. data/spec/custom_field_item_spec.rb +74 -0
  66. data/spec/custom_field_spec.rb +76 -0
  67. data/spec/integration/basic_data/many_spec.rb +123 -0
  68. data/spec/integration/basic_data/one_spec.rb +84 -0
  69. data/spec/integration/basic_data/register_attributes_spec.rb +75 -0
  70. data/spec/integration/board_lists_spec.rb +21 -0
  71. data/spec/integration/card/add_and_remove_attachment_spec.rb +45 -0
  72. data/spec/integration/card/add_and_remove_label_spec.rb +35 -0
  73. data/spec/integration/card/add_checklist_spec.rb +32 -0
  74. data/spec/integration/card/add_comment_spec.rb +19 -0
  75. data/spec/integration/card/add_memeber_spec.rb +19 -0
  76. data/spec/integration/card/associations/actions_spec.rb +17 -0
  77. data/spec/integration/card/associations/attachments_spec.rb +18 -0
  78. data/spec/integration/card/associations/board_spec.rb +17 -0
  79. data/spec/integration/card/associations/check_item_states_spec.rb +17 -0
  80. data/spec/integration/card/associations/checklists_spec.rb +18 -0
  81. data/spec/integration/card/associations/comments_spec.rb +19 -0
  82. data/spec/integration/card/associations/cover_image_spec.rb +18 -0
  83. data/spec/integration/card/associations/custom_field_items_spec.rb +18 -0
  84. data/spec/integration/card/associations/list_spec.rb +16 -0
  85. data/spec/integration/card/associations/members_spec.rb +18 -0
  86. data/spec/integration/card/associations/plugin_data_spec.rb +18 -0
  87. data/spec/integration/card/associations/voters_spec.rb +17 -0
  88. data/spec/integration/card/close!_spec.rb +16 -0
  89. data/spec/integration/card/create_new_check_list_spec.rb +19 -0
  90. data/spec/integration/card/create_spec.rb +50 -0
  91. data/spec/integration/card/delete_spec.rb +16 -0
  92. data/spec/integration/card/find_spec.rb +67 -0
  93. data/spec/integration/card/move_to_board_spec.rb +36 -0
  94. data/spec/integration/card/move_to_list_spec.rb +20 -0
  95. data/spec/integration/card/remove_member_spec.rb +19 -0
  96. data/spec/integration/card/save_spec.rb +61 -0
  97. data/spec/integration/card/update!_spec.rb +53 -0
  98. data/spec/integration/card/vote_spec.rb +50 -0
  99. data/spec/integration/custom_field_item_spec.rb +47 -0
  100. data/spec/label_spec.rb +79 -0
  101. data/spec/spec_helper.rb +48 -23
  102. data/spec/unit/trello/association_builder/has_many_spec.rb +36 -0
  103. data/spec/unit/trello/association_builder/has_one_spec.rb +36 -0
  104. data/spec/unit/trello/association_fetcher/has_many/fetch_spec.rb +38 -0
  105. data/spec/unit/trello/association_fetcher/has_many/params_spec.rb +107 -0
  106. data/spec/unit/trello/association_fetcher/has_many_spec.rb +50 -0
  107. data/spec/unit/trello/association_fetcher/has_one/fetch_spec.rb +51 -0
  108. data/spec/unit/trello/association_fetcher/has_one/params_spec.rb +81 -0
  109. data/spec/unit/trello/association_fetcher/has_one_spec.rb +49 -0
  110. data/spec/unit/trello/association_infer_tool_spec.rb +41 -0
  111. data/spec/unit/trello/basic_data_spec.rb +54 -0
  112. data/spec/unit/trello/card_spec.rb +103 -0
  113. metadata +185 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05525435b0e8308a1adc45db693f314a91a17bb9f565efd8a87048b0dee6a4c6
4
- data.tar.gz: 96229c421c262dba867848a91b23149bce1f947bb4ef7b4df36d8cf5d1693f3a
3
+ metadata.gz: 9e1ea37b580763f0d19ff63872dd1f93cb07084de9bfe26ad391f540ec2b8d04
4
+ data.tar.gz: 58e70f72fecd1dc5e318e065a6d368f7ec176df16816e78e12e8fb872933c326
5
5
  SHA512:
6
- metadata.gz: fc91f9504c4666af90366c3b9cf18f49e50fd7afee11a9a1f9ff1bbbf70da7ab786ec1a56ffcaa3ff8f608012d439f7d9ee8fd5050a2f7ad609bc5a8941d8b09
7
- data.tar.gz: c9034334cc3d962272dad40e9beb781108ee729d2f86137faab7695bd9da00bd94789ecf2c41fcb33e83ddc3151a9f33d449bc52cffd215360958ed33402b511
6
+ metadata.gz: dc1fa46dfff77359d8b6c9e233ae8410fed8a8219860efb9ce849e394071d3781f60b40ac14ab2557cb9f2293554013abe0c4c985cde8d089464eabb2a117efa
7
+ data.tar.gz: 9d96fb20595f45782f52ba891278bbca256d41fde6e1ecef3718061e3dd0cba2fcbbaf9a4940f5a3a1e8f00518a2945149ffcc3ae280c6b1f109a3338cfe4de5
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Ruby Trello API
2
2
 
3
- [![Stories in Ready](http://badge.waffle.io/jeremytregunna/ruby-trello.png)](http://waffle.io/jeremytregunna/ruby-trello)
4
- [![Build Status](https://secure.travis-ci.org/jeremytregunna/ruby-trello.png)](http://travis-ci.org/jeremytregunna/ruby-trello)
3
+ [![Build Status](https://secure.travis-ci.org/jeremytregunna/ruby-trello.svg)](http://travis-ci.org/jeremytregunna/ruby-trello)
5
4
  [![security](https://hakiri.io/github/jeremytregunna/ruby-trello/master.svg)](https://hakiri.io/github/jeremytregunna/ruby-trello/master)
6
5
  [![Code Climate](https://codeclimate.com/github/jeremytregunna/ruby-trello/badges/gpa.svg)](https://codeclimate.com/github/jeremytregunna/ruby-trello)
7
6
 
@@ -12,20 +11,22 @@ Seriously, [check it out](http://www.trello.com/).
12
11
 
13
12
  [Full API documentation](http://www.rubydoc.info/gems/ruby-trello).
14
13
 
15
- ## Installation
16
-
17
- ```
18
- # gem install ruby-trello
19
- ```
20
-
21
14
  Full Disclosure: This library is mostly complete, if you do find anything missing or not functioning as you expect it
22
- to, please [let us know](https://trello.com/card/spot-a-bug-report-it/4f092b2ee23cb6fe6d1aaabd/17).
15
+ to, please just [create an issue](https://github.com/jeremytregunna/ruby-trello/issues/new).
23
16
 
24
- Supports Ruby 2.1.0 or newer.
17
+ ## Requirements
25
18
 
19
+ Use the newest version for Ruby 2.5.0 or newer support.
20
+ Use version 2.2.1 or earlier for Ruby 2.1 ~ 2.4 support.
26
21
  Use version 1.3.0 or earlier for Ruby 1.9.3 support.
27
22
  Use version 1.4.x or earlier for Ruby 2.0.0 support.
28
23
 
24
+ ## Installation
25
+
26
+ ```
27
+ # gem install ruby-trello
28
+ ```
29
+
29
30
  ## Configuration
30
31
 
31
32
  #### Basic authorization:
@@ -181,3 +182,41 @@ Several ways you can contribute. Documentation, code, tests, feature requests, b
181
182
  If you submit a pull request that's accepted, you'll be given commit access to this repository.
182
183
 
183
184
  Please see the `CONTRIBUTING.md` file for more information.
185
+
186
+ ## Local Development
187
+
188
+ Init all Gemfile.lock.* files
189
+
190
+ ```bash
191
+ make init
192
+ ```
193
+
194
+ Bundle install for all Ruby versions
195
+
196
+ ```bash
197
+ make bundle:all
198
+ ```
199
+
200
+ Run tests for all Ruby versions
201
+
202
+ ```bash
203
+ make test:all
204
+ ```
205
+
206
+ Run tests for each Ruby versions individual
207
+
208
+ ```bash
209
+ make test:ruby_2_5
210
+ make test:ruby_2_6
211
+ make test:ruby_2_7
212
+ make test:jruby_9_2
213
+ ```
214
+
215
+ Do development for each Ruby versions individual
216
+
217
+ ```bash
218
+ make dev:ruby_2_5
219
+ make dev:ruby_2_6
220
+ make dev:ruby_2_7
221
+ make dev:jruby_9_2
222
+ ```
data/lib/trello.rb CHANGED
@@ -37,43 +37,55 @@ require 'addressable/uri'
37
37
  #
38
38
  # Feel free to {peruse and participate in our Trello board}[https://trello.com/board/ruby-trello/4f092b2ee23cb6fe6d1aaabd]. It's completely open to the public.
39
39
  module Trello
40
- autoload :Error, 'trello/error'
41
- autoload :Action, 'trello/action'
42
- autoload :Comment, 'trello/comment'
43
- autoload :Association, 'trello/association'
44
- autoload :AssociationProxy, 'trello/association_proxy'
45
- autoload :Attachment, 'trello/attachment'
46
- autoload :CoverImage, 'trello/cover_image'
47
- autoload :BasicData, 'trello/basic_data'
48
- autoload :Board, 'trello/board'
49
- autoload :Card, 'trello/card'
50
- autoload :Checklist, 'trello/checklist'
51
- autoload :Client, 'trello/client'
52
- autoload :Configuration, 'trello/configuration'
53
- autoload :CustomField, 'trello/custom_field'
54
- autoload :CustomFieldItem, 'trello/custom_field_item'
55
- autoload :CustomFieldOption, 'trello/custom_field_option'
56
- autoload :HasActions, 'trello/has_actions'
57
- autoload :Item, 'trello/item'
58
- autoload :CheckItemState, 'trello/item_state'
59
- autoload :Label, 'trello/label'
60
- autoload :LabelName, 'trello/label_name'
61
- autoload :List, 'trello/list'
62
- autoload :Member, 'trello/member'
63
- autoload :MultiAssociation, 'trello/multi_association'
64
- autoload :Notification, 'trello/notification'
65
- autoload :Organization, 'trello/organization'
66
- autoload :PluginDatum, 'trello/plugin_datum'
67
- autoload :Request, 'trello/net'
68
- autoload :TInternet, 'trello/net'
69
- autoload :Token, 'trello/token'
70
- autoload :Webhook, 'trello/webhook'
71
- autoload :JsonUtils, 'trello/json_utils'
40
+ autoload :Error, 'trello/error'
41
+ autoload :Action, 'trello/action'
42
+ autoload :Comment, 'trello/comment'
43
+ autoload :Association, 'trello/association'
44
+ autoload :AssociationProxy, 'trello/association_proxy'
45
+ autoload :Attachment, 'trello/attachment'
46
+ autoload :CoverImage, 'trello/cover_image'
47
+ autoload :BasicData, 'trello/basic_data'
48
+ autoload :Board, 'trello/board'
49
+ autoload :Card, 'trello/card'
50
+ autoload :Checklist, 'trello/checklist'
51
+ autoload :Client, 'trello/client'
52
+ autoload :Configuration, 'trello/configuration'
53
+ autoload :CustomField, 'trello/custom_field'
54
+ autoload :CustomFieldItem, 'trello/custom_field_item'
55
+ autoload :CustomFieldOption, 'trello/custom_field_option'
56
+ autoload :HasActions, 'trello/has_actions'
57
+ autoload :Item, 'trello/item'
58
+ autoload :CheckItemState, 'trello/item_state'
59
+ autoload :Label, 'trello/label'
60
+ autoload :LabelName, 'trello/label_name'
61
+ autoload :List, 'trello/list'
62
+ autoload :Member, 'trello/member'
63
+ autoload :MultiAssociation, 'trello/multi_association'
64
+ autoload :Notification, 'trello/notification'
65
+ autoload :Organization, 'trello/organization'
66
+ autoload :PluginDatum, 'trello/plugin_datum'
67
+ autoload :Request, 'trello/net'
68
+ autoload :TInternet, 'trello/net'
69
+ autoload :Token, 'trello/token'
70
+ autoload :Webhook, 'trello/webhook'
71
+ autoload :JsonUtils, 'trello/json_utils'
72
+ autoload :AssociationInferTool, 'trello/association_infer_tool'
73
+ autoload :RegisterAttributes, 'trello/register_attributes'
72
74
 
73
75
  module Authorization
74
- autoload :AuthPolicy, 'trello/authorization'
75
- autoload :BasicAuthPolicy, 'trello/authorization'
76
- autoload :OAuthPolicy, 'trello/authorization'
76
+ autoload :AuthPolicy, 'trello/authorization'
77
+ autoload :BasicAuthPolicy, 'trello/authorization'
78
+ autoload :OAuthPolicy, 'trello/authorization'
79
+ end
80
+
81
+ module AssociationFetcher
82
+ autoload :HasMany, 'trello/association_fetcher/has_many'
83
+ autoload :HasOne, 'trello/association_fetcher/has_one'
84
+ end
85
+
86
+ module AssociationBuilder
87
+ autoload :HasMany, 'trello/association_builder/has_many'
88
+ autoload :HasOne, 'trello/association_builder/has_one'
77
89
  end
78
90
 
79
91
  # Version of the Trello API that we use by default.
data/lib/trello/action.rb CHANGED
@@ -62,7 +62,9 @@ module Trello
62
62
  List.from_response client.get("/actions/#{id}/list")
63
63
  end
64
64
 
65
- # Returns the member who created the action.
66
- one :member_creator, via: Member, path: :members, using: :member_creator_id
65
+ # Returns the list the action occurred on.
66
+ def member_creator
67
+ Member.from_response client.get("/actions/#{id}/memberCreator")
68
+ end
67
69
  end
68
70
  end
@@ -0,0 +1,17 @@
1
+ module Trello
2
+ module AssociationBuilder
3
+ class HasMany
4
+ class << self
5
+ def build(model_klass, name, options)
6
+ model_klass.class_eval do
7
+ define_method(name) do |*args|
8
+ has_many_fetcher = AssociationFetcher::HasMany.new(self, name, options)
9
+ filter_params = args[0] || {}
10
+ has_many_fetcher.fetch(filter_params)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Trello
2
+ module AssociationBuilder
3
+ class HasOne
4
+ class << self
5
+ def build(model_klass, name, options)
6
+ model_klass.class_eval do
7
+ define_method(name) do |*args|
8
+ has_one_fetcher = AssociationFetcher::HasOne.new(self, name, options)
9
+ has_one_fetcher.fetch
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module Trello
2
+ module AssociationFetcher
3
+ class HasMany
4
+ autoload :Params, 'trello/association_fetcher/has_many/params'
5
+ autoload :Fetch, 'trello/association_fetcher/has_many/fetch'
6
+
7
+ attr_reader :model, :name, :options
8
+
9
+ def initialize(model, name, options)
10
+ @model = model
11
+ @name = name
12
+ @options = options
13
+ end
14
+
15
+ def fetch(filter_params)
16
+ params = Params.new(
17
+ association_owner: model,
18
+ association_name: name,
19
+ association_options: options,
20
+ filter: filter_params
21
+ )
22
+ Fetch.execute(params)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,47 @@
1
+ module Trello
2
+ module AssociationFetcher
3
+ class HasMany
4
+ class Fetch
5
+ class << self
6
+ def execute(params)
7
+ new(params).execute
8
+ end
9
+ end
10
+
11
+ attr_reader :params
12
+
13
+ def initialize(params)
14
+ @params = params
15
+ end
16
+
17
+ def execute
18
+ resources = client.find_many(association_class, path, filter_params)
19
+
20
+ MultiAssociation.new(association_owner, resources).proxy
21
+ end
22
+
23
+ private
24
+
25
+ def client
26
+ association_owner.client
27
+ end
28
+
29
+ def association_class
30
+ params.association_class
31
+ end
32
+
33
+ def path
34
+ params.fetch_path
35
+ end
36
+
37
+ def filter_params
38
+ params.filter_params
39
+ end
40
+
41
+ def association_owner
42
+ params.association_owner
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,56 @@
1
+ module Trello
2
+ module AssociationFetcher
3
+ class HasMany
4
+ class Params
5
+ attr_reader :association_owner
6
+
7
+ def initialize(association_owner:, association_name:, association_options:, filter:)
8
+ @association_owner = association_owner
9
+ @association_name = association_name
10
+ @association_options = association_options || {}
11
+ @filter = filter || {}
12
+ end
13
+
14
+ def association_class
15
+ association_options[:via] || infer_class_on(association_name)
16
+ end
17
+
18
+ def fetch_path
19
+ "/#{parent_restful_resource}/#{parent_restful_resource_id}/#{target_restful_resource}"
20
+ end
21
+
22
+ def filter_params
23
+ default_filter.merge(filter)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :association_name, :association_options, :filter
29
+
30
+ def default_filter
31
+ association_options.reject { |k, _| %w[via in path].include?(k.to_s) }
32
+ end
33
+
34
+ def parent_restful_resource
35
+ association_options[:in] || infer_restful_resource_on(association_owner.class)
36
+ end
37
+
38
+ def parent_restful_resource_id
39
+ association_owner.id
40
+ end
41
+
42
+ def target_restful_resource
43
+ association_options[:path] || association_name
44
+ end
45
+
46
+ def infer_restful_resource_on(klass)
47
+ AssociationInferTool.infer_restful_resource_on_class(klass)
48
+ end
49
+
50
+ def infer_class_on(name)
51
+ AssociationInferTool.infer_class_on_name(name)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,25 @@
1
+ module Trello
2
+ module AssociationFetcher
3
+ class HasOne
4
+ autoload :Params, 'trello/association_fetcher/has_one/params'
5
+ autoload :Fetch, 'trello/association_fetcher/has_one/fetch'
6
+
7
+ attr_reader :model, :name, :options
8
+
9
+ def initialize(model, name, options)
10
+ @model = model
11
+ @name = name
12
+ @options = options
13
+ end
14
+
15
+ def fetch
16
+ params = Params.new(
17
+ association_owner: model,
18
+ association_name: name,
19
+ association_options: options
20
+ )
21
+ Fetch.execute(params)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ module Trello
2
+ module AssociationFetcher
3
+ class HasOne
4
+ class Fetch
5
+ class << self
6
+ def execute(params)
7
+ new(params).execute
8
+ end
9
+ end
10
+
11
+ def initialize(params)
12
+ @params = params
13
+ end
14
+
15
+ def execute
16
+ if association_restful_name
17
+ client.find(association_restful_name, association_restful_id)
18
+ else
19
+ association_class.find(association_restful_id)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :params
26
+
27
+ def client
28
+ association_owner.client
29
+ end
30
+
31
+ def association_owner
32
+ params.association_owner
33
+ end
34
+
35
+ def association_restful_name
36
+ params.association_restful_name
37
+ end
38
+
39
+ def association_restful_id
40
+ params.association_restful_id
41
+ end
42
+
43
+ def association_class
44
+ params.association_class
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end