brainstem 1.1.1 → 1.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +81 -4
- data/Gemfile.lock +9 -9
- data/README.md +134 -37
- data/brainstem.gemspec +1 -1
- data/lib/brainstem/api_docs/endpoint.rb +40 -18
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +27 -22
- data/lib/brainstem/api_docs/formatters/markdown/helper.rb +9 -0
- data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +14 -6
- data/lib/brainstem/api_docs/presenter.rb +3 -7
- data/lib/brainstem/concerns/controller_dsl.rb +138 -14
- data/lib/brainstem/concerns/presenter_dsl.rb +39 -6
- data/lib/brainstem/dsl/array_block_field.rb +25 -0
- data/lib/brainstem/dsl/block_field.rb +69 -0
- data/lib/brainstem/dsl/configuration.rb +13 -5
- data/lib/brainstem/dsl/field.rb +15 -1
- data/lib/brainstem/dsl/fields_block.rb +20 -2
- data/lib/brainstem/dsl/hash_block_field.rb +30 -0
- data/lib/brainstem/presenter.rb +10 -6
- data/lib/brainstem/presenter_validator.rb +20 -11
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/api_docs/endpoint_spec.rb +347 -14
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +106 -13
- data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +19 -0
- data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +150 -37
- data/spec/brainstem/api_docs/presenter_spec.rb +85 -18
- data/spec/brainstem/concerns/controller_dsl_spec.rb +615 -31
- data/spec/brainstem/concerns/inheritable_configuration_spec.rb +32 -9
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +99 -25
- data/spec/brainstem/dsl/array_block_field_spec.rb +43 -0
- data/spec/brainstem/dsl/block_field_spec.rb +188 -0
- data/spec/brainstem/dsl/field_spec.rb +86 -20
- data/spec/brainstem/dsl/hash_block_field_spec.rb +166 -0
- data/spec/brainstem/presenter_collection_spec.rb +24 -24
- data/spec/brainstem/presenter_spec.rb +233 -9
- data/spec/brainstem/query_strategies/filter_and_search_spec.rb +1 -1
- data/spec/spec_helpers/presenters.rb +8 -0
- data/spec/spec_helpers/schema.rb +13 -0
- metadata +15 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5785540a81ca6119478cd1c8567f3c96fa73fa6
|
4
|
+
data.tar.gz: c8ea012472c8d23767b446dfb941d0ebff5ec067
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71a2fd84129676cd27b0d669c94080f92b23417c4d7eb0ccb1366b02e2baa388fd53e0a54d0bbb0f84ea47d139bd54d13bd1e725bafa2bfc613ecb343f7cd119
|
7
|
+
data.tar.gz: e5a35cc09a2a048fa30a4633b1c7b66f2d60f56f52e1889e46466983f4b9dcddbd4828859836680b012a68cbca8fbd74ad66b2cacb6ed2e53a810ea160ee7be8
|
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,89 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
+ **1.
|
4
|
-
- Add
|
3
|
+
+ **1.3.0** - _04/12/2018_
|
4
|
+
- Add the capability to nest fields under evaluable parent blocks where the nested fields are evaluated
|
5
|
+
with the resulting value of the parent field.
|
6
|
+
```ruby
|
7
|
+
fields :tags, :array,
|
8
|
+
info: "The tags for the given category",
|
9
|
+
dynamic: -> (widget) { widget.tags } do |tag|
|
5
10
|
|
6
|
-
|
11
|
+
tag.field :name, :string,
|
12
|
+
info: "Name of the assigned tag"
|
13
|
+
end
|
14
|
+
```
|
15
|
+
- Add support for nested parameters on an endpoint.
|
16
|
+
```ruby
|
17
|
+
model_params :post do |param|
|
18
|
+
...
|
19
|
+
|
20
|
+
param.valid :message, :string, ...
|
21
|
+
|
22
|
+
param.model_params :rating do |rating_param|
|
23
|
+
...
|
24
|
+
|
25
|
+
rating_param.valid :stars, :integer, ...
|
26
|
+
end
|
27
|
+
|
28
|
+
params.valid :replies, :array,
|
29
|
+
item_type: :hash, ... do |reply_params|
|
30
|
+
|
31
|
+
reply_params.valid :message, :string,
|
32
|
+
info: "the message of the post"
|
33
|
+
|
34
|
+
...
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
38
|
+
- Fix issue with nested fields being unable to have the same name as top level fields.
|
39
|
+
- Support generation of markdown documentation for the nested block fields and nested parameters.
|
40
|
+
|
41
|
+
+ **1.2.0** - _03/29/2018_
|
42
|
+
- Add the capability to indicate an endpoint param is required with the `required` key in the options hash.
|
43
|
+
```ruby
|
44
|
+
params.valid :message, :text,
|
45
|
+
required: true,
|
46
|
+
info: "the message of the post"
|
47
|
+
```
|
48
|
+
- Add support for specifying the type of an endpoint param. For an endpoint param that has type `Array`,
|
49
|
+
type of list items can be specified using `item_type` key in the options hash.
|
50
|
+
```ruby
|
51
|
+
params.valid :viewable_by, :array,
|
52
|
+
item_type: :integer,
|
53
|
+
info: "an array of user ids that can access the post"
|
54
|
+
```
|
55
|
+
- Add support for specifying the data type of an item for a presenter field using `item_type` key in the
|
56
|
+
options hash when the field is of type `Array`.
|
57
|
+
```ruby
|
58
|
+
field :aliases, :array,
|
59
|
+
item_type: :string,
|
60
|
+
info: "an array of user ids that can access the post"
|
61
|
+
```
|
62
|
+
- Include the type and item type when generating markdown documentation for endpoint params.
|
63
|
+
- Specify the data type of a filter and available values with `items` key in the options hash. If filter is an array,
|
64
|
+
data type of items can be specified with the `item_type` property in options.
|
65
|
+
```ruby
|
66
|
+
filter :status, :string,
|
67
|
+
items: ['Started', 'Completed'],
|
68
|
+
info: "only returns elements with the given status"
|
69
|
+
|
70
|
+
filter :sprocket_ids, :array,
|
71
|
+
item_type: :integer,
|
72
|
+
info: "returns objects associated with given sprocket Ids"
|
73
|
+
```
|
74
|
+
- Add support for generating markdown documentation for the following:
|
75
|
+
- when the `required` option is specified on an endpoint param
|
76
|
+
- when the `type` and `item_type` params are specified on the endpoint param
|
77
|
+
- when the `type` and `item_type` params are specified on a presenter field
|
78
|
+
- when the `type` and `items` params are specified on a presenter filter
|
79
|
+
|
80
|
+
+ **1.1.1** - _01/15/2017_
|
81
|
+
- Add `Brainstem.mysql_use_calc_found_rows` boolean config option to utilize MySQL's [FOUND_ROWS()](https://dev.mysql.com/doc/refman/5.7/en/information-functions.html#function_found-rows) functionality to avoid issuing a new query to calculate the record count, which has the potential to up to double the response time of the endpoint.
|
82
|
+
|
83
|
+
+ **1.1.0** - _12/18/2017_
|
7
84
|
- Add `meta` key to API responses which includes `page_number`, `page_count`, and `page_size` keys.
|
8
85
|
|
9
|
-
+ **1.0.0 - _07/20/2017_
|
86
|
+
+ **1.0.0** - _07/20/2017_
|
10
87
|
- Add the capability to generate the documentation extracted from your properly annotated
|
11
88
|
presenters and controllers using `bundle exec brainstem generate [ARGS]`.
|
12
89
|
- Update Brainstem to use Ruby version 2.3.3.
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
brainstem (1.
|
4
|
+
brainstem (1.3.0)
|
5
5
|
activerecord (>= 4.1)
|
6
6
|
activesupport (>= 4.1)
|
7
7
|
|
@@ -22,30 +22,30 @@ GEM
|
|
22
22
|
arel (8.0.0)
|
23
23
|
coderay (1.1.2)
|
24
24
|
concurrent-ruby (1.0.5)
|
25
|
-
database_cleaner (1.6.
|
25
|
+
database_cleaner (1.6.1)
|
26
26
|
db-query-matchers (0.9.0)
|
27
27
|
activesupport (>= 4.0, <= 6.0)
|
28
28
|
rspec (~> 3.0)
|
29
29
|
diff-lcs (1.3)
|
30
|
-
i18n (0.9.
|
30
|
+
i18n (0.9.0)
|
31
31
|
concurrent-ruby (~> 1.0)
|
32
32
|
method_source (0.9.0)
|
33
|
-
minitest (5.
|
34
|
-
mysql2 (0.
|
33
|
+
minitest (5.10.3)
|
34
|
+
mysql2 (0.4.10)
|
35
35
|
pry (0.9.12.6)
|
36
36
|
coderay (~> 1.0)
|
37
37
|
method_source (~> 0.8)
|
38
38
|
slop (~> 3.4)
|
39
39
|
pry-nav (0.2.4)
|
40
40
|
pry (>= 0.9.10, < 0.11.0)
|
41
|
-
rake (12.
|
41
|
+
rake (12.2.1)
|
42
42
|
redcarpet (3.4.0)
|
43
43
|
rr (1.2.1)
|
44
44
|
rspec (3.7.0)
|
45
45
|
rspec-core (~> 3.7.0)
|
46
46
|
rspec-expectations (~> 3.7.0)
|
47
47
|
rspec-mocks (~> 3.7.0)
|
48
|
-
rspec-core (3.7.
|
48
|
+
rspec-core (3.7.0)
|
49
49
|
rspec-support (~> 3.7.0)
|
50
50
|
rspec-expectations (3.7.0)
|
51
51
|
diff-lcs (>= 1.2.0, < 2.0)
|
@@ -59,7 +59,7 @@ GEM
|
|
59
59
|
thread_safe (0.3.6)
|
60
60
|
tzinfo (1.2.4)
|
61
61
|
thread_safe (~> 0.1)
|
62
|
-
yard (0.9.
|
62
|
+
yard (0.9.9)
|
63
63
|
|
64
64
|
PLATFORMS
|
65
65
|
ruby
|
@@ -68,7 +68,7 @@ DEPENDENCIES
|
|
68
68
|
brainstem!
|
69
69
|
database_cleaner
|
70
70
|
db-query-matchers
|
71
|
-
mysql2
|
71
|
+
mysql2 (= 0.4.10)
|
72
72
|
pry
|
73
73
|
pry-nav
|
74
74
|
rake
|
data/README.md
CHANGED
@@ -50,13 +50,13 @@ module Api
|
|
50
50
|
default_sort_order "updated_at:desc"
|
51
51
|
|
52
52
|
# Optional filter that applies a lambda.
|
53
|
-
filter :location_name do |scope, location_name|
|
53
|
+
filter :location_name, :string, items: [:sf, :la] do |scope, location_name|
|
54
54
|
scope.joins(:locations).where("locations.name = ?", location_name)
|
55
55
|
end
|
56
56
|
|
57
57
|
# Filter with an overridable default. This will run on every request,
|
58
58
|
# passing in `bool` as `false` unless a user has specified otherwise.
|
59
|
-
filter :include_legacy_widgets, default: false do |scope, bool|
|
59
|
+
filter :include_legacy_widgets, :boolean, default: false do |scope, bool|
|
60
60
|
bool ? scope : scope.without_legacy_widgets
|
61
61
|
end
|
62
62
|
|
@@ -66,11 +66,54 @@ module Api
|
|
66
66
|
|
67
67
|
# Specify the fields to be present in the returned JSON.
|
68
68
|
fields do
|
69
|
-
field :name, :string,
|
70
|
-
|
71
|
-
field :
|
72
|
-
|
73
|
-
|
69
|
+
field :name, :string,
|
70
|
+
info: "the Widget's name"
|
71
|
+
field :legacy, :boolean,
|
72
|
+
info: "true for legacy Widgets, false otherwise",
|
73
|
+
via: :legacy?
|
74
|
+
field :longform_description, :string,
|
75
|
+
info: "feature-length description of this Widget",
|
76
|
+
optional: true
|
77
|
+
field :aliases, :array,
|
78
|
+
item_type: :string,
|
79
|
+
info: "the differnt aliases for the widget"
|
80
|
+
field :updated_at, :datetime,
|
81
|
+
info: "the time of this Widget's last update"
|
82
|
+
field :created_at, :datetime,
|
83
|
+
info: "the time at which this Widget was created"
|
84
|
+
|
85
|
+
# Fields can be nested under non-evaluable parent fields where the nested fields
|
86
|
+
# are evaluated with the presented model.
|
87
|
+
fields :permissions, :hash do |permissions_field|
|
88
|
+
|
89
|
+
# Since the permissions parent field is not evaluable, the can_edit? method is
|
90
|
+
# evaluated with the presented Widget model.
|
91
|
+
permissions_field.field :can_edit, :boolean,
|
92
|
+
via: :can_edit?,
|
93
|
+
info: "Indicates if the user can edit the widget"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Specify nested fields within an evaluable parent block field. A parent block field
|
97
|
+
# is evaluable only if one of the following options :via, :dynamic or :lookup is specified.
|
98
|
+
# The nested fields are evaluated with the value of the parent.
|
99
|
+
fields :tags, :array,
|
100
|
+
item_type: :hash,
|
101
|
+
info: "The tags for the given category",
|
102
|
+
dynamic: -> (widget) { widget.tags } do |tag|
|
103
|
+
|
104
|
+
# The name method will be evaluated with each tag model returned by the the parent block.
|
105
|
+
tag.field :name, :string,
|
106
|
+
info: "Name of the assigned tag"
|
107
|
+
end
|
108
|
+
|
109
|
+
fields :primary_category, :hash,
|
110
|
+
via: :primary_category,
|
111
|
+
info: "The primary category of the widget" do |category|
|
112
|
+
|
113
|
+
# The title method will be evaluated with each category model returned by the parent block.
|
114
|
+
category.field :title, :string,
|
115
|
+
info: "The title of the category"
|
116
|
+
end
|
74
117
|
end
|
75
118
|
|
76
119
|
# Associations can be included by providing include=association_name in the URL.
|
@@ -78,8 +121,10 @@ module Api
|
|
78
121
|
# columns on the model, otherwise the user must explicitly request associations
|
79
122
|
# to avoid unnecessary loads.
|
80
123
|
associations do
|
81
|
-
association :features, Feature,
|
82
|
-
|
124
|
+
association :features, Feature,
|
125
|
+
info: "features associated with this Widget"
|
126
|
+
association :location, Location,
|
127
|
+
info: "the location of this Widget"
|
83
128
|
end
|
84
129
|
end
|
85
130
|
end
|
@@ -447,11 +492,14 @@ class PostsPresenter < Brainstem::Presenter
|
|
447
492
|
MARKDOWN
|
448
493
|
|
449
494
|
associations do
|
450
|
-
association :author, User,
|
495
|
+
association :author, User,
|
496
|
+
info: "the author of the post"
|
451
497
|
|
452
498
|
# Temporarily disable documenting this relationship as we revamp the
|
453
499
|
# editorial system:
|
454
|
-
association :editor, User,
|
500
|
+
association :editor, User,
|
501
|
+
info: "the editor of the post",
|
502
|
+
nodoc: true
|
455
503
|
end
|
456
504
|
end
|
457
505
|
```
|
@@ -489,17 +537,19 @@ context, and it will keep the documentation isolated to that specific action:
|
|
489
537
|
|
490
538
|
```ruby
|
491
539
|
brainstem_params do
|
492
|
-
valid :global_controller_param,
|
493
|
-
|
540
|
+
valid :global_controller_param, :string,
|
541
|
+
info: "A trivial example of a param that applies to all actions."
|
494
542
|
|
495
543
|
actions :index do
|
496
544
|
# This adds a `blog_id` param to just the `index` action.
|
497
|
-
valid :blog_id,
|
545
|
+
valid :blog_id, :integer,
|
546
|
+
info: "The id of the blog to which this post belongs"
|
498
547
|
end
|
499
548
|
|
500
549
|
actions :create, :update do
|
501
550
|
# This will add an `id` param to both `create` and `update` actions.
|
502
|
-
valid :id,
|
551
|
+
valid :id, :integer,
|
552
|
+
info: "The id of the blog post"
|
503
553
|
end
|
504
554
|
end
|
505
555
|
```
|
@@ -575,21 +625,55 @@ class BlogPostsController < ApiController
|
|
575
625
|
brainstem_params do
|
576
626
|
|
577
627
|
# Add an `:category_id` param to all actions in this controller / children:
|
578
|
-
valid :category_id,
|
628
|
+
valid :category_id, :integer,
|
629
|
+
info: "(required) the category's ID"
|
579
630
|
|
580
631
|
# Do not document this additional field.
|
581
|
-
valid :lang,
|
582
|
-
|
583
|
-
|
632
|
+
valid :lang, :string,
|
633
|
+
info: "(optional) the language of the requested post",
|
634
|
+
nodoc: true
|
635
|
+
|
584
636
|
|
585
637
|
actions :show do
|
586
638
|
# Declare a nested param under the `brainstem_model_name` root key,
|
587
639
|
# i.e. `params[:blog_post][:id]`):
|
588
640
|
model_params do |post|
|
589
|
-
post.valid :id,
|
641
|
+
post.valid :id, :integer,
|
642
|
+
info: "the id of the post", required: true
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
|
647
|
+
actions :create do
|
648
|
+
model_params :post do |params|
|
649
|
+
params.valid :message, :string,
|
650
|
+
info: "the message of the post",
|
651
|
+
required: true
|
652
|
+
|
653
|
+
params.valid :viewable_by, :array,
|
654
|
+
item_type: :integer,
|
655
|
+
info: "an array of user ids that can access the post"
|
656
|
+
|
657
|
+
# Declare a nested param with an explicit root key:, i.e. `params[:rating][...]`
|
658
|
+
model_params :rating do |rating_param|
|
659
|
+
rating_param.valid :stars, :integer,
|
660
|
+
info: "the rating of the post"
|
661
|
+
end
|
662
|
+
|
663
|
+
# Declare nested array params with an explicit key:, i.e. `params[:replies][0][...]`
|
664
|
+
params.valid :replies, :array,
|
665
|
+
item_type: :hash,
|
666
|
+
info: "an array of reply params that can be created along with the post" do |reply_params|
|
667
|
+
reply_params.valid :message, :string,
|
668
|
+
info: "the message of the post"
|
669
|
+
reply_params.valid :replier_id, :integer,
|
670
|
+
info: "the ID of the user"
|
671
|
+
...
|
672
|
+
end
|
590
673
|
end
|
591
674
|
end
|
592
675
|
|
676
|
+
|
593
677
|
actions :share do
|
594
678
|
# Declare a nested param with an explicit root key:, i.e. `params[:share][...]`
|
595
679
|
model_param :share do
|
@@ -826,13 +910,13 @@ Brainstem provides a rich DSL for building presenters. This section details the
|
|
826
910
|
|
827
911
|
```ruby
|
828
912
|
# Optional filter that applies a lambda.
|
829
|
-
filter :location_name do |scope, location_name|
|
913
|
+
filter :location_name, :string do |scope, location_name|
|
830
914
|
scope.joins(:locations).where("locations.name = ?", location_name)
|
831
915
|
end
|
832
916
|
|
833
917
|
# Filter with an overridable default. This will run on every request,
|
834
918
|
# passing in `bool` as `false` unless a user has specified otherwise.
|
835
|
-
filter :include_legacy_widgets, default: false do |scope, bool|
|
919
|
+
filter :include_legacy_widgets, :boolean, default: false do |scope, bool|
|
836
920
|
bool ? scope : scope.without_legacy_widgets
|
837
921
|
end
|
838
922
|
```
|
@@ -892,6 +976,9 @@ Brainstem provides a rich DSL for building presenters. This section details the
|
|
892
976
|
field :dynamic_name, :string,
|
893
977
|
info: "a formatted name for this Widget",
|
894
978
|
dynamic: lambda { |widget| "This Widget's name is #{widget.name}" }
|
979
|
+
field :aliases, :array,
|
980
|
+
item_type: :string,
|
981
|
+
info: "the differnt aliases for the widget"
|
895
982
|
field :longform_description, :string,
|
896
983
|
info: "feature-length description of this Widget",
|
897
984
|
optional: true
|
@@ -900,6 +987,16 @@ Brainstem provides a rich DSL for building presenters. This section details the
|
|
900
987
|
fields :permissions do
|
901
988
|
field :access_level, :integer
|
902
989
|
end
|
990
|
+
|
991
|
+
# Fields can be nested under executable parent blocks.
|
992
|
+
# Sub fields are evaluated with the value of the parent block.
|
993
|
+
fields :tags, :array,
|
994
|
+
info: "The tags for the given category",
|
995
|
+
dynamic: -> (widget) { widget.tags } do |tag|
|
996
|
+
|
997
|
+
tag.field :name, :string,
|
998
|
+
info: "Name of the assigned tag"
|
999
|
+
end
|
903
1000
|
end
|
904
1001
|
```
|
905
1002
|
|
@@ -946,12 +1043,12 @@ the `lookup` will be used.
|
|
946
1043
|
```ruby
|
947
1044
|
associations do
|
948
1045
|
association :current_user_groups, Group,
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
1046
|
+
info: "the Groups for the current user",
|
1047
|
+
lookup: lambda { |models|
|
1048
|
+
Group.where(subject_id: models.map(&:id)
|
1049
|
+
.where(user_id: current_user.id)
|
1050
|
+
.group_by { |group| group.subject_id }
|
1051
|
+
}
|
955
1052
|
end
|
956
1053
|
```
|
957
1054
|
|
@@ -963,15 +1060,15 @@ the `lookup` will be used.
|
|
963
1060
|
```ruby
|
964
1061
|
fields do
|
965
1062
|
field :current_user_post_count, Post,
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
1063
|
+
info: "count of Posts the current_user has for this model",
|
1064
|
+
lookup: lambda { |models|
|
1065
|
+
lookup = Post.where(subject_id: models.map(&:id)
|
1066
|
+
.where(user_id: current_user.id)
|
1067
|
+
.group_by { |post| post.subject_id }
|
1068
|
+
|
1069
|
+
lookup
|
1070
|
+
},
|
1071
|
+
lookup_fetch: lambda { |lookup, model| lookup[model.id] }
|
975
1072
|
end
|
976
1073
|
```
|
977
1074
|
|
data/brainstem.gemspec
CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |gem|
|
|
27
27
|
gem.add_development_dependency "rr"
|
28
28
|
gem.add_development_dependency "rspec", "~> 3.5"
|
29
29
|
gem.add_development_dependency "sqlite3"
|
30
|
-
gem.add_development_dependency "mysql2"
|
30
|
+
gem.add_development_dependency "mysql2", "0.4.10"
|
31
31
|
gem.add_development_dependency "database_cleaner"
|
32
32
|
gem.add_development_dependency "yard"
|
33
33
|
gem.add_development_dependency "pry"
|