Question

I have two examples that give the same result.

With block:

def self.do_something(object_id)
  self.with_params(object_id) do |params|
    some_stuff(params)
  end
end

def self.with_params(object_id, &block)
  find_object_by_id
  calculate_params_hash
  block.call(params_hash)
end

and with method:

def self.do_something(object_id)
  some_stuff(self.get_params(object_id))
end

def self.get_params(object_id)
  find_object_by_id
  calculate_params_hash
  params_hash
end

The second solution seems more straightforward, but I found some usages of the first one in our application code. My question is: in which situation the first one is recommended? What are the pros and cons of each one?

Was it helpful?

Solution

The main difference between a block and function as per your example is that the block runs within the context of the calling function.

So if your example was as:

def self.do_something(object_id)
  x = "boogy on"
  self.with_params(object_id) do |params|
    some_stuff(params)
    puts x
  end
end

The code within the block can access the variable x that was defined outside the block. This is called a closure. You couldn't do this if you were just calling a function as per your second example.

Another interesting thing about blocks is they can affect the control flow of the outer function. So it is possible to do :

def self.do_something(object_id)
  self.with_params(object_id) do |params|
    if some_stuff(params)
        return
    end
  end

  # This wont get printed if some_stuff returns true.
  puts "porkleworkle"
end

If the some_stuff call within the block returns a true value, the block will return. This will return out of the block and out of the dosomething method. porkleworkle would not get output.

In your examples you don't rely on either of these, so using function calls is probably much cleaner.

However, there are many situations where using blocks to allow you to take advantage of these things is invaluable.

OTHER TIPS

Normally people use blocks when they want to run a piece of code inside of another piece of code. Examples:

DB.with_shard_for_user(user_id) do |db|
  # perform operations on a user's shard

end # shard is reverted back to original value

File.new(filename) do |file|
  # work with file
end # file is closed automatically

User.transaction do
  # run some operations as a single transaction
end

These blocks are closed on their lexical context (they capture variables from where the block is declared, and carry them over to the place when blocks are called).

Schematic structure of a method that accepts a block.

def transaction
  open_transaction # pre- part

  yield if block_given? # run provided code

  commit_transaction # post- part
rescue
  rollback_transaction # handle problems
end

In your first example, use of a block is probably unjustified (IMHO). Too complex for no apparent reason.

When you call with_params(), you are not only sending in data, you are also providing some code to run. See if different blocks are sent into the with_params() call:

...
self.with_params(object_id) do |params|
  some_other_stuff()
  some_stuff(params)
end
...

and somewhere else:

...
self.with_params(object_id) do |params|
  even_more_stuff(params)
end
...

If the blocks are all the same or with_params() is just called from one place then you might consider eliminating the blocks.

To sum up: use blocks if you want to pass into the method different bits of code(blocks) as well as data: hey with_params, take this data(object_id) and, by the way, run this code(block) while you're at it.

BTW you are doing different things in the two examples: with_params() returns

some_stuff(params_hash)

after evaluating the block. And get_params() just returns

params_hash

A block totally rely on your code, but a function has its own code.

So, if your code varies situation by situation, use block. If not, build a function and use it as a block box.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top