Understanding scope in ruby block - ruby-on-rails

I'm using the Prawn gem to write to PDF. I have started an action to write the PDF but I don't understand how to use my data in the right way. I have:
def download
#bid = Bid.find(params[:bid_id])
#title = #bid.bid_title.gsub(/\s+/, "")
Prawn::Document.generate("#{#title}.pdf") do
text #bid.client_name
end
end
Where I add the text, the Bid is nil. How do I use the #bid that I created before in the block below?

It is often useful to dug into source code to understand how all the magic works.
If we consider Prawn source code, we can see that in method self.generate(filename, options = {}, &block) our block is transmitted to Prawn::Document.new method. Hence, we shall consider Prawn::Document initialize method. There we can see the following code:
if block
block.arity < 1 ? instance_eval(&block) : block[self]
end
#arity is a number of block arguments.
# block[self] is a block.call(self)
If we simplify Prawn source code, we can mock this situation in order to understand it better:
module Prawn
class Document
def self.generate(filename, &block)
block.arity < 1 ? instance_eval(&block) : block[self]
end
end
end
class A
def initialize
#a = 1
end
def foo
qwe = 1
Prawn::Document.generate("foobar") do
p #a
p qwe
p instance_variables
end
end
end
A.new.foo
# Output:
nil # #a
1 # qwe
[] # there is no instance_variables
But if we provide an argument for our block, another condition in generate will be called (block[self] instead of instance_eval):
module Prawn
class Document
def self.generate(filename, &block)
block.arity < 1 ? instance_eval(&block) : block[self]
end
end
end
class A
def initialize
#a = 1
end
def foo
qwe = 1
Prawn::Document.generate("foobar") do |whatever|
p #a
p qwe
p instance_variables
end
end
end
A.new.foo
# Output
1 # #a
1 # qwe
[:#a] # instance_variables
So in your situation this code will work I think:
def download
#bid = Bid.find(params[:bid_id])
#title = #bid.bid_title.gsub(/\s+/, "")
Prawn::Document.generate("#{#title}.pdf") do |ignored|
text #bid.client_name
end
end
or
def download
bid = Bid.find(params[:bid_id])
title = #bid.bid_title.gsub(/\s+/, "")
Prawn::Document.generate("#{title}.pdf") do
text bid.client_name
end
end

Your problem is that Prawn::Document.generate evaluates the block in the context of a Prawn::Document instance. This means that instance variables in the block will be resolved as instance variables of the Prawn::Document object, since that is self in the context of the block.
To make this work, use local variables instead of (or in addition to) instance variables.

Related

Modify instance variables which are parameters inside method

I want to pass instance variables to a method, which then modifies them. This is because I have the same logic for different instance variables. Is this possible? I haven't got it working.
class A
def update_ivar()
update(#ivar)
end
def update(var)
var = 1
end
def print_ivar()
puts #ivar
puts #ivar == nil
end
end
a = A.new
a.update_ivar()
a.print_ivar()
Output
true
You can use instance_variable_set like this:
class A
def update_ivar
update(:#ivar) # Note the symbolized name here, it is not the variable itself
end
def update(var_name)
instance_variable_set(var_name, 1)
end
def print_ivar
puts #ivar
puts #ivar == nil
end
end
a = A.new
a.update_ivar
a.print_ivar
#=> 1
#=> false
I personally wouldn't like such a pattern because it leads to hard to read and understand code. But is it a code smell? That certainly depends on your application and your exact use case.

How to pass a instance/accessor variable in ruby to another class and get data stored in respective variable?

Referring to the below code is there a way that I can pass the variable row from class A to class B#kick and get the data stored?
class A
attr_accessor :row
def fetch
B.new.kick(self.row)
puts row.inspect
end
end
class B
def kick(x)
x = [3,4]
end
end
#test = A.new.fetch
expect(#test.row).to eql([3,4])
Current O/P => nil
However If I pass self and assign that works , but I dont want to use this approach:
Working Code
class A
attr_accessor :row
def fetch
B.new.kick(self)
puts row.inspect
end
end
class B
def kick(x)
x.row = [3,4]
end
end
#test = A.new.fetch
#=> [3, 4]
Short version:
x = [3, 4] will create new instance of array and saves to x variable, where row will still reference to the original value(or no value nil).
Another approach could be the kick method to return "kicked" value.
class A
def fetch
#row = B.new.kick
puts row.inspect
end
end
class B
def kick(x)
[3,4]
end
end
If you want to follow object-oriented programming principle "Tell, don't ask" you can try visitor pattern approach.
class A
def fetch
B.new.kick(self)
puts row.inspect
end
def save(row)
#row = row
end
end
class B
def kick(x)
x.save([3,4])
end
end

How to forward a block to a method that generates methods

In my current project, I am having the following repetitive pattern in some classes:
class MyClass
def method1(pars1, ...)
preamble
# implementation method1
rescue StandardError => e
recovery
end
def method2(pars2, ...)
preamble
# implementation method2
rescue StandardError => e
recovery
end
# more methods with the same pattern
end
So, I have been thinking about how to dry that repetive pattern. My goal is to have something like this:
class MyClass
define_operation :method1, pars1, ... do
# implementation method1
end
define_operation :method2, pars2, ... do
# implementation method2
end
# more methods with the same pattern but generated with define_wrapper_method
end
I have tried to implement a kind of metagenerator but I have had problems for forwarding the block that would receive the generator. This is more or less what I have tried:
def define_operation(op_name, *pars, &block)
define_method(op_name.to_s) do |*pars|
preamble
yield # how can I do here for getting the block? <-----
rescue StandardError => e
recovery
end
end
Unfortunately, I cannot find a way for forwarding block to the define_method method. Also, it is very possible that the parameters, whose number is variable, are being passed to define_method in a wrong way.
I would appreciate any clue, help, suggestion.
You do not need metaprogramming to achieve this. Just add a new method that wraps the common logic like below:
class MyClass
def method1(param1)
run_with_recovery(param1) do |param1|
# implementation method1
end
end
def method2(param1, param2)
run_with_recovery(param1, param2) do |param1, param2|
# implementation method2
end
end
private
def run_with_recovery(*params)
preamble
yield(*params)
rescue StandardError => e
recovery
end
end
Test it here: http://rubyfiddle.com/riddles/4b6e2
If you really wanted to do metaprogramming, this will work:
class MyClass
def self.define_operation(op_name)
define_method(op_name.to_s) do |*args|
begin
puts "preamble"
yield(args)
rescue StandardError => e
puts "recovery"
end
end
end
define_operation :method1 do |param1|
puts param1
end
define_operation :method2 do |param1, param2|
puts param1
puts param2
end
end
MyClass.new.method1("hi")
MyClass.new.method2("hi", "there")
Test this here: http://rubyfiddle.com/riddles/81b9d/2
If I understand correctly you are looking for something like:
class Operation
def self.op(name,&block)
define_method(name) do |*args|
op_wrap(block.arity,*args,&block)
end
end
def op_wrap(arity=0,*args)
if arity == args.size || (arrity < 0 && args.size >= arity.abs - 1)
begin
preamble
yield *args
rescue StandardError => e
recovery
end
else
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{arity < 0 ? (arity.abs - 1).to_s.+('+') : arity })"
end
end
def preamble
puts __method__
end
def recovery
puts __method__
end
end
So your usage would be
class MyClass < Operation
op :thing1 do |a,b,c|
puts "I got #{a},#{b},#{c}"
end
op :thing2 do |a,b|
raise StandardError
end
def thing3
thing1(1,2,3)
end
end
Additionally this offers you both options presented as you could still do
def thing4(m1,m2,m3)
#m1 = m1
op_wrap(1,'inside_wrapper') do |str|
# no need to yield because the m1,m2,m3 are in scope
# but you could yield other arguments
puts "#{str} in #{__method__}"
end
end
Allowing you to pre-process arguments and decide what to yield to the block
Examples
m = MyClass.new
m.thing1(4,5,6)
# preamble
# I got 4,5,6
#=> nil
m.thing2('A','B')
# preamble
# recovery
#=> nil
m.thing3
# preamble
# I got 1,2,3
#=> nil
m.thing1(12)
#=> #<ArgumentError: wrong number of arguments (given 1, expected 3)>

Getting undefined method error in RSpec

I'm using RSpec and FactoryGirl for testing my models and I'm stuck at "highest_priority" method which can't be seen by RSpec for some reason.
Here's the method itself:
models/task.rb
class Task < ActiveRecord::Base
#some stuff
def self.highest_priority
p = Task.order(:priority).last.try(:priority)
p ? p + 1 : 1
end
end
And when I run task_spec.rb
require 'spec_helper'
describe Task do
it "returns highest priority" do
last_task = FactoryGirl.build(:task, priority: "5")
last_task.highest_priority
expect(last_task(:priority)).to eq("6")
end
end
I get the following error:
When I'm calling this method in my controller like this
def create
#task = current_user.tasks.build(task_params)
#task.highest_priority
#task.complete = false
respond_to do |format|
if #task.save
format.js
else
format.js
end
end
end
And the method looks like
def highest_priority
self.maximum(:priority).to_i + 1
end
I'm getting
First of all, you better use ActiveRecord's maximum instead of ordering and then picking one, you'll avoid the instance initialization and get a number directly from the query
Task.maximum(:priority)
this could be put in a class method like this
def self.maximum_priority
Task.maximum(:priority) || 0 # fall back to zero if no maximum exists
end
Then for the second half which is updating the method, i would create an instance method for that, and using the class method
def set_maximum_priority
self.priority = self.class.maximum_priority + 1
self
end
Note that I returned self at the end for chainability
Then your action would become something like this
def create
#task = current_user.tasks.build(task_params).set_maximum_priority
#task.complete = false
...
end
You need to create the method as an instance method of Task model. Like below :
class Task < ActiveRecord::Base
#some stuff
def highest_priority
p = Task.order(:priority).last.try(:priority)
p ? p + 1 : 1
end
end

Capture block in object initializer in Ruby

I have a class Notification::Pseudo in my rails application that has a custom initialize method. I would like this method to capture the output of the block that was passed to new and use that as the value for #message
class Notification::Pseudo
attr_accessor :message
def initialize(&block)
#message = begin
capture(&block) if block_given?
end || ""
end
end
In my view I then have something like
- notification = Notification::Pseudo.new do
This is a test!
This doesn't work though. This gives me the error ArgumentError: wrong number of arguments (0 for 1).
What is wrong w/ my initializer?
capture method you are calling is defined on Kernel module. You want to call capture from ActionView::Helpers::CaptureHelper module. It is automaticaly included into view context and you need to run it in this context so you need:
class Notification::Pseudo
attr_accessor :message
def initialize(vc, &block)
#message = begin
vc.capture(&block) if block_given?
end || ""
end
end
#In your view
- notification = Notification::Pseudo.new self do
This is a test!
UPDATE:
To make it work also outside of the view, do:
class Notification::Pseudo
attr_accessor :message
def initialize(vc = nil, &block)
#message = begin
return unless block_given?
vc ? vc.capture(&block) : block.call
end || ""
end
end

Resources