simple_jsonapi 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rubocop.yml +131 -0
  4. data/CHANGELOG.md +2 -0
  5. data/Gemfile +5 -0
  6. data/Jenkinsfile +92 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +532 -0
  9. data/Rakefile +10 -0
  10. data/lib/simple_jsonapi.rb +112 -0
  11. data/lib/simple_jsonapi/definition/attribute.rb +45 -0
  12. data/lib/simple_jsonapi/definition/base.rb +50 -0
  13. data/lib/simple_jsonapi/definition/concerns/has_links_object.rb +36 -0
  14. data/lib/simple_jsonapi/definition/concerns/has_meta_object.rb +36 -0
  15. data/lib/simple_jsonapi/definition/error.rb +70 -0
  16. data/lib/simple_jsonapi/definition/error_source.rb +29 -0
  17. data/lib/simple_jsonapi/definition/link.rb +27 -0
  18. data/lib/simple_jsonapi/definition/meta.rb +27 -0
  19. data/lib/simple_jsonapi/definition/relationship.rb +60 -0
  20. data/lib/simple_jsonapi/definition/resource.rb +104 -0
  21. data/lib/simple_jsonapi/error_serializer.rb +76 -0
  22. data/lib/simple_jsonapi/errors/bad_request.rb +11 -0
  23. data/lib/simple_jsonapi/errors/exception_serializer.rb +6 -0
  24. data/lib/simple_jsonapi/errors/wrapped_error.rb +35 -0
  25. data/lib/simple_jsonapi/errors/wrapped_error_serializer.rb +35 -0
  26. data/lib/simple_jsonapi/helpers/exceptions.rb +39 -0
  27. data/lib/simple_jsonapi/helpers/serializer_inferrer.rb +136 -0
  28. data/lib/simple_jsonapi/helpers/serializer_methods.rb +36 -0
  29. data/lib/simple_jsonapi/node/attributes.rb +51 -0
  30. data/lib/simple_jsonapi/node/base.rb +91 -0
  31. data/lib/simple_jsonapi/node/data/collection.rb +25 -0
  32. data/lib/simple_jsonapi/node/data/singular.rb +26 -0
  33. data/lib/simple_jsonapi/node/document/base.rb +62 -0
  34. data/lib/simple_jsonapi/node/document/collection.rb +17 -0
  35. data/lib/simple_jsonapi/node/document/errors.rb +17 -0
  36. data/lib/simple_jsonapi/node/document/singular.rb +17 -0
  37. data/lib/simple_jsonapi/node/error.rb +55 -0
  38. data/lib/simple_jsonapi/node/error_source.rb +40 -0
  39. data/lib/simple_jsonapi/node/errors.rb +28 -0
  40. data/lib/simple_jsonapi/node/included.rb +45 -0
  41. data/lib/simple_jsonapi/node/object_links.rb +40 -0
  42. data/lib/simple_jsonapi/node/object_meta.rb +40 -0
  43. data/lib/simple_jsonapi/node/relationship.rb +79 -0
  44. data/lib/simple_jsonapi/node/relationship_data/base.rb +53 -0
  45. data/lib/simple_jsonapi/node/relationship_data/collection.rb +32 -0
  46. data/lib/simple_jsonapi/node/relationship_data/singular.rb +33 -0
  47. data/lib/simple_jsonapi/node/relationships.rb +60 -0
  48. data/lib/simple_jsonapi/node/resource/base.rb +21 -0
  49. data/lib/simple_jsonapi/node/resource/full.rb +49 -0
  50. data/lib/simple_jsonapi/node/resource/linkage.rb +25 -0
  51. data/lib/simple_jsonapi/parameters/fields_spec.rb +45 -0
  52. data/lib/simple_jsonapi/parameters/include_spec.rb +57 -0
  53. data/lib/simple_jsonapi/parameters/sort_spec.rb +107 -0
  54. data/lib/simple_jsonapi/serializer.rb +89 -0
  55. data/lib/simple_jsonapi/version.rb +3 -0
  56. data/simple_jsonapi.gemspec +29 -0
  57. data/test/errors/bad_request_test.rb +34 -0
  58. data/test/errors/error_serializer_test.rb +229 -0
  59. data/test/errors/exception_serializer_test.rb +25 -0
  60. data/test/errors/wrapped_error_serializer_test.rb +91 -0
  61. data/test/errors/wrapped_error_test.rb +44 -0
  62. data/test/parameters/fields_spec_test.rb +56 -0
  63. data/test/parameters/include_spec_test.rb +58 -0
  64. data/test/parameters/sort_spec_test.rb +65 -0
  65. data/test/resources/attributes_test.rb +109 -0
  66. data/test/resources/extras_test.rb +70 -0
  67. data/test/resources/id_and_type_test.rb +76 -0
  68. data/test/resources/inclusion_test.rb +134 -0
  69. data/test/resources/links_test.rb +63 -0
  70. data/test/resources/meta_test.rb +49 -0
  71. data/test/resources/relationships_test.rb +262 -0
  72. data/test/resources/sorting_test.rb +79 -0
  73. data/test/resources/sparse_fieldset_test.rb +160 -0
  74. data/test/root_objects_test.rb +165 -0
  75. data/test/test_helper.rb +31 -0
  76. metadata +235 -0
@@ -0,0 +1,58 @@
1
+ require 'test_helper'
2
+
3
+ class IncludeSpecTest < Minitest::Spec
4
+ describe SimpleJsonapi::Parameters::IncludeSpec do
5
+ describe "parsing" do
6
+ it "accepts a comma-delimited list" do
7
+ spec = SimpleJsonapi::Parameters::IncludeSpec.new("order,product")
8
+ assert_equal({ order: {}, product: {} }, spec.to_h)
9
+ end
10
+
11
+ it "accepts a list of strings" do
12
+ spec = SimpleJsonapi::Parameters::IncludeSpec.new("order", "product")
13
+ assert_equal({ order: {}, product: {} }, spec.to_h)
14
+ end
15
+
16
+ it "accepts an array of strings" do
17
+ spec = SimpleJsonapi::Parameters::IncludeSpec.new(["order", "product"])
18
+ assert_equal({ order: {}, product: {} }, spec.to_h)
19
+ end
20
+ end
21
+
22
+ describe "nested includes" do
23
+ let(:nested_spec) do
24
+ SimpleJsonapi::Parameters::IncludeSpec.new(
25
+ "rel_1",
26
+ "rel_1.rel_1_a",
27
+ "rel_1.rel_1_a.rel_1_a_i",
28
+ "rel_1.rel_1_a.rel_1_a_ii",
29
+ "rel_1.rel_1_b",
30
+ )
31
+ end
32
+
33
+ it "parses nested includes" do
34
+ expected_value = {
35
+ rel_1: {
36
+ rel_1_a: {
37
+ rel_1_a_i: {},
38
+ rel_1_a_ii: {},
39
+ },
40
+ rel_1_b: {},
41
+ },
42
+ }
43
+ assert_equal(expected_value, nested_spec.to_h)
44
+ end
45
+
46
+ it "extracts a nested include spec" do
47
+ expected_value = {
48
+ rel_1_a: {
49
+ rel_1_a_i: {},
50
+ rel_1_a_ii: {},
51
+ },
52
+ rel_1_b: {},
53
+ }
54
+ assert_equal(expected_value, nested_spec[:rel_1].to_h)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,65 @@
1
+ require 'test_helper'
2
+
3
+ class SortSpecTest < Minitest::Spec
4
+ describe SimpleJsonapi::Parameters::SortSpec do
5
+ it "interprets a field name as ascending" do
6
+ spec = SimpleJsonapi::Parameters::SortSpec.new("orders" => "customer_name")
7
+
8
+ assert_equal [:customer_name], spec[:orders].map(&:field)
9
+ assert_equal [:asc], spec[:orders].map(&:dir)
10
+ end
11
+
12
+ it "interprets a leading hyphen as descending" do
13
+ spec = SimpleJsonapi::Parameters::SortSpec.new("orders" => "-customer_name")
14
+
15
+ assert_equal [:customer_name], spec[:orders].map(&:field)
16
+ assert_equal [:desc], spec[:orders].map(&:dir)
17
+ end
18
+
19
+ it "accepts comma-delimited strings" do
20
+ spec = SimpleJsonapi::Parameters::SortSpec.new(
21
+ "orders" => "customer_name,-description",
22
+ "line_items" => "-order_id,product_id",
23
+ )
24
+
25
+ assert_equal [:customer_name, :description], spec[:orders].map(&:field)
26
+ assert_equal [:asc, :desc], spec[:orders].map(&:dir)
27
+
28
+ assert_equal [:order_id, :product_id], spec[:line_items].map(&:field)
29
+ assert_equal [:desc, :asc], spec[:line_items].map(&:dir)
30
+ end
31
+
32
+ it "accepts arrays of strings" do
33
+ spec = SimpleJsonapi::Parameters::SortSpec.new(
34
+ "orders" => ["customer_name", "-description"],
35
+ "line_items" => ["-order_id", "product_id"],
36
+ )
37
+
38
+ assert_equal [:customer_name, :description], spec[:orders].map(&:field)
39
+ assert_equal [:asc, :desc], spec[:orders].map(&:dir)
40
+
41
+ assert_equal [:order_id, :product_id], spec[:line_items].map(&:field)
42
+ assert_equal [:desc, :asc], spec[:line_items].map(&:dir)
43
+ end
44
+ end
45
+
46
+ describe SimpleJsonapi::Parameters::SortFieldSpec do
47
+ it "interprets a field name as ascending" do
48
+ field_spec = SimpleJsonapi::Parameters::SortFieldSpec.new("customer_name")
49
+
50
+ assert_equal :customer_name, field_spec.field
51
+ assert_equal :asc, field_spec.dir
52
+ assert_equal true, field_spec.asc?
53
+ assert_equal false, field_spec.desc?
54
+ end
55
+
56
+ it "interprets a leading hyphen as descending" do
57
+ field_spec = SimpleJsonapi::Parameters::SortFieldSpec.new("-customer_name")
58
+
59
+ assert_equal :customer_name, field_spec.field
60
+ assert_equal :desc, field_spec.dir
61
+ assert_equal false, field_spec.asc?
62
+ assert_equal true, field_spec.desc?
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,109 @@
1
+ require 'test_helper'
2
+
3
+ class AttributesTest < Minitest::Spec
4
+ class Thing < TestModel
5
+ attr_accessor :name, :number, :timestamp
6
+ end
7
+
8
+ describe "Resource attributes" do
9
+ describe "rendering" do
10
+ class BasicSerializer < SimpleJsonapi::Serializer
11
+ attribute :name
12
+ attribute(:number) { |res| res.number&.to_i }
13
+ attribute(:date) { |res| res.timestamp&.to_date&.iso8601 }
14
+ end
15
+
16
+ let(:thing) do
17
+ Thing.new(
18
+ id: 1,
19
+ name: "Thing 1",
20
+ number: 2.718,
21
+ timestamp: Time.new(2017, 7, 15, 12, 34, 56),
22
+ )
23
+ end
24
+
25
+ let(:serialized) { SimpleJsonapi.render_resource(thing, serializer: BasicSerializer) }
26
+ let(:attributes) { serialized.dig(:data, :attributes) }
27
+
28
+ it "calls the matching method by default" do
29
+ assert_equal "Thing 1", attributes[:name]
30
+ end
31
+
32
+ it "executes a proc" do
33
+ assert_equal 2, attributes[:number]
34
+ end
35
+ end
36
+
37
+ describe "conditional rendering" do
38
+ class ConditionalSerializer < SimpleJsonapi::Serializer
39
+ attribute :name, if: ->(res) { res.name.starts_with?("A") }
40
+ attribute :number, unless: ->(res) { res.name.starts_with?("Z") }
41
+ attribute(:date) { |res| res.timestamp&.to_date&.iso8601 }
42
+ end
43
+
44
+ let(:thing_a) { Thing.new(id: 1, name: "Abigail", number: 123) }
45
+ let(:thing_z) { Thing.new(id: 2, name: "Zachary", number: 456) }
46
+
47
+ def attributes_for(thing)
48
+ SimpleJsonapi.render_resource(thing, serializer: ConditionalSerializer).dig(:data, :attributes)
49
+ end
50
+
51
+ it "renders the attribute when the 'if' is truthy" do
52
+ assert_equal "Abigail", attributes_for(thing_a)[:name]
53
+ end
54
+
55
+ it "renders the attribute when the 'unless' is falsey" do
56
+ assert_equal 123, attributes_for(thing_a)[:number]
57
+ end
58
+
59
+ it "omits the attribute when the 'if' is falsey" do
60
+ refute_includes attributes_for(thing_z).keys, :name
61
+ end
62
+
63
+ it "omits the attribute when the 'unless' is truthy" do
64
+ refute_includes attributes_for(thing_z).keys, :number
65
+ end
66
+ end
67
+
68
+ describe "validation" do
69
+ it "cannot be named id" do
70
+ assert_raises(ArgumentError) do
71
+ Class.new(SimpleJsonapi::Serializer) { attribute :id }
72
+ end
73
+ end
74
+
75
+ it "cannot be named type" do
76
+ assert_raises(ArgumentError) do
77
+ Class.new(SimpleJsonapi::Serializer) { attribute :type }
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "documentation" do
83
+ class DocSerializer < SimpleJsonapi::Serializer
84
+ attribute :name
85
+ attribute :number, type: :numeric, description: "a number"
86
+ attribute :colors, type: :string, array: true
87
+ end
88
+
89
+ it "builds an attribute without documentation" do
90
+ attr_defn = DocSerializer.definition.attribute_definitions[:name]
91
+ assert_nil attr_defn.data_type
92
+ assert_equal false, attr_defn.array
93
+ assert_nil attr_defn.description
94
+ end
95
+
96
+ it "stores the type and description" do
97
+ attr_defn = DocSerializer.definition.attribute_definitions[:number]
98
+ assert_equal :numeric, attr_defn.data_type
99
+ assert_equal "a number", attr_defn.description
100
+ end
101
+
102
+ it "stores the type and array-ness" do
103
+ attr_defn = DocSerializer.definition.attribute_definitions[:colors]
104
+ assert_equal :string, attr_defn.data_type
105
+ assert_equal true, attr_defn.array
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,70 @@
1
+ require 'test_helper'
2
+
3
+ class ExtrasTest < Minitest::Spec
4
+ class Order < TestModel
5
+ attr_accessor :order_number, :total_price, :customer
6
+ end
7
+
8
+ class Customer < TestModel
9
+ attr_accessor :first_name, :last_name
10
+ end
11
+
12
+ class OrderSerializer < SimpleJsonapi::Serializer
13
+ attribute(:order_number) { |_order| @override_order_number }
14
+ attribute(:total_price) { |_order| @override_total_price }
15
+ has_one :customer, if: ->(_order) { @has_a_customer }
16
+ end
17
+
18
+ class CustomerSerializer < SimpleJsonapi::Serializer
19
+ attribute(:first_name) { |_customer| @override_first_name }
20
+ attribute(:last_name) { |_customer| @override_last_name }
21
+ end
22
+
23
+ let(:order) { Order.new(id: 1, order_number: "123", total_price: "5.99", customer: customer) }
24
+ let(:customer) { Customer.new(id: 2, first_name: "Andrei", last_name: "Romanov") }
25
+
26
+ describe "Extra contextual data" do
27
+ describe "on the root resource" do
28
+ it "runs when no extras are provided" do
29
+ serialized = SimpleJsonapi.render_resource(order)
30
+ assert_nil serialized.dig(:data, :attributes, :order_number)
31
+ end
32
+
33
+ it "makes the extras hash available to the attributes" do
34
+ serialized = SimpleJsonapi.render_resource(
35
+ order,
36
+ extras: {
37
+ override_order_number: "999",
38
+ override_total_price: 1.00,
39
+ },
40
+ )
41
+ assert_equal "999", serialized.dig(:data, :attributes, :order_number)
42
+ assert_equal 1.00, serialized.dig(:data, :attributes, :total_price)
43
+ end
44
+
45
+ it "makes the extras hash available to an if predicate" do
46
+ serialized = SimpleJsonapi.render_resource(order, extras: { has_a_customer: false })
47
+ assert_nil serialized.dig(:data, :relationships, :customer, :data)
48
+
49
+ serialized = SimpleJsonapi.render_resource(order, extras: { has_a_customer: true })
50
+ assert_equal "2", serialized.dig(:data, :relationships, :customer, :data, :id)
51
+ end
52
+ end
53
+
54
+ describe "on a related resource" do
55
+ it "makes the extras hash available to the attributes" do
56
+ serialized = SimpleJsonapi.render_resource(
57
+ order,
58
+ include: "customer",
59
+ extras: {
60
+ has_a_customer: true,
61
+ override_first_name: "Jane",
62
+ override_last_name: "Doe",
63
+ },
64
+ )
65
+ assert_equal "Jane", serialized.dig(:included, 0, :attributes, :first_name)
66
+ assert_equal "Doe", serialized.dig(:included, 0, :attributes, :last_name)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,76 @@
1
+ require 'test_helper'
2
+
3
+ class IdAndTypeTest < Minitest::Spec
4
+ class Thing < TestModel
5
+ end
6
+
7
+ let(:thing) { Thing.new(id: 4) }
8
+
9
+ describe "Resource id and type" do
10
+ describe "default implementation" do
11
+ class DefaultsSerializer < SimpleJsonapi::Serializer
12
+ end
13
+
14
+ it "calls the id method" do
15
+ serialized = SimpleJsonapi.render_resource(thing, serializer: DefaultsSerializer)
16
+ assert_equal "4", serialized.dig(:data, :id)
17
+ end
18
+
19
+ it "uses the objects type" do
20
+ serialized = SimpleJsonapi.render_resource(thing, serializer: DefaultsSerializer)
21
+ assert_equal "things", serialized.dig(:data, :type)
22
+ end
23
+ end
24
+
25
+ describe "defined with blocks" do
26
+ class BlocksSerializer < SimpleJsonapi::Serializer
27
+ id { |thing| "#{thing.id}-the-thing" }
28
+ type { |thing| thing.class.name }
29
+ end
30
+
31
+ it "calls the block" do
32
+ serialized = SimpleJsonapi.render_resource(thing, serializer: BlocksSerializer)
33
+ assert_equal "4-the-thing", serialized.dig(:data, :id)
34
+ end
35
+
36
+ it "calls the block" do
37
+ serialized = SimpleJsonapi.render_resource(thing, serializer: BlocksSerializer)
38
+ assert_equal "IdAndTypeTest::Thing", serialized.dig(:data, :type)
39
+ end
40
+ end
41
+
42
+ describe "defined with procs" do
43
+ class ProcsSerializer < SimpleJsonapi::Serializer
44
+ id ->(thing) { "#{thing.id}-the-thing" }
45
+ type ->(thing) { thing.class.name }
46
+ end
47
+
48
+ it "calls the proc" do
49
+ serialized = SimpleJsonapi.render_resource(thing, serializer: ProcsSerializer)
50
+ assert_equal "4-the-thing", serialized.dig(:data, :id)
51
+ end
52
+
53
+ it "calls the proc" do
54
+ serialized = SimpleJsonapi.render_resource(thing, serializer: ProcsSerializer)
55
+ assert_equal "IdAndTypeTest::Thing", serialized.dig(:data, :type)
56
+ end
57
+ end
58
+
59
+ describe "defined with constant values" do
60
+ class ValuesSerializer < SimpleJsonapi::Serializer
61
+ id 42
62
+ type "values"
63
+ end
64
+
65
+ it "renders the value" do
66
+ serialized = SimpleJsonapi.render_resource(thing, serializer: ValuesSerializer)
67
+ assert_equal "42", serialized.dig(:data, :id)
68
+ end
69
+
70
+ it "renders the value" do
71
+ serialized = SimpleJsonapi.render_resource(thing, serializer: ValuesSerializer)
72
+ assert_equal "values", serialized.dig(:data, :type)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,134 @@
1
+ require 'test_helper'
2
+
3
+ class InclusionTest < Minitest::Spec
4
+ class Order < TestModel
5
+ attr_writer :line_items
6
+
7
+ def line_items
8
+ @line_items ||= []
9
+ end
10
+ end
11
+
12
+ class LineItem < TestModel
13
+ attr_accessor :product
14
+ end
15
+
16
+ class Product < TestModel
17
+ end
18
+
19
+ class OrderSerializer < SimpleJsonapi::Serializer
20
+ has_many :line_items do
21
+ data(&:line_items)
22
+ end
23
+ has_many :products do
24
+ data { |o| o.line_items.flat_map(&:product).compact.uniq }
25
+ end
26
+ end
27
+
28
+ class LineItemSerializer < SimpleJsonapi::Serializer
29
+ has_one :product do
30
+ data(&:product)
31
+ end
32
+ end
33
+
34
+ class ProductSerializer < SimpleJsonapi::Serializer
35
+ end
36
+
37
+ let(:order) { Order.new(id: 10, line_items: [line_item]) }
38
+ let(:line_item) { LineItem.new(id: 20, product: product) }
39
+ let(:product) { Product.new(id: 30) }
40
+
41
+ let(:included_line_item) do
42
+ serialized[:included]&.find { |res| res[:type] == "line_items" }
43
+ end
44
+ let(:included_product) do
45
+ serialized[:included]&.find { |res| res[:type] == "products" }
46
+ end
47
+
48
+ # def meta_for_resource(resource, relationship_name)
49
+ # resource.dig(:relationships, relationship_name, :data, :meta)
50
+ # end
51
+
52
+ describe "Including related resources" do
53
+ describe "without an include parameter" do
54
+ let(:serialized) { SimpleJsonapi.render_resource(order) }
55
+
56
+ it "doesn't include any related resources" do
57
+ refute serialized.key?(:included)
58
+ end
59
+
60
+ it "adds meta information to the root resource" do
61
+ assert_equal(
62
+ { included: false },
63
+ serialized.dig(:data, :relationships, :line_items, :data, 0, :meta) # has_many
64
+ )
65
+ end
66
+ end
67
+
68
+ describe "with one level of inclusion" do
69
+ let(:serialized) do
70
+ SimpleJsonapi.render_resource(order, include: "line_items")
71
+ end
72
+
73
+ it "includes the related resources" do
74
+ refute_nil included_line_item
75
+ assert_nil included_product
76
+ end
77
+
78
+ it "adds meta information to the root resource" do
79
+ assert_equal(
80
+ { included: true },
81
+ serialized.dig(:data, :relationships, :line_items, :data, 0, :meta) # has_many
82
+ )
83
+ end
84
+
85
+ it "adds meta information to the included resource" do
86
+ assert_equal(
87
+ { included: false },
88
+ included_line_item.dig(:relationships, :product, :data, :meta) # has_one
89
+ )
90
+ end
91
+ end
92
+
93
+ describe "with two levels of inclusion" do
94
+ let(:serialized) do
95
+ SimpleJsonapi.render_resource(order, include: "line_items,line_items.product")
96
+ end
97
+
98
+ it "includes the related resources" do
99
+ refute_nil included_line_item
100
+ refute_nil included_product
101
+ end
102
+
103
+ it "adds meta information to the base resource" do
104
+ assert_equal(
105
+ { included: true },
106
+ serialized.dig(:data, :relationships, :line_items, :data, 0, :meta) # has_many
107
+ )
108
+ end
109
+
110
+ it "adds meta information to the included resource" do
111
+ assert_equal(
112
+ { included: true },
113
+ included_line_item.dig(:relationships, :product, :data, :meta) # has_one
114
+ )
115
+ end
116
+ end
117
+
118
+ describe "duplicate resources" do
119
+ let(:serialized) do
120
+ SimpleJsonapi.render_resource(order, include: "line_items,line_items.product,products")
121
+ end
122
+
123
+ it "includes the related object only once" do
124
+ order_to_products_linkage = serialized.dig(:data, :relationships, :products, :data, 0) # has_many
125
+ line_item_to_product_linkage = included_line_item.dig(:relationships, :product, :data) # has_one
126
+
127
+ assert_equal product.id.to_s, order_to_products_linkage[:id]
128
+ assert_equal product.id.to_s, line_item_to_product_linkage[:id]
129
+
130
+ assert_equal(1, serialized[:included].count { |res| res[:type] == "products" })
131
+ end
132
+ end
133
+ end
134
+ end