eitil 0.3.2 → 0.3.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14233b2ffb39a1ca7d1710fd9a57925994d5784e427f8d022d21ceeb02eb2fdf
4
- data.tar.gz: c3f42a3bda7b6efd0292f7e3b364ce3595ac023216a3d5d1364d90257828c1a5
3
+ metadata.gz: 24a0dccf0357bf5e01f41be73b6b1d4c840cbb8efa233ab772ad1c6c8bd94cff
4
+ data.tar.gz: 7dc80a7a0268a68302fa74ddc1e0f371a83fba8a77dee2cb38a5ad03c08b3688
5
5
  SHA512:
6
- metadata.gz: 7f0748ebb4cfeb31128024ac32226811205827db8ad381226dd3f04532c6cf2fc700448712e6bd16ba86770574bd161eebb56d6c44b20b03694ed5a0a9160ddc
7
- data.tar.gz: 762bca6004ea991b05cad4df3a10f4cf16ba9c3f91e1d2d6b32c29dada7e2a9b7f8a6572d8096c6d53b5e3777a2e802662dab85df49ffecf4083f84b4caeacf8
6
+ metadata.gz: 6d4a5e8baadfbd18962a5452e42b2051dff922765fbd2a53dcbe15a415a8e805a031efe18defc70d7360f52701d12b2ddcbbc07a96dc8ae2cffd3c33e121a636
7
+ data.tar.gz: 83a19e9611e3553faf36663b5676c1abb3bc81af22052014cc250f8230665ce36b98a606a4a834923d2c24d81f7c347fa27ab91171a1cd74ca25a871e81d4e7a
data/README.md CHANGED
@@ -50,15 +50,21 @@ args_to_ivars(binding, *local_vars)
50
50
  # sets specified keywords arguments of the method's local binding to instance variables
51
51
  # call as: all_args_to_ivars binding :user_id, :user_name
52
52
 
53
- all_kwargs_to_ivars(binding)
53
+ all_kwargs_to_ivars(local_binding, namespace=:kwargs)
54
54
  # sets the method's **kwargs argument to instance variables, with each key as the ivar's "@#{name}" and the value as its value
55
55
  # call as: kwargs_to_ivars binding
56
+ # the keywords container name can be overwritten, e.g. the common :attributes
56
57
 
57
58
  set_ivars(*ivars)
58
59
  # sets instance variables named @"#{ivar}" for each symbol passed, by invoking send("set_#{ivar}")
59
60
  # e.g. set_ivars(:user) sets @user with the value returned by your local method .set_user
60
61
  # call as: set_ivars :user, :authentication, :connection
61
62
 
63
+ run_validations(*validations)
64
+ # calls a method for each argument, namespaced as "validate_#{argument}"
65
+ # call as: run_validations(:single_receipt, :single_order)
66
+ # => calls #validate_single_receipt and #validate_single_order
67
+
62
68
  safe_send(method, *args, return_value: nil)
63
69
  # a safe version of .send, which in case of an error rescues and returns return_value (default: nil) instead
64
70
 
@@ -92,6 +98,7 @@ include_concerns_of(*directories, namespace: nil)
92
98
  # call as: include_concerns_of :user, :mail
93
99
  # => includes all modules of models/concerns/user/* and models/oncerns/mail/*
94
100
  # or call as: include_concerns_of :request_service, namespace: Eivid::Concerns
101
+ # tip: call Class.included_modules to view all included modules
95
102
  ```
96
103
 
97
104
 
@@ -124,6 +131,14 @@ slice_params(*args)
124
131
  self.all_associations
125
132
  # returns all associations for a given model
126
133
  # call as: Environment.all_associations
134
+
135
+ self.where_like(_hash)
136
+ # runs .where with your string field made into a wildcard and case insensitive
137
+ # call as: User.where_like(name: 'jurriaan')
138
+
139
+ self.find_by_like(_hash)
140
+ # runs .find_by with your string field made into a wildcard and case insensitive
141
+ # call as: User.find_by_like(name: 'jurriaan')
127
142
  ```
128
143
 
129
144
 
@@ -170,7 +185,7 @@ decorate(dec_item, dec_method: nil, dec_class: nil, **dec_kwargs)
170
185
  # initializers/eitil.rb
171
186
 
172
187
  Eitil.set_config do |config|
173
- config.get_controller_ivars_method = 'API::VerlofVerzoekenController.controller_ivars' # => [:user, :env]
188
+ config.get_controller_ivars_method = 'API::BaseController.controller_ivars' # => [:user, :env]
174
189
  end
175
190
  ```
176
191
 
@@ -220,14 +235,18 @@ The router wrapper is a single module which is automatically included into your
220
235
 
221
236
  ### Info
222
237
 
223
- The Eitil jobs wrapper enables you to create perform_later jobs on the fly, without the need to create a new class and file. The macro new_job accepts a :method as argument and defines a new method :method_job. The newly defined :method_job, when called, performs the orginal :method in the background. The new_job macro accepts both instance methods and singleton methods, which are defined within there own method scope.
238
+ The Eitil jobs wrapper enables you to create perform_now and perform_later jobs on the fly, without the need to create a new class and file. The macro new_job accepts a :method as argument and defines a new method :method_job. The newly defined :method_job, when called, performs the orginal :method in the background. The new_job macro accepts both instance methods and singleton methods, which are defined within there own method scope. In contrast, the new_job_debugger macro defines a new :method_job_debugger method, which performs in the foreground.
224
239
 
225
240
  ### Job Macro
226
241
 
227
242
  ```ruby
228
- new_job(_method)
229
- # assuming a method :print_hello_world, call as: new_job :hello_world
230
- # => method :hello_world_job
243
+ new_job(_method, *args, **kwargs)
244
+ # assuming a method :hello_world, call as: new_job :hello_world
245
+ # => defines :hello_world_job
246
+
247
+ new_job_debugger(_method, *args, **kwargs)
248
+ # assuming a method :hello_world, call as: new_job_debugger :hello_world
249
+ # => defines :hello_world_job_debugger
231
250
  ```
232
251
 
233
252
  - method is
@@ -242,15 +261,7 @@ The new_job macro is monkey patched into Kernel, therefore no configuration is r
242
261
 
243
262
  ### Info
244
263
 
245
- The Eitil scopes wrapper creates various default scopes for a model, simply through including 'use_eitil_scopes' in the top of your model file, for example:
246
-
247
- ```ruby
248
- # models/user.rb
249
-
250
- class User < ApplicationRecord
251
- use_eitil_scopes
252
- end
253
- ```
264
+ The Eitil scopes wrapper creates various default scopes for a model.
254
265
 
255
266
  ### The Scopes
256
267
 
@@ -305,3 +316,50 @@ Scopes are generated through the columns of your model's database table. Which s
305
316
  Your models should inherit the module Eitil::DefaultScopes through inclusion in a superclass, such as ApplicationRecord.
306
317
 
307
318
 
319
+
320
+ # Modules & Classes
321
+
322
+
323
+
324
+ ## Eitil::Dir
325
+
326
+ ### Info
327
+
328
+ The Eitil::Dir module provides methods which help you introspect your Rails project.
329
+
330
+ ### Methods
331
+
332
+ ```ruby
333
+ Eitil::Dir.contents(directory='app')
334
+ # returns all files and subdirectories of a given directory
335
+
336
+ Eitil::Dir.files(directory='app')
337
+ # returns all files of a given directory
338
+
339
+ Eitil::Dir.subdirs(directory='app')
340
+ # returns all subdirectories of a given directory
341
+
342
+ Eitil::Dir.lines(directory='app')
343
+ # returns the total amount of lines of all files of a given directory
344
+ ```
345
+
346
+
347
+
348
+ ## Eitil::StackTrace
349
+
350
+ ### Info
351
+
352
+ The two classes Eitil::StackTrace::Stack and Eitil::StackTrace::Call, and the single module Eitil::StackTrace::Audit, provide the utility of easily viewing and storing the current stacktrace anywhere in your code. By initializing stack = Eitil::StackTrace::Stack.new, you can call methods as stack.report (returns the stacktrace as array), stack.show (pretty prints the stacktrace) and stack.find (accepts a block to find a call).
353
+
354
+ You can also store the stacktrace of a record's update action into its audit. In order to do so, you need 1) to include Eitil::StackTrace::Audit into your model, and 2) add a :stacktrace column to your audits through the following migration.
355
+
356
+ ### Migration
357
+
358
+ ```ruby
359
+ def change
360
+ add_column :audits, :stacktrace, :text, array: true, default: []
361
+ end
362
+ ```
363
+
364
+
365
+
@@ -0,0 +1,15 @@
1
+ module Eitil::StackTrace::Audit
2
+ extend ActiveSupport::Concern
3
+ included do
4
+
5
+ private
6
+
7
+ after_update :add_stacktrace_to_audit
8
+
9
+ def add_stacktrace_to_audit
10
+ stacktrace = Eitil::StackTrace::Stack.new.report
11
+ self.audits.last.update(stacktrace: stacktrace)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Eitil::StackTrace
2
+ class Call
3
+
4
+ attr_reader :program, :line, :meth
5
+
6
+ CALL_RE = /(.*):(\d+):in `(.*)'/
7
+
8
+ def initialize(string)
9
+ @program, @line, @meth = CALL_RE.match(string).captures
10
+ end
11
+
12
+ def to_s
13
+ "#{"#{meth}".ljust(50)} #{"(#{line})".ljust(6)} #{program}"
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ module Eitil::StackTrace
2
+ class Stack
3
+
4
+ attr_reader :stack, :backtrace
5
+
6
+ def initialize
7
+ @stack = caller[1..]
8
+ @backtrace = stack.map { |call| Eitil::StackTrace::Call.new(call) }
9
+ end
10
+
11
+ def report
12
+ backtrace.map(&:to_s)
13
+ end
14
+
15
+ def show
16
+ ap report
17
+ end
18
+
19
+ def find(&block)
20
+ backtrace.find(&block)
21
+ end
22
+
23
+ def self.parse(array_as_string)
24
+ array_as_string.sub('[', ' ').reverse.sub(']','').reverse.split(',').flatten
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ module Eitil::Dir
2
+ class << self
3
+
4
+ def contents(directory='app')
5
+ Dir[File.join(directory, '**', '*')]
6
+ end
7
+
8
+ def files(directory='app')
9
+ contents(directory).select { |file| File.file?(file) }
10
+ end
11
+
12
+ def subdirs(directory='app')
13
+ contents(directory).select { |file| File.directory?(file) }
14
+ end
15
+
16
+ def lines(directory='app')
17
+ files(directory).map { |file| File.open(file).count }.sum
18
+ end
19
+
20
+ end
21
+ end
@@ -4,11 +4,24 @@ module ApplicationRecordMonkey
4
4
  extend ActiveSupport::Concern
5
5
  included do
6
6
 
7
- def self.all_associations
8
- reflect_on_all_associations.map do |assoc|
9
- assoc_type = assoc.association_class.to_s.split('::').last
10
- "#{assoc.name} (#{assoc_type})"
7
+ class << self
8
+
9
+ def all_associations
10
+ reflect_on_all_associations.map do |assoc|
11
+ { assoc.name => assoc.association_class.to_s.demodulize }
12
+ end.inject &:merge
13
+ end
14
+
15
+ def where_like(_hash)
16
+ column, like = _hash.first
17
+ where("LOWER(#{column}) LIKE ?", "%#{like.downcase}%")
11
18
  end
19
+
20
+ def find_by_like(_hash)
21
+ column, like = _hash.first
22
+ find_by("LOWER(#{column}) LIKE ?", "%#{like.downcase}%")
23
+ end
24
+
12
25
  end
13
26
 
14
27
  end
@@ -8,7 +8,7 @@ class Hash
8
8
  return answer if answer
9
9
  end
10
10
 
11
- false
11
+ nil
12
12
  end
13
13
 
14
14
  end
@@ -12,8 +12,8 @@ Kernel.module_eval do
12
12
  end
13
13
  end
14
14
 
15
- def all_kwargs_to_ivars(local_binding)
16
- local_binding.local_variable_get(:kwargs).each do |name, value|
15
+ def all_kwargs_to_ivars(local_binding, namespace=:kwargs)
16
+ local_binding.local_variable_get(namespace).each do |name, value|
17
17
  instance_variable_set "@#{name}", value
18
18
  end
19
19
  end
@@ -24,6 +24,10 @@ Kernel.module_eval do
24
24
  end
25
25
  end
26
26
 
27
+ def run_validations(*validations)
28
+ validations.each { |v| eval "validate_#{v}" }
29
+ end
30
+
27
31
  def safe_call(*args, return_value: nil, &block)
28
32
  block.call self, *args
29
33
  rescue
@@ -1,18 +1,52 @@
1
1
  Kernel.module_eval do
2
2
 
3
- def new_job(_method)
3
+ # BEWARE: _self is currently not accepted in perform_later jobs due to serialization errors
4
+
5
+ # The cases of 'id' and '_self' both handle instances, with the difference
6
+ # being that 'id' works for objects that have a database record, while '_self'
7
+ # works for non database supported instanes, such as an Exporter instance.
8
+
9
+ def new_job(_method, *args, **kwargs)
4
10
 
5
11
  if instance_methods(false).include? _method
6
- define_method "#{_method}_job" do
7
- Eitil::SingleMethodJob.perform_later(_class: self.class.to_s, _method: _method.to_s, id: id)
12
+ define_method "#{_method}_job" do |_self = nil, *args, **kwargs|
13
+
14
+ Eitil::SingleMethodJob.perform_later(
15
+ *args, _class: self.class.to_s, _method: _method.to_s, id: safe_send(:id), _self: self.to_json, **kwargs
16
+ )
8
17
  end
9
18
 
10
- elsif singleton_methods(false).include? _method
11
- define_singleton_method "#{_method}_job" do
12
- Eitil::SingleMethodJob.perform_later(_class: to_s, _method: _method.to_s)
19
+ elsif singleton_methods(false).include? _method
20
+ define_singleton_method "#{_method}_job" do |*args, **kwargs|
21
+
22
+ Eitil::SingleMethodJob.perform_later(
23
+ *args, _class: to_s, _method: _method.to_s, **kwargs
24
+ )
13
25
  end
14
- end
26
+ end
27
+ end
28
+
29
+ # BEWARE: This is an exact copy of the above method, except for .perform_now instead of .perform_later and the method's name.
30
+ # The reason for not using helper methods is to not unnecessarily fill and potentially fuck up the Kernel environment.
31
+
32
+ def new_job_debugger(_method, *args, **kwargs)
15
33
 
34
+ if instance_methods(false).include? _method
35
+ define_method "#{_method}_job_debugger" do |_self = nil, *args, **kwargs|
36
+
37
+ Eitil::SingleMethodJob.perform_now(
38
+ *args, _class: self.class.to_s, _method: _method.to_s, id: safe_send(:id), _self: self.to_json, **kwargs
39
+ )
40
+ end
41
+
42
+ elsif singleton_methods(false).include? _method
43
+ define_singleton_method "#{_method}_job_debugger" do |*args, **kwargs|
44
+
45
+ Eitil::SingleMethodJob.perform_now(
46
+ *args, _class: to_s, _method: _method.to_s, **kwargs
47
+ )
48
+ end
49
+ end
16
50
  end
17
51
 
18
52
  end
@@ -2,9 +2,23 @@ module Eitil
2
2
  class SingleMethodJob < ApplicationJob
3
3
  queue_as :default
4
4
 
5
- def perform(_class:, _method:, id: nil)
6
- object = id ? _class.constantize.find(id) : _class.constantize
7
- object.send _method
5
+ # BEWARE: _self is currently not accepted in perform_later jobs due to serialization errors
6
+
7
+ # The cases of 'id' and '_self' both handle instances, with the difference
8
+ # being that 'id' works for objects that have a database record, while '_self'
9
+ # works for non database supported instanes, such as an Exporter instance.
10
+
11
+ def perform(*args, _class:, _method:, id: nil, _self: nil, **kwargs)
12
+ object =
13
+ if id
14
+ _class.constantize.find(id)
15
+ elsif _self
16
+ _self
17
+ else
18
+ _class.constantize
19
+ end
20
+
21
+ object.send _method, *args, **kwargs
8
22
  end
9
23
  end
10
24
  end
@@ -2,80 +2,82 @@ module Eitil
2
2
  module DefaultScopes
3
3
  extend ActiveSupport::Concern
4
4
  included do
5
-
6
- SharableDateScopes = -> (column) {
7
- eitil_scope :"#{column}_today", -> { where("#{column} = ?", Date.today) }
8
- eitil_scope :"#{column}_past", -> { where("#{column} < ?", Date.today) }
9
- eitil_scope :"#{column}_future", -> { where("#{column} > ?", Date.today) }
10
-
11
- eitil_scope :"#{column}_on_date", -> (date) { where("#{column} = ?", date) }
12
- eitil_scope :"#{column}_before_date", -> (date) { where("#{column} = ?", date) }
13
- eitil_scope :"#{column}_after_date", -> (date) { where("#{column} = ?", date) }
14
- eitil_scope :"#{column}_between_dates", -> (from, till) { where("#{column} >= ? and #{column} <= ?", from, till) }
5
+ class << self
15
6
 
16
- eitil_scope :"#{column}_oldest_first", -> { order("#{column} ASC") }
17
- eitil_scope :"#{column}_newest_first", -> { order("#{column} DESC") }
18
- }
7
+ SharableDateScopes = -> (_class, column) {
8
+ _class.eitil_scope :"#{column}_today", -> { where("#{column} = ?", Date.today) }
9
+ _class.eitil_scope :"#{column}_past", -> { where("#{column} < ?", Date.today) }
10
+ _class.eitil_scope :"#{column}_future", -> { where("#{column} > ?", Date.today) }
11
+
12
+ _class.eitil_scope :"#{column}_on_date", -> (date) { where("#{column} = ?", date) }
13
+ _class.eitil_scope :"#{column}_before_date", -> (date) { where("#{column} = ?", date) }
14
+ _class.eitil_scope :"#{column}_after_date", -> (date) { where("#{column} = ?", date) }
15
+ _class.eitil_scope :"#{column}_between_dates", -> (from, till) { where("#{column} >= ? and #{column} <= ?", from, till) }
19
16
 
20
- SharableNumScopes = -> (column) {
21
- eitil_scope :"#{column}_equal_to", -> (number) { where("#{column} = ?", number) }
22
- eitil_scope :"#{column}_lower_than", -> (number) { where("#{column} = <", number) }
23
- eitil_scope :"#{column}_higher_than", -> (number) { where("#{column} = >", number) }
24
- eitil_scope :"#{column}_between", -> (min, max) { where("#{column} >= ? and #{column} <= ?", min, max) }
17
+ _class.eitil_scope :"#{column}_oldest_first", -> { order("#{column} ASC") }
18
+ _class.eitil_scope :"#{column}_newest_first", -> { order("#{column} DESC") }
19
+ }
25
20
 
26
- eitil_scope :"#{column}_ascending", -> { order("#{column} ASC") }
27
- eitil_scope :"#{column}_descending", -> { order("#{column} DESC") }
28
- }
21
+ SharableNumScopes = -> (_class, column) {
22
+ _class.eitil_scope :"#{column}_equal_to", -> (number) { where("#{column} = ?", number) }
23
+ _class.eitil_scope :"#{column}_lower_than", -> (number) { where("#{column} = <", number) }
24
+ _class.eitil_scope :"#{column}_higher_than", -> (number) { where("#{column} = >", number) }
25
+ _class.eitil_scope :"#{column}_between", -> (min, max) { where("#{column} >= ? and #{column} <= ?", min, max) }
29
26
 
30
- class << self
27
+ _class.eitil_scope :"#{column}_ascending", -> { order("#{column} ASC") }
28
+ _class.eitil_scope :"#{column}_descending", -> { order("#{column} DESC") }
29
+ }
31
30
 
32
- private
31
+ def inherited(subclass)
32
+ super
33
+ subclass.use_eitil_scopes
34
+ end
33
35
 
34
- def use_eitil_scopes
35
- %i[boolean datetime date integer float].each { |_type| send :"create_eitil_#{_type}_scopes" }
36
- end
36
+ def use_eitil_scopes
37
+ return if abstract_class?
38
+ %i[boolean datetime date integer float].each { |_type| send :"create_eitil_#{_type}_scopes" }
39
+ end
37
40
 
38
- def eitil_scope(_name, _proc)
39
- scope _name, _proc unless respond_to? _name
40
- end
41
-
42
- def columns_of_type(data_type)
43
- columns_hash.select { |column,v| v.sql_type_metadata.type == data_type }
44
- end
41
+ def eitil_scope(_name, _proc)
42
+ scope _name, _proc unless respond_to? _name
43
+ end
44
+
45
+ def columns_of_type(data_type)
46
+ columns_hash.select { |column,v| v.sql_type_metadata.type == data_type }
47
+ end
45
48
 
46
- def create_eitil_boolean_scopes
47
- columns_of_type(:boolean).map do |column, object|
48
- eitil_scope :"#{column}_true", -> { where(column => true) }
49
- eitil_scope :"#{column}_false", -> { where(column => [false, nil]) }
49
+ def create_eitil_boolean_scopes
50
+ columns_of_type(:boolean)&.map do |column, object|
51
+ eitil_scope :"#{column}_true", -> { where(column => true) }
52
+ eitil_scope :"#{column}_false", -> { where(column => [false, nil]) }
53
+ end
50
54
  end
51
- end
52
55
 
53
- def create_eitil_datetime_scopes
54
- columns_of_type(:datetime).map do |column, object|
55
- SharableDateScopes.call column
56
+ def create_eitil_datetime_scopes
57
+ columns_of_type(:datetime)&.map do |column, object|
58
+ SharableDateScopes.call self, column
59
+ end
56
60
  end
57
- end
58
61
 
59
- def create_eitil_date_scopes
60
- columns_of_type(:date).map do |column, object|
61
- SharableDateScopes.call column
62
+ def create_eitil_date_scopes
63
+ columns_of_type(:date)&.map do |column, object|
64
+ SharableDateScopes.call self, column
65
+ end
62
66
  end
63
- end
64
67
 
65
- def create_eitil_integer_scopes
66
- columns_of_type(:integer).map do |column, object|
67
- SharableNumScopes.call column
68
+ def create_eitil_integer_scopes
69
+ columns_of_type(:integer)&.map do |column, object|
70
+ SharableNumScopes.call self, column
71
+ end
68
72
  end
69
- end
70
73
 
71
- def create_eitil_float_scopes
72
- columns_of_type(:float).map do |column, object|
73
- SharableNumScopes.call column
74
+ def create_eitil_float_scopes
75
+ columns_of_type(:float)&.map do |column, object|
76
+ SharableNumScopes.call self, column
77
+ end
74
78
  end
75
- end
76
79
 
77
80
  end
78
-
79
81
  end
80
82
  end
81
83
  end
data/lib/eitil/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eitil
2
- VERSION = '0.3.2'
2
+ VERSION = '0.3.7'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eitil
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jurriaan Schrofer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-29 00:00:00.000000000 Z
11
+ date: 2021-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -52,6 +52,10 @@ files:
52
52
  - app/jobs/eitil/application_job.rb
53
53
  - app/mailers/eitil/application_mailer.rb
54
54
  - app/models/eitil/application_record.rb
55
+ - app/models/eitil/stack_trace/audit.rb
56
+ - app/models/eitil/stack_trace/call.rb
57
+ - app/models/eitil/stack_trace/stack.rb
58
+ - config/initializers/modules/dir.rb
55
59
  - config/initializers/monkeys/application_controller.rb
56
60
  - config/initializers/monkeys/application_record.rb
57
61
  - config/initializers/monkeys/date_time.rb