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.
Related
Want to achieve the following code using metaprogramming.
#resource = {}
#voters = {}
#is_upvoted = {}
def resource(comment)
#resource[comment.id]
end
def voters(comment)
#voters[comment.id]
end
def is_upvoted(comment)
#is_upvoted[comment.id]
end
How can I create these methods using ruby metaprogramming and access the hash?
Can you tell me what is wrong in my code ?
['resource', 'voters', 'is_upvoted'].each do |attribute|
define_method("#{attribute}") do |comment|
instance_variable_set("##{attribute}", comment.id)
end
end
This bit seems redundant:
#resource = {}
#voters = {}
#is_upvoted = {}
Since you're already looping an array to do your metaprogramming.
You might try something like:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |comment|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
instance_variable_get("##{attr_sym}")[comment.id]
end
end
end
Which I believe will give you methods roughly like:
class Foo
def resource(comment)
#resource ||= {}
#resource[comment.id]
end
end
Personally, it seems not great to me to have comment.id in your method. Because what if someday you want to use a different attribute (or something else altogether) as the key?
So, I think I would do:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |key|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
instance_variable_get("##{attr_sym}")[key]
end
end
end
Now, it seems like you're going to want an easy way to set key-value pairs on your instance variable, so I guess I would try something like:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |key=nil|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
hsh = instance_variable_get("##{attr_sym}")
return hsh[key] if key
hsh
end
end
end
In which case you should be able to do (assuming you have a #comment variable that responds to id):
#comment.id
=> 1
foo = Foo.new
=> #<Foo:0x000056536d7504b0>
foo.resource
=> {}
foo.resource[#comment.id] = :bar
=> :bar
foo.resource
=> {1=>:bar}
foo.resource[#comment.id]
=> :bar
Can you tell me what is wrong in my code ?
It's doing the equivalent of this:
def resource(comment)
#resource = comment.id
end
instance_variable_get would be a better choice.
This is how I used it and it works
['resource', 'voters', 'is_upvoted'].each do |attribute|
define_method("#{attribute}") do |comment|
instance_variable_get("##{attribute}")[comment.id]
end
end
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
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.
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
Consider this helper method:
module SomeHelper
def display_button
Foo.find_by_id params[:id] and Foo.find(params[:id]).organizer.name != current_name and Foo.find(params[:id]).friends.find_by_name current_name
end
end
How to refactor into something more readable?
Rails 3.2.2
Something like this?
module SomeHelper
def display_button?
if foo = Foo.find(params[:id])
foo.organizer.name != current_name if foo.friends.find_by_name(current_name)
end
end
end
Note: if the helper method is returning a boolean, I would append the name with a ? ... ruby convention.
You can factorize the call to Foo.find(params[:id]) and use exists? for the third condition
module SomeHelper
def display_button
foo = foo.find_by_id params[:id]
foo and foo.organizer.name != current_name and foo.friends.where(:name => current_name).exists?
end
end
You can also create several methods to gain on reusability (and will save trouble if you model changes):
module SomeHelper
def display_button
foo = foo.find_by_id params[:id]
foo && !is_organizer?(foo, current_name) && has_friend?(foo, current_name)
end
def is_organizer?(foo, name)
foo.organizer.name == name
end
def has_friend?(foo, name)
foo.friends.where(:name => name).exists?
end
end
try invokes the passed block on non-nil objects. Returns nil otherwise. So the return will be nil,true,false depending on your data.
def display_button
Foo.find_by_id(params[:id]).try do |foo|
foo.organizer.name != current_name &&
foo.friends.find_by_name current_name
end
end