dnclabs-fakeweb-matcher 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Pat Allan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ h1. FakeWeb Matcher
2
+
3
+ An RSpec matcher for the Fakeweb HTTP stubbing library, allowing you to use RSpec syntax to check if requests to particular URIs have been made.
4
+
5
+ h2. Installing
6
+
7
+ First, install the gem
8
+ <pre><code>gem install fakeweb-matcher --source http://gemcutter.org</code></pre>
9
+
10
+ Then, in your @spec/spec_helper.rb@ file, you'll need to require the library _after_ you have required "FakeWeb":http://fakeweb.rubyforge.org and "RSpec":http://rspec.info. It should end up looking something like this:
11
+
12
+ <pre><code>require 'spec'
13
+ require 'fakeweb'
14
+ require 'fakeweb_matcher'</code></pre>
15
+
16
+ This ensures that the matcher is automatically loaded into RSpec for you.
17
+
18
+ h2. Usage
19
+
20
+ <pre><code>FakeWeb.should have_requested(:get, 'http://example.com')
21
+ FakeWeb.should have_requested(:any, 'http://example.com')
22
+ FakeWeb.should have_requested(:post, /http:\/\/example.com\/)
23
+ FakeWeb.should_not have_requested(:put, 'http://example.com')</code></pre>
24
+
25
+ h2. Contribution
26
+
27
+ Unsurprisingly, this library is tested using RSpec, and relies upon FakeWeb. It also uses "YARD":http://yard.soen.ca/ for documentation, so if you're submitting patches (which are most definitely welcome!) please use YARD syntax and have valid specs.
28
+
29
+ h2. Contributors
30
+
31
+ * "Thilo Utke":http://github.com/thilo (Regex URL matching)
32
+
33
+ h2. Copyright
34
+
35
+ Copyright (c) 2009 Pat Allan, released under an MIT Licence
@@ -0,0 +1,4 @@
1
+ require 'tasks/distribution'
2
+ require 'tasks/testing'
3
+
4
+ task :default => :spec
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 1
4
+ :patch: 0
@@ -0,0 +1,37 @@
1
+ require 'fake_web_matcher/extension'
2
+ require 'fake_web_matcher/matchers'
3
+ require 'fake_web_matcher/request_matcher'
4
+
5
+ # An RSpec matcher for the Fakeweb HTTP stubbing library, allowing you to use
6
+ # RSpec syntax to check if requests to particular URIs have been made.
7
+ #
8
+ # The matcher is automatically included into RSpec's set, and can be used as
9
+ # follows:
10
+ #
11
+ # @example
12
+ # FakeWeb.should have_requested(:get, 'http://example.com')
13
+ # FakeWeb.should have_requested(:any, 'http://example.com')
14
+ # FakeWeb.should_not have_requested(:put, 'http://example.com')
15
+ #
16
+ # @see FakeWebMatcher::Matchers
17
+ # @see http://fakeweb.rubyforge.org
18
+ # @author Pat Allan
19
+ #
20
+ module FakeWebMatcher
21
+ #
22
+ end
23
+
24
+ FakeWeb::Registry.class_eval do
25
+ # Don't like doing this, but need some way to track the requests
26
+ include FakeWebMatcher::Extension
27
+ end
28
+
29
+ Spec::Runner.configure { |config|
30
+ # Adding the custom matcher to the default set
31
+ config.include FakeWebMatcher::Matchers
32
+
33
+ # Ensuring the request list gets cleared after each spec
34
+ config.before :each do
35
+ FakeWeb::Registry.instance.clear_requests
36
+ end
37
+ }
@@ -0,0 +1,43 @@
1
+ module FakeWebMatcher
2
+ # Extension for FakeWeb::Registry, to track requests made for given URIs. The
3
+ # code that includes this into FakeWeb is in the base FakeWebMatcher module.
4
+ #
5
+ # @see http://fakeweb.rubyforge.org
6
+ #
7
+ module Extension
8
+ def self.included(base)
9
+ base.class_eval do
10
+ # Keep the original response_for method
11
+ alias_method :response_without_request_tracking, :response_for
12
+
13
+ # Overwrites the existing FakeWeb::Registry#response method, to ensure
14
+ # requests are tracked. Returns the usual stubbed response.
15
+ #
16
+ # @param [Symbol] method HTTP method
17
+ # @param [String] uri URI requested
18
+ # @param [Proc] block The block passed into Net::HTTP requests
19
+ # @return [String] The stubbed page response
20
+ #
21
+ def response_for(method, uri, &block)
22
+ requests << [method, uri]
23
+ response_without_request_tracking(method, uri, &block)
24
+ end
25
+ end
26
+ end
27
+
28
+ # A list of the requests, kept as an array of arrays, where each child array
29
+ # has two values - the method and the URI.
30
+ #
31
+ # @return [Array] Recorded requests
32
+ #
33
+ def requests
34
+ @requests ||= []
35
+ end
36
+
37
+ # Clears the stored request list
38
+ #
39
+ def clear_requests
40
+ requests.clear
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ # An RSpec matcher for the Fakeweb HTTP stubbing library, allowing you to use
2
+ # RSpec syntax to check if requests to particular URIs have been made.
3
+ #
4
+ # @see FakeWebMatcher::Matchers
5
+ # @see http://fakeweb.rubyforge.org
6
+ # @author Pat Allan
7
+ #
8
+ module FakeWebMatcher
9
+ # Custom matcher holder for RSpec
10
+ #
11
+ module Matchers
12
+ # Returns a new matcher instance.
13
+ #
14
+ # @param [Symbol] method The HTTP method
15
+ # @param [String] uri The URI to check for
16
+ # @return [FakeWebMatcher::RequestMatcher]
17
+ #
18
+ def have_requested(method, uri)
19
+ FakeWebMatcher::RequestMatcher.new(method, uri)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,139 @@
1
+ module FakeWebMatcher
2
+ # Matcher class, following RSpec's expectations. Used to confirm whether a
3
+ # request has been made on a given method and URI.
4
+ #
5
+
6
+ # Monkey patch to add a normalize! method to the URI::HTTP class.
7
+ # It doesn't currently sort the query params which means two URLs
8
+ # that are equivalent except for a different order of query params
9
+ # will be !=. This isn't how FakeWeb works, so we patch it here.
10
+ class ::URI::HTTP
11
+
12
+ # Normalize an HTTP URI so that query param order differences don't
13
+ # affect equality.
14
+ def normalize!
15
+ if query
16
+ query_array = self.query.split('&')
17
+ set_query(query_array.sort.join('&'))
18
+ end
19
+ super
20
+ end
21
+ end
22
+
23
+ class RequestMatcher
24
+ attr_reader :url, :method
25
+
26
+ # Create a new matcher.
27
+ #
28
+ # @param [Symbol] method The HTTP method. Defaults to :any if not supplied.
29
+ # @param [String, Regexp] uri The URI to check for
30
+ #
31
+ def initialize(*args)
32
+ @method, @url = args_split(*args)
33
+ end
34
+
35
+ # Indication of whether there's a match on the URI from given requests.
36
+ #
37
+ # @param [Module] FakeWeb Module, necessary for RSpec, although not
38
+ # required internally.
39
+ # @return [Boolean] true if the URI was requested, otherwise false.
40
+ #
41
+ def matches?(fakeweb)
42
+ !FakeWeb::Registry.instance.requests.detect { |req|
43
+ method, url = args_split(*req)
44
+ match_method(method) && match_url(url)
45
+ }.nil?
46
+ end
47
+
48
+ # Failure message if the URI should have been requested.
49
+ #
50
+ # @return [String] failure message
51
+ #
52
+ def failure_message
53
+ regex?(@url) ? regex_failure_message : url_failure_message
54
+ end
55
+
56
+ # Failure message if the URI should not have been requested.
57
+ #
58
+ # @return [String] failure message
59
+ #
60
+ def negative_failure_message
61
+ regex?(@url) ? regex_negative_failure_message : url_negative_failure_message
62
+ end
63
+
64
+ private
65
+
66
+ def regex_negative_failure_message
67
+ "A URL that matches #{@url.inspect} was requested#{failure_message_method} and should not have been."
68
+ end
69
+
70
+ def url_negative_failure_message
71
+ "The URL #{@url} was requested#{failure_message_method} and should not have been."
72
+ end
73
+
74
+ def regex_failure_message
75
+ "A URL that matches #{@url.inspect} was not requested#{failure_message_method}."
76
+ end
77
+
78
+ def url_failure_message
79
+ "The URL #{@url} was not requested#{failure_message_method}."
80
+ end
81
+
82
+ def failure_message_method
83
+ " using #{formatted_method}" unless @method == :any
84
+ end
85
+
86
+ # Compares methods, or ignores if either side of the comparison is :any.
87
+ #
88
+ # @param [Symbol] method HTTP method
89
+ # @return [Boolean] true if methods match or either is :any.
90
+ #
91
+ def match_method(method)
92
+ @method == :any || method == :any || method == @method
93
+ end
94
+
95
+ # Compares the url either by match it agains a regular expression or
96
+ # by simple comparison
97
+ #
98
+ # @param [String] url the called URI
99
+ # @return [Boolean] true if exprexted URI and called URI match.
100
+ #
101
+ def match_url(url)
102
+ return @url.match(url.to_s) if regex?(@url)
103
+ @url == url
104
+ end
105
+
106
+ # Expected method formatted to be an uppercase string. Example: :get becomes
107
+ # "GET".
108
+ #
109
+ # @return [String] uppercase method
110
+ #
111
+ def formatted_method
112
+ @method.to_s.upcase
113
+ end
114
+
115
+ # Interprets given arguments to a method and URI instance. The URI, as a
116
+ # string, is required, but the method is not (will default to :any).
117
+ #
118
+ # @param [Array] args
119
+ # @return [Array] Two items: method and URI instance or regular expression
120
+ #
121
+ def args_split(*args)
122
+ method = :any
123
+ uri = nil
124
+
125
+ case args.length
126
+ when 1 then uri = args[0]
127
+ when 2 then method, uri = args[0], args[1]
128
+ else
129
+ raise ArgumentError.new("wrong number of arguments")
130
+ end
131
+ uri = URI.parse(uri) unless regex?(uri)
132
+ return method, uri
133
+ end
134
+
135
+ def regex?(object)
136
+ object.is_a?(Regexp)
137
+ end
138
+ end
139
+ end
@@ -0,0 +1 @@
1
+ require 'fake_web_matcher'
@@ -0,0 +1,42 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe FakeWebMatcher::Extension do
4
+ it "should be included into the FakeWeb::Registry class" do
5
+ FakeWeb::Registry.included_modules.should include(FakeWebMatcher::Extension)
6
+ end
7
+
8
+ describe '#requests' do
9
+ it "should return an empty Array by default" do
10
+ FakeWeb::Registry.instance.requests.should == []
11
+ end
12
+ end
13
+
14
+ describe '#clear_requests' do
15
+ it "should clear the requests array" do
16
+ registry = FakeWeb::Registry.instance
17
+ registry.requests << :something
18
+ registry.requests.should == [:something]
19
+
20
+ registry.clear_requests
21
+ registry.requests.should == []
22
+ end
23
+ end
24
+
25
+ describe '#response_for' do
26
+ before :each do
27
+ @registry = FakeWeb::Registry.instance
28
+ end
29
+
30
+ it "should track request" do
31
+ @registry.response_for(:any, 'http://uri.com')
32
+
33
+ @registry.requests.should == [[:any, 'http://uri.com']]
34
+ end
35
+
36
+ it "should return the underlying response from response_without_request_tracking" do
37
+ @registry.stub!(:response_without_request_tracking => :response)
38
+
39
+ @registry.response_for(:any, 'http://uri.com').should == :response
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe FakeWebMatcher::Matchers do
4
+ describe '#have_requested' do
5
+ before :each do
6
+ class Matchbox
7
+ include FakeWebMatcher::Matchers
8
+ end
9
+
10
+ @matcher = Matchbox.new.have_requested(:put, 'http://url.com')
11
+ end
12
+
13
+ it "should return an instance of RequestMatcher" do
14
+ @matcher.should be_a(FakeWebMatcher::RequestMatcher)
15
+ end
16
+
17
+ it "should set the url and method using the matcher arguments" do
18
+ @matcher.url.to_s.should == 'http://url.com'
19
+ @matcher.method.should == :put
20
+ end
21
+ end
22
+
23
+ it "should pass if the request has been made" do
24
+ FakeWeb.register_uri(:get, 'http://example.com/', :body => 'foo')
25
+ open('http://example.com/')
26
+
27
+ FakeWeb.should have_requested(:get, 'http://example.com')
28
+ end
29
+
30
+ it "should pass if the request has not been made" do
31
+ FakeWeb.register_uri(:get, 'http://example.com/', :body => 'foo')
32
+
33
+ FakeWeb.should_not have_requested(:get, 'http://example.com')
34
+ end
35
+
36
+ it "should not care about the order of the query parameters" do
37
+ FakeWeb.register_uri(:get, URI.parse('http://example.com/page?foo=bar&baz=qux'), :body => 'foo')
38
+ open('http://example.com/page?baz=qux&foo=bar')
39
+
40
+ FakeWeb.should have_requested(:get, 'http://example.com/page?foo=bar&baz=qux')
41
+ end
42
+ end
@@ -0,0 +1,123 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe FakeWebMatcher::RequestMatcher do
4
+ describe '#initialize' do
5
+ it "should set the url if no method is supplied" do
6
+ matcher = FakeWebMatcher::RequestMatcher.new('http://example.com')
7
+ matcher.url.to_s.should == 'http://example.com'
8
+ end
9
+
10
+ it "set the url if a method is explicitly supplied" do
11
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, 'http://example.com')
12
+ matcher.url.to_s.should == 'http://example.com'
13
+ end
14
+
15
+ it "should set the method to any if not supplied" do
16
+ matcher = FakeWebMatcher::RequestMatcher.new('http://example.com')
17
+ matcher.method.should == :any
18
+ end
19
+
20
+ it "set the method if explicitly supplied" do
21
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, 'http://example.com')
22
+ matcher.method.should == :get
23
+ end
24
+ end
25
+
26
+ describe '#matches?' do
27
+ before :each do
28
+ FakeWeb.register_uri(:get, 'http://example.com/', :body => 'foo')
29
+ open('http://example.com/')
30
+ end
31
+
32
+ it "should return true if same url and any method" do
33
+ matcher = FakeWebMatcher::RequestMatcher.new('http://example.com')
34
+ matcher.matches?(FakeWeb).should be_true
35
+ end
36
+
37
+ it "should return true if same url and same explicit method" do
38
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, 'http://example.com')
39
+ matcher.matches?(FakeWeb).should be_true
40
+ end
41
+
42
+ it "should return false if same url and different explicit method" do
43
+ matcher = FakeWebMatcher::RequestMatcher.new(:post, 'http://example.com')
44
+ matcher.matches?(FakeWeb).should be_false
45
+ end
46
+
47
+ it "should return false if different url and same method" do
48
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, 'http://domain.com')
49
+ matcher.matches?(FakeWeb).should be_false
50
+ end
51
+
52
+ it "should return false if different url and different explicit method" do
53
+ matcher = FakeWebMatcher::RequestMatcher.new(:post, 'http://domain.com')
54
+ matcher.matches?(FakeWeb).should be_false
55
+ end
56
+
57
+ describe "matching url is regular expression" do
58
+ it "should return true if expression matches" do
59
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, /example/)
60
+ matcher.matches?(FakeWeb).should be_true
61
+ end
62
+
63
+ it "should return false if don't match" do
64
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, /domain/)
65
+ matcher.matches?(FakeWeb).should be_false
66
+ end
67
+
68
+ end
69
+ end
70
+
71
+ describe '#failure_message' do
72
+ it "should mention the method if explicitly set" do
73
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, 'http://example.com')
74
+ matcher.failure_message.
75
+ should == 'The URL http://example.com was not requested using GET.'
76
+ end
77
+
78
+ it "should not mention the method if not explicitly set" do
79
+ matcher = FakeWebMatcher::RequestMatcher.new('http://example.com')
80
+ matcher.failure_message.
81
+ should == 'The URL http://example.com was not requested.'
82
+ end
83
+
84
+ it "should mention failing match" do
85
+ matcher = FakeWebMatcher::RequestMatcher.new(/example.com/)
86
+ matcher.failure_message.
87
+ should == 'A URL that matches /example.com/ was not requested.'
88
+ end
89
+
90
+ it "should mention failing match with method if set" do
91
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, /example.com/)
92
+ matcher.failure_message.
93
+ should == 'A URL that matches /example.com/ was not requested using GET.'
94
+ end
95
+ end
96
+
97
+ describe '#negative_failure_message' do
98
+ it "should mention the method if explicitly set" do
99
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, 'http://example.com')
100
+ matcher.negative_failure_message.
101
+ should == 'The URL http://example.com was requested using GET and should not have been.'
102
+ end
103
+
104
+ it "should not mention the method if not explicitly set" do
105
+ matcher = FakeWebMatcher::RequestMatcher.new('http://example.com')
106
+ matcher.negative_failure_message.
107
+ should == 'The URL http://example.com was requested and should not have been.'
108
+ end
109
+
110
+ it "should mention unexpected match" do
111
+ matcher = FakeWebMatcher::RequestMatcher.new(/example.com/)
112
+ matcher.negative_failure_message.
113
+ should == 'A URL that matches /example.com/ was requested and should not have been.'
114
+ end
115
+
116
+ it "should mention unexpected match with method if set" do
117
+ matcher = FakeWebMatcher::RequestMatcher.new(:get, /example.com/)
118
+ matcher.negative_failure_message.
119
+ should == 'A URL that matches /example.com/ was requested using GET and should not have been.'
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe FakeWebMatcher do
4
+ #
5
+ end
@@ -0,0 +1,11 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'rubygems'
4
+ require 'open-uri'
5
+ require 'spec'
6
+ require 'fake_web'
7
+ require 'fake_web_matcher'
8
+
9
+ Spec::Runner.configure do |config|
10
+ #
11
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dnclabs-fakeweb-matcher
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 1
9
+ - 0
10
+ version: 1.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Pat Allan
14
+ - Wes Morgan
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-10-23 00:00:00 -04:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: fakeweb
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 21
31
+ segments:
32
+ - 1
33
+ - 2
34
+ - 5
35
+ version: 1.2.5
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 31
47
+ segments:
48
+ - 1
49
+ - 2
50
+ - 0
51
+ version: 1.2.0
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ description:
55
+ email:
56
+ - pat@freelancing-gods.com
57
+ - morganw@dnc.org
58
+ executables: []
59
+
60
+ extensions: []
61
+
62
+ extra_rdoc_files:
63
+ - LICENSE
64
+ - README.textile
65
+ files:
66
+ - LICENSE
67
+ - README.textile
68
+ - Rakefile
69
+ - VERSION.yml
70
+ - lib/fake_web_matcher.rb
71
+ - lib/fake_web_matcher/extension.rb
72
+ - lib/fake_web_matcher/matchers.rb
73
+ - lib/fake_web_matcher/request_matcher.rb
74
+ - lib/fakeweb_matcher.rb
75
+ - spec/lib/fake_web_matcher/extension_spec.rb
76
+ - spec/lib/fake_web_matcher/matchers_spec.rb
77
+ - spec/lib/fake_web_matcher/request_matcher_spec.rb
78
+ - spec/lib/fake_web_matcher_spec.rb
79
+ - spec/spec_helper.rb
80
+ has_rdoc: true
81
+ homepage: http://github.com/dnclabs/fakeweb-matcher
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options:
86
+ - --charset=UTF-8
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ requirements: []
108
+
109
+ rubyforge_project:
110
+ rubygems_version: 1.3.7
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: RSpec matcher for the FakeWeb library
114
+ test_files:
115
+ - spec/lib/fake_web_matcher/extension_spec.rb
116
+ - spec/lib/fake_web_matcher/matchers_spec.rb
117
+ - spec/lib/fake_web_matcher/request_matcher_spec.rb
118
+ - spec/lib/fake_web_matcher_spec.rb
119
+ - spec/spec_helper.rb