Split a string on the first occurrence
You can split a string on the first occurrence of pattern by setting the limit to 2
"I am, a, string, of, many, many, commas".split(",", 2)
=> ["I am", " a, string, of, many, many, commas"]
You can split a string on the first occurrence of pattern by setting the limit to 2
"I am, a, string, of, many, many, commas".split(",", 2)
=> ["I am", " a, string, of, many, many, commas"]
While debugging, if you ever need to write to $stderr you might use $stderr#puts
, but you can use Warning#warn
which is better called from Kernel
, because Kernel
appends newlines and respects the -W0
flag:
$stderr.puts "you have been warned"
Warning.warn "you have been warned"
Kernel.warn "you have been warned"
warn "you have been warned"
Output:
you have been warned
you have been warnedyou have been warned
you have been warned
You can conditionlly eagerload relationships in graphql-ruby
by looking at look aheads:
module Resolvers
class Customer < Resolvers::BaseResolver
extras [:lookahead]
argument :id, ID, required: true
def resolve(lookahead:, id:)
scope = ::Customer
if lookahead.selects?(:orders)
scope = scope.includes(:orders)
end
scope.find_by(id)
end
end
end
You can map keys by giving a map of new keys:
data = {
isAdmin: true,
firstName: "Luke",
lastName: "Walker"
}
data.transform_keys({
isAdmin: :admin?,
firstName: :first_name,
lastName: :last_name
})
=> {
:admin? => true,
:first_name => "Luke",
:last_ame => "Walker"
}
You can avoid time discrepancies due to gc and allocs by using Benchmark#bmbm
:
require 'benchmark'
array = (1..1000000).map { rand }
Benchmark.bmbm do |x|
x.report("sort!") { array.dup.sort! }
x.report("sort") { array.dup.sort }
end
Rehearsal -----------------------------------------
sort! 1.490000 0.010000 1.500000 ( 1.490520)
sort 1.460000 0.000000 1.460000 ( 1.463025)
-------------------------------- total: 2.960000sec
user system total real
sort! 1.460000 0.000000 1.460000 ( 1.460465)
sort 1.450000 0.010000 1.460000 ( 1.448327)
My first instinct when it comes to concatenating strings would be to use the +=
operator:
name = "Peter"
=> "Peter"
name.object_id
=> 15320
name += " Parker"
=> "Peter Parker"
name.object_id
=> 34480
However, as you can see, doing this creates a new object. Instead, <<
should be used to maintain the same string object and also improve performance when working on larger batches of strings.
name = "Peter"
=> "Peter"
name.object_id
=> 54960
name << " Parker"
=> "Peter Parker"
name.object_id
=> 54960
You can get the terminal size with IO
class’ #winsize
method
require "io/console"
p IO.console.winsize
=> [26, 46]
IO.console
is just the file /dev/tty
https://ruby-doc.org/stdlib-3.1.1/libdoc/io/console/rdoc/IO.html#method-i-winsize
Ruby’s Array#first
will have very different return types based on what arguments you provide. Without an argument, it will return nil
or object
. With an argument, it will always return a new array; even if your argument is 1
or 0
.
a = [:foo, 'bar', 2]
a.first
# => :foo
a.first(1)
# => [:foo]
a.first(0)
# => []
With an empty array:
[].first
# => nil
[].first(1)
# => []
[].first(0)
# => []
You can invoke a <Proc (lambda)>
with brackets Proc[]
:
UriService = lambda do |username:, password:|
def scream(n) n.upcase end
"http://#{scream(username)}:#{password}@api.example.com"
end
UriService.call(username: "password", password: "123")
=> "http://PASSWORD:123@api.example.com"
UriService[username: "password", password: "123"]
=> "http://PASSWORD:123@api.example.com"
Today I learner that Ruby Module has private_class_method
, this way we can for example make the new
method as private
on service objects:
class MyServiceObject
private_class_method :new
def self.call(*args)
new(*args).call
end
def initialize(foo:)
@foo = foo
end
def call
@foo
end
end
Note that in this example the new
method is private, so the .call
will work, but the .new().call
will raise an error:
irb> MyServiceObject.call(foo: "FOO")
=> "FOO"
irb> MyServiceObject.new(foo: "FOO").call
NoMethodError: private method `new' called for MyServiceObject:Class
You can add a gem to a specific group with the --group
flag.
Normally you add a gem by running:
bundle add standard
But if you want to add it only to the “development, test” group you can run:
bundle add --group "development, test" standard
The graphql-ruby
gem has a built-in blank validator:
class Mutations::UserUpdate < Mutations::BaseMutation
null true
argument :user_id,
String,
"Identifier of user",
required: true,
validates: {allow_blank: false}
field :user_id, String, null: false
def resolve(user_id:)
{user_id:}
end
end
So now a mutation with a user_id
of " "
will cause the graphql response to have an error:
mutation UserUpdate($userId: String!) {
userUpdate(userId: $userId) {
userId
}
}
def send_push_notification(to:, body:)
sound = "default"
messages = [{
to:,
sound:,
body:
}]
client.send_messages(messages)
end
send_push_notification(to: session.pn_token, body: "Welcome to our App!")
# sent messages with: [{
# :to=>"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
# :sound=>"default",
# :body=>"Welcome to our App!"
# }]
If the optional
keyword_init
keyword argument is set to true,.new
takes keyword arguments instead of normal arguments
ApiResponse = Struct.new(:ok, :error, keyword_init: true) { alias_method :ok?, :ok }
response = ApiResponse[ok: true, error: nil]
puts response.ok?
=> true
Ruby has a method to remove repeating characters in a string called squeeze
"foobar".squeeze
# => "fobar"
"foo foo bar".squeeze
# => "fo fo bar"
It also accepts args to narrow down the specific characters for which you would want to remove repeats:
"foobar hello world".squeeze("o")
# => "fobar hello world"
"foobar hello world".squeeze(" ")
# => "foobar hello world"
email = "luke.s.walker@example.com"
URI::MailTo::EMAIL_REGEXP.match?(email)
=> true
email = "luke.s.walker@@example.com"
URI::MailTo::EMAIL_REGEXP.match?(email)
=> false
Kernel#then
is similar to #tap
, but the return values are different; #tap
returns self
, but #then
returns the result of the block:
"a".tap {|s| Time.current }
=> "a"
"a".then {|s| Time.current }
=> Thu, 02 Dec 2021 16:50:57.454554000 UTC +00:00
I use Ruby Structs all the time. They’re great… if you don’t, check them out!
However I have found them a bit cumbersome to set up because they are generally used with positional arguments:
Money = Struct.new(:price, :currency)
Money.new(1.23, "USD")
Be cumbered no more! I have found a different approach.
Money = Struct.new(:price, :currency, keyword_init: true)
Money.new(currency: "USD", price: 1.23)
Using the keyword_init
argument allows the new Struct instantiation to accept keyword arguments which, I find, clearer to read and also do not need to be positional.
You can access the last n characters of a string by using and endless range:
credit_card = "1234•5678•7126•5555"
last_four = (-4..)
puts "Last four: #{credit_card[last_four]}"
=> Last four: 5555
I use Array#compact
all the time… you know the one that gets rid of all the nils.
Well I have no idea why it never occured to me that Hash
ALSO has a #compact
method.
It removes all the key/value pairs where the value is nil.
{
a: "Alluro",
b: nil,
c: "Cheetara"
}.compact
=> {a: "Alluro", c: "Cheetara"}
Ruby has implicit returns for any possible block that I can think of, except ensure
. There might be more, but this is the only one that I can think of now.
So in order to return something from inside the ensure
block we must use the return
reserved word explicitly. Check this out:
def check_this_out
yield if block_given?
:ok
rescue
:error
ensure
:ensured
end
irb()> check_this_out { "should work" }
=> :ok
irb()> check_this_out { raise "should fail" }
=> :error
As we can see even though the ensure
code runs on all calls it does not return :ensured
.
Here’s the code with the explicit return
:
def check_this_out_explicit_ensure_return
yield if block_given?
:ok
rescue
:error
ensure
return :ensured
end
irb()> check_this_out_explicit_ensure_return { "should work" }
=> :ensured
irb()> check_this_out_explicit_ensure_return { raise "should fail" }
=> :ensured
More differences between Procs & Lambdas
If a Proc has an explicit return then that return bubbles up to where it is being used.
Let’s take a look at a Proc’s behavior:
# Explicit return
def work_it
p = Proc.new { puts "Workout"; return }
puts "Pre workout"
p.call
puts "Post workout"
end
=> work_it
Pre workout
Workout
=> nil
# No explicit return
def work_it
p = Proc.new { puts "Workout" }
puts "Pre workout"
p.call
puts "Post workout"
end
=> work_it
Pre workout
Workout
Post workout
=> nil
Now let’s look at lambdas:
# Explicit return
def work_it
l = -> { puts "Workout"; return }
puts "Pre workout"
l.call
puts "Post workout"
end
=> work_it
Pre workout
Workout
Post workout
=> nil
Now even with the explicit return in the lambda, the method is able to complete its own logic path.
Many devs think Procs & Lambas in Ruby are interchangable… and it a lot of cases they can be.
However I did come across a difference to be aware of.
Procs do not enforce arity where a Lambda will.
Let’s take a look at a Proc’s behavior:
# Argument provided
p = Proc.new { |arg| puts arg }
p.call("TEST")
TEST
=> nil
# No argument provided
p = Proc.new { |arg| puts arg }
p.call
=> nil
Now let’s look at lambdas:
# Argument provided
l = ->(arg) { puts arg }
l.call("TEST")
TEST
=> nil
# No argument provided
l = ->(arg) { puts arg }
l.call
=> wrong number of arguments (given 0, expected 1) (ArgumentError)
See how there is strict arity on a lambda where the proc will not complain.
When working with older ruby applications you might need to use an older version of bundler. You can references different versions with the first argument:
$ cd old-project
$ bundle install
Bundler could not find compatible versions for gem "bundler":
...
$ bundle _1.17.3_ install
You cannot set the value of an environment variable to something that is not a string
# THIS DOES NOT WORK
ENV["SKIP_AUTH"] = true
=> `[]=': no implicit conversion of true into String (TypeError)
You can, however, pass an object that implements #to_str
class User < ApplicationRecord
def to_str
to_global_id.to_s
end
end
ENV["user"] = User.first
ENV["user"]
=> "gid://rails-app/User/3f565b9c-0899-49f6-ab20-aa2724235ff5"
Be careful when stubbing ENV
in specs:
# ENV could never return a boolean, your tests will lie to you.
RSpec.describe "ENV" do
before do
stub_const("ENV", {"SKIP_AUTH" => true})
end
end
Need to do exponential calculations in Ruby? Use the **
operator:
5 ** 2
# => 25
2 ** 3
# => 8
4 ** 4
# => 256
Method names in ruby can have spaces, but you need to meta program them and call them with #send
:
class Greeter
define_method("How are you today?") do
"I'm doing well"
end
end
Greeter.new.public_send("How are you today?")
=> "I'm doing well"
Don’t use a hash, just pass send with a comma-separated list of keyword arguments:
class Animal < Struct.new(:name)
def greet(name:, catch_phrase:)
puts "Heya #{name}! What's new, #{catch_phrase}?"
end
end
Animal.new("Rex").send(:greet, name: "Dillon", catch_phrase: "cool cat")
=> "Heya Dillon! What's new, cool cat?"
Sending with a hash will fail:
Animal.new("Rex").send(:greet, {name: "Dillon", catch_phrase: "cool cat"})
=> wrong number of arguments (given 1, expected 0; required keywords: name, catch_phrase) (ArgumentError)
Calling #clear
on a Thread::Queue
will remove all objects from that queue
q = Queue.new
42.times { q << 1 }
q.size == 42
=> true
q.clear
q.size == 0
=> true
Ruby’s undef_method
and remove_method
are both methods for removing a method from a class, but there are subtle differences between the two.
Say we have two classes that both define the method name
, with one class inheriting from the other:
class Human
def name
"homo sapien"
end
end
class Child < Human
def name
"small homo sapien"
end
end
remove_method
fully deletes a method from a particular class, but will still look for the method on parent classes or modules when called on the particular class:
child = Child.new
child.name
# => "small homo sapien"
class Child
remove_method :name
end
child.name
# => "homo sapien"
undef_method
in contrast will prevent Ruby from looking up the method on parent classes
child = Child.new
child.name
# => "small homo sapien"
class Child
undef_method :name
end
child.name
# => raises NoMethodError
# undefined method `name' for #<Child:0x00007ffd91a007a8>
Ruby String class has a start_with?
method:
"#️⃣".start_with? "#"
=> true
Ever been working with a string full of new lines?
Ruby has a lines
method for helping with those strings:
"wibble\nwubble".lines
# => ["wibble\n", "wubble"]
"wibble\nwubble".lines(chomp: true)
# => ["wibble", "wubble"]
Today I came across yet another way to add items to an array in ruby
I already knew about push
and <<
, but did you know that there’s also an append
?
[1,2,3].push(4)
# => [1,2,3,4]
[1,2,3] << 4
# => [1,2,3,4]
[1,2,3].append(4)
# => [1,2,3,4]
append
is ultimately just an alias for push
, but always good to know!
If you ever need to parse a query string in Ruby - or Rails, Rack has a convenient utility to do just that. parse_nested_query
will parse from a string to a hash:
Rack::Utils.parse_nested_query("&sort_dir=asc&sort_by=date_created&filter_by=lead")
=> {"sort_dir"=>"asc", "sort_by"=>"date_created", "filter_by"=>"lead"}
You can also go the opposite way with build_nested_query
and generate a query string:
Rack::Utils.build_nested_query({"sort_dir"=>"asc", "sort_by"=>"date_created", "filter_by"=>"lead"})
=> "sort_dir=asc&sort_by=date_created&filter_by=lead"
In addition to being more meaningful/readable (subjective), array.size.times
is faster than array.each_with_index
(objective). This was something that I had run across in a refactor of my own, where I ended up only needing an index value. I found this and quite a few other tidbits in this post.
my_array = [1, 2, 3, 4, 5, 6]
shuffled = my_array.shuffle # some random ordering, e.g. [3, 2, 1, 6, 4, 5]
Very useful for testing things where ordering shouldn’t matter and you want to explicitly declare that.
Ruby’s Array#sample
method can take an unnamed argument to return more than one random element.
%w[a b c].sample(2)
=> ["a", "c"]
By default, the Ruby JSON.parse
method returns a ruby Hash for any json object, and a ruby Array for any json array.
However, you can customize the returned object classes using the object_class
and array_class
options:
source = JSON.dump({ wibble: "wubble", data: [1,2,3] })
result = JSON.parse(
source,
object_class: OpenStruct,
array_class: Set
)
# => #<OpenStruct wibble="wubble", data=#<Set: {1, 2, 3}>>
result.data # => #<Set: {1, 2, 3}>
result.wibble # => "wubble"
Ruby String
has a #start_with?
variadic method:
"hello".start_with?("heaven", /hell/)
=> true
h/t Mary
rspec has a #thrice
method for testing receive counts:
describe Account do
context "when opened" do
it "logger#account_opened was called once" do
logger = double("logger")
account = Account.new
account.logger = logger
logger.should_receive(:account_opened).thrice
account.open
account.open
account.open
end
end
end
Ruby String#tr
allows you to replace characters or patterns in strings:
irb(main):001:0> "I love coffee".tr("love", "😍")
=> "I 😍😍😍😍 c😍ff😍😍"
Compare with #gsub
:
irb(main):001:0> "I love coffee".gsub("love", "😍")
=> "I 😍 coffee"
If your pattern
arg to gsub is only one character consider using #tr
, but beware of multi-length from_str
arg to #tr
I really like declarative things. It’s probably why I like React, and really dig functional programming approaches. So when I’m writing Ruby, sometimes I find myself wanting to delcare some array of values to use in a map
, reduce
, or each
. The problem is, sometimes I only want a particular value give a condition. One pattern that I’ve started to try on for size is
def things
[something, (something_else if reasons)]
end
Then, you can use it like so:
things.compact.map(&:cool_method)
The conditional will evaluate and leave a nil
in the array, which isn’t my favorite. However, I’ve found that this is a very useful pattern for simplifying certain methods.
Bit verbose, but:
self.class.superclass.instance_method(:method_name).bind(self).call
For those times when you want to use super
but you’re calling from outside of the actual method on super.
I don’t hate how explicit it is though. It can also be extracted into a helper class to eliminate some of the craziness.
Today I stumbled across a neat Ruby object, itself
. itself
returns the receiver, itself.
string = "my string"
string.itself.object_id == string.object_id #=> true
What’s a use case for this object? I used it to divide an array into arrays of matching integers.
> [1,99,99,1,2,3,2,5].group_by(&:itself)
=> {1=>[1, 1], 99=>[99, 99], 2=>[2, 2], 3=>[3], 5=>[5]}
Today I was working with a Ruby method that deletes the prefix from a string, turning --code
into code
. While there are several ways to get this done, a handy Ruby feature I discovered today is delete_prefix
:
> "--code".delete_prefix("--")
=> "code"
Intention-revealing and very Ruby.
While reading through the Rails 6 changelog, I noticed an entry for a rails command called notes
. Having never seen this before, I took a quick look at the Rails Guides.
The command rails notes
will return a list of all instances of the following annotations in your codebase - FIXME
, OPTIMIZE
, and TODO
.
You can optionally search for your own custom annotations with the --annotations
(-a
) flag:
rails notes -a NOTE
app/controllers/admin/blog_posts_controller.rb:
* [10] [NOTE] Only return the last 10 blog posts
README.md:
* [ 1] [NOTE] Set the following env variables
There’s also a way to register your own custom annotations for use with the default runner
config.annotations.register_tags("DEPRECATEME", "TESTME")
Since version 0.10.0, the pry
gem has shipped with a built in alias for whereami
: the extremely convenient @
Other useful aliases can be found using the help
command:
[1] pry(main)> help
Aliases
!!! Alias for `exit-program`
!!@ Alias for `exit-all`
$ Alias for `show-source`
? Alias for `show-doc`
@ Alias for `whereami`
clipit Alias for `gist --clip`
file-mode Alias for `shell-mode`
history Alias for `hist`
quit Alias for `exit`
quit-program Alias for `exit-program`
reload-method Alias for `reload-code`
show-method Alias for `show-source`
In Rails code, you’ll see a path constructed and returned as a string like this:
> Rails.root.join('app', 'models', 'my_path').to_s
=> "app/models/my_path"
An alternative method, that I think reads nicely, is to call to_path
:
Rails.root.join('app', 'models', 'my_path').to_path
=> "app/models/my_path"
Ruby’s Enumerable
class has a member?
method that returns a boolean.
For arrays, the method checks if the supplied value is included (similar to ['a'].include?('a')
):
[:a, :b].member?(:b) # => true
[:a, :b].member?(:c) # => false
For hashes, the method checks if the supplied value is included in the keys for the hash:
{ a: 'b' }.member?(:a) # => true
{ a: 'b' }.member?(:c) # => false
ruby -e 'print "\a"'