angus 0.0.1 → 0.0.2

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 (44) hide show
  1. data/bin/angus +13 -0
  2. data/lib/angus.rb +6 -4
  3. data/lib/angus/base.rb +119 -0
  4. data/lib/angus/base_actions.rb +38 -0
  5. data/lib/angus/base_resource.rb +14 -0
  6. data/lib/angus/command.rb +27 -0
  7. data/lib/angus/generator.rb +141 -0
  8. data/lib/angus/generator/templates/Gemfile +5 -0
  9. data/lib/angus/generator/templates/README.md +0 -0
  10. data/lib/angus/generator/templates/config.ru.erb +10 -0
  11. data/lib/angus/generator/templates/definitions/messages.yml +0 -0
  12. data/lib/angus/generator/templates/definitions/operations.yml.erb +21 -0
  13. data/lib/angus/generator/templates/definitions/representations.yml +0 -0
  14. data/lib/angus/generator/templates/definitions/service.yml.erb +3 -0
  15. data/lib/angus/generator/templates/resources/resource.rb.erb +6 -0
  16. data/lib/angus/generator/templates/services/service.rb.erb +4 -0
  17. data/lib/angus/marshallings/base.rb +2 -0
  18. data/lib/angus/marshallings/marshalling.rb +136 -0
  19. data/lib/angus/marshallings/unmarshalling.rb +29 -0
  20. data/lib/angus/renders/base.rb +2 -0
  21. data/lib/angus/renders/html_render.rb +9 -0
  22. data/lib/angus/renders/json_render.rb +15 -0
  23. data/lib/angus/request_handler.rb +62 -0
  24. data/lib/angus/resource_definition.rb +78 -0
  25. data/lib/angus/response.rb +53 -0
  26. data/lib/angus/responses.rb +232 -0
  27. data/lib/angus/rspec/spec_helper.rb +3 -0
  28. data/lib/angus/rspec/support/examples.rb +64 -0
  29. data/lib/angus/rspec/support/examples/describe_errors.rb +36 -0
  30. data/lib/angus/rspec/support/matchers/operation_response_matchers.rb +117 -0
  31. data/lib/angus/rspec/support/operation_response.rb +57 -0
  32. data/lib/angus/utils.rb +2 -0
  33. data/lib/angus/utils/params.rb +24 -0
  34. data/lib/angus/utils/string.rb +27 -0
  35. data/lib/angus/version.rb +2 -2
  36. data/spec/angus/rspec/examples/describe_errors_spec.rb +64 -0
  37. data/spec/spec_helper.rb +27 -0
  38. metadata +181 -19
  39. data/.gitignore +0 -17
  40. data/Gemfile +0 -4
  41. data/LICENSE.txt +0 -22
  42. data/README.md +0 -29
  43. data/Rakefile +0 -1
  44. data/angus.gemspec +0 -23
@@ -0,0 +1,3 @@
1
+ require 'angus'
2
+
3
+ Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |f| require f }
@@ -0,0 +1,64 @@
1
+ require 'rspec'
2
+
3
+ require_relative 'examples/describe_errors'
4
+
5
+ # Describes an operation
6
+ #
7
+ # Builds a describe block that:
8
+ # - includes Rack::Test::Methods
9
+ # - defines <b>app</b> method that returns the service
10
+ # - defines <b>response</b> method
11
+ #
12
+ # @param operation the operation being specified, ex: GET /profiles
13
+ # @param service the Service (rack app) that exposes the operation
14
+ def describe_operation(operation, service, &block)
15
+
16
+ describe(operation) do
17
+ include Rack::Test::Methods
18
+
19
+ define_method :app do
20
+ service
21
+ end
22
+
23
+ define_method :method_missing do |m, *args, &block|
24
+ if m.to_s =~ /_path$/
25
+ service.public_send m, *args, &block
26
+ else
27
+ super(m, *args, &block)
28
+ end
29
+ end
30
+
31
+ def response
32
+ if @response && @response.wraps?(last_response)
33
+ return @response
34
+ else
35
+ @response = OperationResponse.new(last_response)
36
+ end
37
+ end
38
+
39
+
40
+ define_singleton_method :describe_errors do |errors_spec|
41
+ errors_spec.each do |e, status_code|
42
+
43
+ __caller = caller[2..-1]
44
+ it "raises #{e} with status = #{status_code}", :caller => __caller do
45
+
46
+ Angus::RSpec::Examples::DescribeErrors.mock_service(service, e, self) do
47
+ get '/'
48
+
49
+ if response.http_status_code != status_code
50
+ e = RSpec::Expectations::ExpectationNotMetError.new(
51
+ "Expected status: #{status_code}, got: #{response.http_status_code}"
52
+ )
53
+ e.set_backtrace(__caller)
54
+ raise e
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end
61
+
62
+ instance_eval(&block)
63
+ end
64
+ end
@@ -0,0 +1,36 @@
1
+ module Angus
2
+ module RSpec
3
+ module Examples
4
+ module DescribeErrors
5
+
6
+ # Mock a base service by redefining .app method, yields the given block, and
7
+ # restores the original .app method
8
+ #
9
+ # @param [Class] Class that responds to .get
10
+ # @param error Exception that will be raised when executing a get route
11
+ # @param [#app] example Runing example
12
+ def self.mock_service(base, error, example, &block)
13
+
14
+ mock = Class.new(base) do
15
+ get '/' do
16
+ raise error
17
+ end
18
+ end
19
+
20
+ example.define_singleton_method :app do
21
+ mock
22
+ end
23
+
24
+ begin
25
+ yield
26
+ ensure
27
+ example.define_singleton_method :app do
28
+ base
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,117 @@
1
+ require 'rspec'
2
+
3
+ RSpec::Matchers.define :have_no_data do
4
+ match do |operation_response|
5
+ operation_response.body == {}
6
+ end
7
+
8
+ failure_message_for_should do |operation_response|
9
+ "expected that #{operation_response.body} would be {}"
10
+ end
11
+
12
+ failure_message_for_should_not do |operation_response|
13
+ "expected that #{operation_response.body} would not be {}"
14
+ end
15
+
16
+ description do
17
+ "have no data"
18
+ end
19
+ end
20
+
21
+ RSpec::Matchers.define :be_success do
22
+ match do |operation_response|
23
+ operation_response.success?
24
+ end
25
+
26
+ failure_message_for_should do |operation_response|
27
+ "expected that (#{operation_response.http_status_code}, #{operation_response.status}) would be (200, success)"
28
+ end
29
+
30
+ failure_message_for_should_not do |operation_response|
31
+ "expected that (#{operation_response.http_status_code}, #{operation_response.status}) would not be (200, success)"
32
+ end
33
+
34
+ description do
35
+ "be a (200, success) response"
36
+ end
37
+ end
38
+
39
+ RSpec::Matchers.define :be_forbidden do
40
+ match do |operation_response|
41
+ operation_response.forbidden?
42
+ end
43
+
44
+ failure_message_for_should do |operation_response|
45
+ "expected that (#{operation_response.http_status_code}, #{operation_response.status}) would be (403, error)"
46
+ end
47
+
48
+ failure_message_for_should_not do |operation_response|
49
+ "expected that (#{operation_response.http_status_code}, #{operation_response.status}) would not be (403, error)"
50
+ end
51
+
52
+ description do
53
+ "be a (403, error) response"
54
+ end
55
+ end
56
+
57
+ RSpec::Matchers.define :contain_message do |level = nil, key = nil, description = nil|
58
+ match do |operation_response|
59
+ operation_response.messages.find do |message|
60
+ level_and_key_matches = message['level'] == level.to_s && message['key'] == key
61
+
62
+ description_matches = true
63
+
64
+ if level_and_key_matches && description.present?
65
+ description_matches = message['dsc'] == description
66
+ end
67
+
68
+ level_and_key_matches && description_matches
69
+ end
70
+ end
71
+
72
+ failure_message_for_should do |operation_response|
73
+ "message expected to contain #{level}, #{key}, #{description}"
74
+ end
75
+
76
+ failure_message_for_should_not do |operation_response|
77
+ "message expected to not contain #{level}, #{key}, #{description}"
78
+ end
79
+
80
+ description do
81
+ "contains a message"
82
+ end
83
+ end
84
+
85
+ RSpec::Matchers.define :contain_element do |name, *value|
86
+ match do |operation_response|
87
+ if value.empty?
88
+ operation_response.body.include?(name.to_s)
89
+ else
90
+ # we only take into account the first received value
91
+ first_value = value.first
92
+ operation_response.body[name.to_s] == first_value
93
+ end
94
+ end
95
+
96
+ failure_message_for_should do |operation_response|
97
+ message = "response expected to contain #{name}"
98
+ if !value.empty?
99
+ message += " = #{value}"
100
+ end
101
+
102
+ message
103
+ end
104
+
105
+ failure_message_for_should_not do |operation_response|
106
+ message = "response expected not to contain #{name}"
107
+ if !value.empty?
108
+ message += " = #{value}"
109
+ end
110
+
111
+ message
112
+ end
113
+
114
+ description do
115
+ "contains an element"
116
+ end
117
+ end
@@ -0,0 +1,57 @@
1
+ require 'json'
2
+
3
+ require 'angus/responses'
4
+
5
+ # Wraps the response return from an operation invocation
6
+ class OperationResponse
7
+
8
+ def initialize(rack_response)
9
+ @rack_response = rack_response
10
+ @parsed_response = JSON(rack_response.body)
11
+ end
12
+
13
+ def body
14
+ @parsed_response
15
+ end
16
+
17
+ def messages
18
+ @parsed_response['messages'] || []
19
+ end
20
+
21
+ def wraps?(rack_response)
22
+ @rack_response == rack_response
23
+ end
24
+
25
+ def success?
26
+ @parsed_response['status'] == 'success' &&
27
+ http_status_code == Angus::Responses::HTTP_STATUS_CODE_OK
28
+ end
29
+
30
+ def forbidden?
31
+ @parsed_response['status'] == 'error' &&
32
+ http_status_code == Angus::Responses::HTTP_STATUS_CODE_FORBIDDEN
33
+ end
34
+
35
+ def not_found?
36
+ @parsed_response['status'] == 'error' &&
37
+ http_status_code == Angus::Responses::HTTP_STATUS_CODE_NOT_FOUND
38
+ end
39
+
40
+ def conflict?
41
+ @parsed_response['status'] == 'error' &&
42
+ http_status_code == Angus::Responses::HTTP_STATUS_CODE_CONFLICT
43
+ end
44
+
45
+ def unprocessable_entity?
46
+ @parsed_response['status'] == 'error' &&
47
+ http_status_code == Angus::Responses::HTTP_STATUS_CODE_UNPROCESSABLE_ENTITY
48
+ end
49
+
50
+ def http_status_code
51
+ @rack_response.status
52
+ end
53
+
54
+ def status
55
+ @parsed_response['status']
56
+ end
57
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'utils/params'
2
+ require_relative 'utils/string'
@@ -0,0 +1,24 @@
1
+ module Angus
2
+ module Params
3
+
4
+ # Enable string or symbol key access to the nested params hash.
5
+ def self.indifferent_params(object)
6
+ case object
7
+ when Hash
8
+ new_hash = indifferent_hash
9
+ object.each { |key, value| new_hash[key] = indifferent_params(value) }
10
+ new_hash
11
+ when Array
12
+ object.map { |item| indifferent_params(item) }
13
+ else
14
+ object
15
+ end
16
+ end
17
+
18
+ # Creates a Hash with indifferent access.
19
+ def self.indifferent_hash
20
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module Angus
2
+ module String
3
+
4
+ class << self
5
+
6
+ def underscore(string)
7
+ string.gsub(/::/, '/').
8
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
9
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
10
+ tr('-', '_').
11
+ downcase
12
+ end
13
+
14
+ def camelize(term, uppercase_first_letter = true)
15
+ string = term.to_s
16
+ if uppercase_first_letter
17
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
18
+ else
19
+ string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
20
+ end
21
+ string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
data/lib/angus/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Angus
2
- VERSION = "0.0.1"
3
- end
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ require 'angus/rspec/support/examples/describe_errors'
4
+
5
+ describe Angus::RSpec::Examples::DescribeErrors do
6
+
7
+ subject(:descriptor) { Angus::RSpec::Examples::DescribeErrors }
8
+
9
+ let(:base) do
10
+ Class.new do
11
+ def self.get(path, &block)
12
+ :get_v0
13
+ end
14
+ end
15
+ end
16
+
17
+ let(:error) { StandardError }
18
+
19
+ let(:example) { double(:example, :app => base) }
20
+
21
+ describe '.mock_service' do
22
+
23
+ it 'mocks app method' do
24
+ mocked_app = nil
25
+
26
+ descriptor.mock_service(base, error, example) do
27
+ mocked_app = example.app
28
+ end
29
+
30
+ mocked_app.should_not eq(base)
31
+ end
32
+
33
+ it 'binds to the original app method after running' do
34
+ example.app.should eq(base)
35
+ descriptor.mock_service(base, error, example) { }
36
+
37
+ example.app.should eq(base)
38
+ end
39
+
40
+ context 'when exception occurs' do
41
+ it 'binds to the original app method after running' do
42
+ example.app.should eq(base)
43
+
44
+ begin
45
+ descriptor.mock_service(base, error, example) do
46
+ raise StandardError
47
+ end
48
+ rescue
49
+ end
50
+
51
+ example.app.should eq(base)
52
+ end
53
+
54
+ it 'reraises the exception' do
55
+ expect {
56
+ descriptor.mock_service(base, error, example) do
57
+ raise StandardError
58
+ end
59
+ }.to raise_error(StandardError)
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,27 @@
1
+ require 'bundler/setup'
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start
5
+
6
+ require 'rspec'
7
+
8
+ require 'simplecov-rcov'
9
+ require 'simplecov-rcov-text'
10
+
11
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
12
+ SimpleCov::Formatter::HTMLFormatter,
13
+ SimpleCov::Formatter::RcovFormatter,
14
+ SimpleCov::Formatter::RcovTextFormatter
15
+ ]
16
+
17
+ Dir[File.join(File.dirname(__FILE__), 'support', '**', '*.rb')].each { |f| require f }
18
+
19
+ RSpec.configure do |config|
20
+
21
+ # Run specs in random order to surface order dependencies. If you find an
22
+ # order dependency and want to debug it, you can fix the order by providing
23
+ # the seed, which is printed after each run.
24
+ # --seed 1234
25
+ config.order = 'random'
26
+
27
+ end
metadata CHANGED
@@ -1,32 +1,66 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: angus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
+ - Pablo Ifran
8
9
  - Adrian Gomez
10
+ - Gianfranco Zas
9
11
  autorequire:
10
12
  bindir: bin
11
13
  cert_chain: []
12
- date: 2013-09-20 00:00:00.000000000 Z
14
+ date: 2013-10-30 00:00:00.000000000 Z
13
15
  dependencies:
14
16
  - !ruby/object:Gem::Dependency
15
- name: bundler
17
+ name: thor
18
+ requirement: !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ! '>='
22
+ - !ruby/object:Gem::Version
23
+ version: '0'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ! '>='
30
+ - !ruby/object:Gem::Version
31
+ version: '0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: angus-sdoc
16
34
  requirement: !ruby/object:Gem::Requirement
17
35
  none: false
18
36
  requirements:
19
37
  - - ~>
20
38
  - !ruby/object:Gem::Version
21
- version: '1.3'
22
- type: :development
39
+ version: '0.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0.0'
48
+ - !ruby/object:Gem::Dependency
49
+ name: angus-router
50
+ requirement: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '0.0'
56
+ type: :runtime
23
57
  prerelease: false
24
58
  version_requirements: !ruby/object:Gem::Requirement
25
59
  none: false
26
60
  requirements:
27
61
  - - ~>
28
62
  - !ruby/object:Gem::Version
29
- version: '1.3'
63
+ version: '0.0'
30
64
  - !ruby/object:Gem::Dependency
31
65
  name: rake
32
66
  requirement: !ruby/object:Gem::Requirement
@@ -43,22 +77,148 @@ dependencies:
43
77
  - - ! '>='
44
78
  - !ruby/object:Gem::Version
45
79
  version: '0'
46
- description: Angus
80
+ - !ruby/object:Gem::Dependency
81
+ name: rspec
82
+ requirement: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: '2.12'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ~>
94
+ - !ruby/object:Gem::Version
95
+ version: '2.12'
96
+ - !ruby/object:Gem::Dependency
97
+ name: faker
98
+ requirement: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: simplecov
114
+ requirement: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ~>
118
+ - !ruby/object:Gem::Version
119
+ version: '0.7'
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ~>
126
+ - !ruby/object:Gem::Version
127
+ version: '0.7'
128
+ - !ruby/object:Gem::Dependency
129
+ name: simplecov-rcov
130
+ requirement: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ~>
134
+ - !ruby/object:Gem::Version
135
+ version: '0.2'
136
+ type: :development
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ~>
142
+ - !ruby/object:Gem::Version
143
+ version: '0.2'
144
+ - !ruby/object:Gem::Dependency
145
+ name: simplecov-rcov-text
146
+ requirement: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ~>
150
+ - !ruby/object:Gem::Version
151
+ version: '0.0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
157
+ - - ~>
158
+ - !ruby/object:Gem::Version
159
+ version: '0.0'
160
+ - !ruby/object:Gem::Dependency
161
+ name: ci_reporter
162
+ requirement: !ruby/object:Gem::Requirement
163
+ none: false
164
+ requirements:
165
+ - - ! '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ type: :development
169
+ prerelease: false
170
+ version_requirements: !ruby/object:Gem::Requirement
171
+ none: false
172
+ requirements:
173
+ - - ! '>='
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ description: angus
47
177
  email:
48
- - adrian.gomez@moove-it.com
49
- executables: []
178
+ - angus@moove-it.com
179
+ executables:
180
+ - angus
50
181
  extensions: []
51
182
  extra_rdoc_files: []
52
183
  files:
53
- - .gitignore
54
- - Gemfile
55
- - LICENSE.txt
56
- - README.md
57
- - Rakefile
58
- - angus.gemspec
59
- - lib/angus.rb
184
+ - lib/angus/responses.rb
185
+ - lib/angus/marshallings/unmarshalling.rb
186
+ - lib/angus/marshallings/marshalling.rb
187
+ - lib/angus/marshallings/base.rb
188
+ - lib/angus/generator.rb
60
189
  - lib/angus/version.rb
61
- homepage: ''
190
+ - lib/angus/base_actions.rb
191
+ - lib/angus/request_handler.rb
192
+ - lib/angus/renders/html_render.rb
193
+ - lib/angus/renders/base.rb
194
+ - lib/angus/renders/json_render.rb
195
+ - lib/angus/generator/templates/resources/resource.rb.erb
196
+ - lib/angus/generator/templates/Gemfile
197
+ - lib/angus/generator/templates/README.md
198
+ - lib/angus/generator/templates/config.ru.erb
199
+ - lib/angus/generator/templates/services/service.rb.erb
200
+ - lib/angus/generator/templates/definitions/representations.yml
201
+ - lib/angus/generator/templates/definitions/service.yml.erb
202
+ - lib/angus/generator/templates/definitions/operations.yml.erb
203
+ - lib/angus/generator/templates/definitions/messages.yml
204
+ - lib/angus/base_resource.rb
205
+ - lib/angus/command.rb
206
+ - lib/angus/response.rb
207
+ - lib/angus/rspec/support/examples/describe_errors.rb
208
+ - lib/angus/rspec/support/matchers/operation_response_matchers.rb
209
+ - lib/angus/rspec/support/operation_response.rb
210
+ - lib/angus/rspec/support/examples.rb
211
+ - lib/angus/rspec/spec_helper.rb
212
+ - lib/angus/utils/string.rb
213
+ - lib/angus/utils/params.rb
214
+ - lib/angus/resource_definition.rb
215
+ - lib/angus/utils.rb
216
+ - lib/angus/base.rb
217
+ - lib/angus.rb
218
+ - spec/spec_helper.rb
219
+ - spec/angus/rspec/examples/describe_errors_spec.rb
220
+ - bin/angus
221
+ homepage: http://mooveit.github.io/angus-sdoc
62
222
  licenses:
63
223
  - MIT
64
224
  post_install_message:
@@ -82,5 +242,7 @@ rubyforge_project:
82
242
  rubygems_version: 1.8.25
83
243
  signing_key:
84
244
  specification_version: 3
85
- summary: Angus
86
- test_files: []
245
+ summary: angus
246
+ test_files:
247
+ - spec/spec_helper.rb
248
+ - spec/angus/rspec/examples/describe_errors_spec.rb