Blocks and Collections in Ruby

for (year = 1950, year < Date.current.year, year.advance(1.year))
    if year > 1974
        mocking_for_year[year].mock_about_overly_imperative_code!
    end
end

Alternatives

  • map
  • each
  • filter
  • detect
  • inject
  • for_each_object

Example

tag_conditions = tag_list.map do |tag|
  sanitize_sql(["tags.name like ?", tag])
end.join(" OR ")

What does do do?

Blocks

They let you pass code

into another context

and they are lexically scoped

They are not a λ

twice = lambda do |x|
  x * 2
end

twice.call(5) #=> 10
twice = -> (x) { x * 2 }

twice.call(5) #=> 10

Stabby Syntax

Block usefulness

Opening files (naive)

file = File.open("/etc/hosts")
data = file.read
...

File Descriptor just hanging out

file.close

Block usefulness

Better

data = File.open("/etc/hosts") do |file|
  file.read
end

Differences

Blocks Lambdas Procs
Is an object No Yes Yes
Multiple can be passed into function No Yes Yes
Check number of arguments ​Yes Yes No

Examples

Each

[34, 52, 88].each do |id|
  suspend_account(id)
end
for id in [34, 52, 88] do
  suspend_account(id)
end

each_with_index

[34, 52, 88].each_with_index do |i, index|
  Account.find(i).delete if index.odd?
end

Map

balances = []

[34, 52, 88].each do |id|
  balances << account_balance(id)
end
balances = [34, 52, 88].map do |id|
  account_balance(id)
end

Filter

active_accounts = [34, 52, 88].select do |id|
  Account.find(id).active?
end
active_accounts = []

[34, 52, 88].each do |id|
  active_accounts << id if Account.find(id).active?
end

Detect

big_number = [34, 52, 88].detect do |id|
  id > 50
end
big_number = nil

[34, 52, 88].each do |id|
  if id > 50
    big_number = id
    break
  end
end

each_with_object

by_currency = Account.all.each_with_object(Hash.new(0)) do |account, acc|
  acc[account.currency] += 1
end
by_currency = Account.all.group_by(&:currency)

group_by

index_by

by_currency = Account.all.index_by(&:id)

inject

sum = [34,52,88].inject(0) do |acc, n|
  acc + n
end
sum = [34,52,88].inject(0, &:+)

inject

sum

sum = [34,52,88].sum

inject

sum = [34,52,88].inject(0) do |acc, id|
  account = Account.find(id)
  acc + account.balance
end
sum = [34,52,88].map do |id|
 Account.find(id).balance
end.sum

map + inject

select

[34,52,88, 33].select(&:even?)
sum = [34,52,88].map do |id|
  Account.find(id)
end.reject do |account|
  account.active?
end.map(&:balance).sum

balance of inactive accts

any? many? all?

[34,52,88,33].any? do |i|
  i.even?
end
[34,52,88].all?(&:even?)

all?

Made with Slides.com