Capture block in object initializer in Ruby - ruby-on-rails

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

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.

Call a block on a Model

Instead of defining a scope in a class like this:
scope :first_user, -> { first }
And calling it like this: User.first_user
I would like to define a block in another class, that can be called on the user class and works like a Scope:
This code is not working but it should signalize what behaviour I want to achieve:
class Manage
def get_first_user
User.&first_added
end
def first_added
Proc.new { first }
end
end
When I run this code:
a = Manage.new
a.get_first_user
it says me, & undefined method for User. How can I execute the Block defined in first_added on the User model?
How can I in general call a block on a class? Thanks
If I understand your question correctly, you can use class_eval:
>> foo = Proc.new { count }
=> #<Proc:0x007f1aa7cacfd8#(pry):30>
>> Buyer.class_eval(&foo)
(30.6ms) SELECT COUNT(*) FROM "buyers"
=> 1234
Or with your example:
class Manage
def get_first_user
User.class_eval(&first_added)
end
def first_added
Proc.new { first }
end
end
It is not what you want but maybe this will be helpful.
I am not sure if one can call proc on something. I think one can only call proc with something, i.t. in your case passing User as parameter.
def get_first_user
wrap User, &first_added
end
def first_added
Proc.new { |model| model.where(...) }
end
private
def wrap(model, &block)
yield model
end
Here's three ways to call first on User from Manager using:
Object#send:
http://ruby-doc.org/core-2.3.1/Object.html#method-i-send
Module#class_eval:
http://ruby-doc.org/core-2.3.1/Module.html#method-i-class_eval
File manage.rb:
class User
def self.first
puts 'record would probably go here'
end
def self.yielder
print "via yielder => "
self.send(yield) if block_given?
end
def self.blocker(&block)
print "via blocker => "
self.send(block.call) if block_given?
end
def self.evaller(&block)
print "via evaller => "
self.class_eval(block.call) if block_given?
end
end
class Manage
def get_first_user
User.yielder(&first_added)
User.blocker(&first_added)
User.evaller(&first_added)
end
def first_added
Proc.new {"first"}
end
end
a = Manage.new
a.get_first_user
Output:
$ ruby manage.rb
via yielder => record would probably go here
via blocker => record would probably go here
via evaller => record would probably go here

Understanding scope in ruby block

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.

Rails - how to enforce one method call per method, like controllers do with `render`

So in Rails, you get an error if you try to call render multiple times within a controller action.
I have another Ruby class that I'm writing, and I'd like to try to do something similar (make sure that my own respond_with method is only called once.
So for example, this would be fine:
def my_method
if (my_value == true)
...
respond_with(:a, :b, :c)
else
...
respond_with(:x, :y, :z)
end
end
But this would raise an error if my_value == 4
def my_method
if (my_value >= 4)
...
respond_with(:a, :b, :c)
end
if (my_value <= 4)
...
respond_with(:d, :e, :f)
else
...
respond_with(:x, :y, :z)
end
end
Any thoughts on how to best accomplish that?
class MyBaseClass
def respond_with(arguments)
if #rendered
raise DoubleRenderError #or whatever
end
#rendered = true
#whatever the respond_with function should do
end
end
Here's one way I could think of - in your Ruby class, define a #responded attribute by doing attr_accessor :responded. In your respond_with method, add the following lines:
def respond_with
raise DoubleRenderError if self.responded
# do stuff
self.responded = true
end
The above code should raise an error if you call respond_with twice on the same object.

How to refactor this Rails code?

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

Resources