A Ruby block question


#1

I am in need of making a method that accepts a block. It basicly needs
to do the following:

  • The method it self should output some content first.
  • Then after that content it should output the content defined in
    &block.
  • And at last output some other content defined by the method it self.

In other words I need to put user-defined input in between some
pre-defined content. How can I approach that?


#2

Alle Thursday 16 October 2008, David T. ha scritto:

I am in need of making a method that accepts a block. It basicly needs
to do the following:

  • The method it self should output some content first.
  • Then after that content it should output the content defined in
    &block.
  • And at last output some other content defined by the method it self.

In other words I need to put user-defined input in between some
pre-defined content. How can I approach that?

If I understand correctly what you want to do, you don’t need to do
anything
special:

def my_method
puts “predefined text 1”
puts yield
puts “predefined text 2”
end

Stefano


#3

On Thu, Oct 16, 2008 at 08:20:34PM +0900, David T. wrote:

I am in need of making a method that accepts a block. It basicly needs
to do the following:

  • The method it self should output some content first.
  • Then after that content it should output the content defined in
    &block.
  • And at last output some other content defined by the method it self.

In other words I need to put user-defined input in between some
pre-defined content. How can I approach that?

Look at yield?

def foo(&block)
puts “Oh Hai”
yield
puts “kthxbai”
end

fooi {puts “No Wai!”}


nathan
nathan_at_nathanpowell_dot_org

Another flaw in the human character is that everybody wants to build
and nobody wants to do maintenance.
~ Kurt Vonnegut


#4

Stefano C. wrote:

In other words I need to put user-defined input in between some
pre-defined content. How can I approach that?

If I understand correctly what you want to do, you don’t need to do
anything
special:

def my_method
puts “predefined text 1”
puts yield
puts “predefined text 2”
end

My method looks like this:

def fields_for_setting(namespace, name, &block)
namespace = namespace.to_s
name = name.to_s
id = “settings_#{namespace}#{name}
m = Builder::XmlMarkup.new :indent => 2
fields_for “settings[]”, setting =
Setting.find_or_initialize_by_namespace_and_name(namespace, name) do |f|
m.p do
unless setting.new_record?
m << (f.hidden_field :id, :index => nil, :id => id+“id”)
end
m << (f.hidden_field :namespace, :index => nil, :id =>
id+“namespace”)
m << (f.hidden_field :name, :index => nil, :id => id+“name”)
#puts yield f???
end
end
end

The problem is, I’m using Builder::XmlMarkup and the fields_for from
Rails. I want to put some hidden fields AND the text fields defined by
the block into a

tag. That means I need to be able to access the f
object provided by fields_for and use it in this method’s block.

But how?


#5

Alle Thursday 16 October 2008, David T. ha scritto:

puts yield
fields_for “settings[]”, setting =
end
end

The problem is, I’m using Builder::XmlMarkup and the fields_for from
Rails. I want to put some hidden fields AND the text fields defined by
the block into a

tag. That means I need to be able to access the f
object provided by fields_for and use it in this method’s block.

But how?

I don’t know Rails and Builder::XmlMarkup, so I can be completely wrong.
From
a quick look at Builder::XmlMarkup api, I’d say that, if the block
returns the
string you want to insert in the p element, you only have to insert it
in m
like you did for the texts:

m << yield f

Stefano


#6

David T. wrote:

syntax error, unexpected tIDENTIFIER, expecting kEND

Then I tried this:

m << (yield f)

But that is giving me this error:

can’t convert nil into String

That doesn’t really make sence, does it?


#7

Stefano C. wrote:

But how?

I don’t know Rails and Builder::XmlMarkup, so I can be completely wrong.
From
a quick look at Builder::XmlMarkup api, I’d say that, if the block
returns the
string you want to insert in the p element, you only have to insert it
in m
like you did for the texts:

m << yield f

Yes, that was also my first idea, but that is giving me this syntax
error:

syntax error, unexpected tIDENTIFIER, expecting kEND


#8

Hi –

On Thu, 16 Oct 2008, David T. wrote:

can’t convert nil into String

That doesn’t really make sence, does it?

Actually it does, because your block contains a call to puts, and puts
always returns nil. You probably want your block just to contain a
string.

David


#9

Hi –

On Thu, 16 Oct 2008, David T. wrote:

like you did for the texts:

m << yield f

Yes, that was also my first idea, but that is giving me this syntax
error:

syntax error, unexpected tIDENTIFIER, expecting kEND

Put f in parens:

m << yield(f)

David


#10

On Thu, 16 Oct 2008, David T. wrote:

m << yield(f)

That’s also a possibility. But it seems to be a little more complicated
as mentioned.

can’t convert nil into String

You’re one post behind – read my previous answer :slight_smile:

David


#11

David A. Black wrote:

m << yield f

Yes, that was also my first idea, but that is giving me this syntax
error:

syntax error, unexpected tIDENTIFIER, expecting kEND

Put f in parens:

m << yield(f)

That’s also a possibility. But it seems to be a little more complicated
as mentioned.

can’t convert nil into String


#12

David A. Black wrote:

Hi –

On Thu, 16 Oct 2008, David T. wrote:

can’t convert nil into String

That doesn’t really make sence, does it?

Actually it does, because your block contains a call to puts, and puts
always returns nil. You probably want your block just to contain a
string.

Ah, I see. Let me just explain. Since I’m using Rails I am calling this
method with the block in a view or a template. My template language is
Haml, and this is how it’s called:

  • fields_for_setting(“site”, “name”) do |f|
    = f.label :value, “Site name”, :index => nil
    = f.text_field :value, :index => nil

The = is a short hand for puts as you pointed out. But I don’t see other
possibilities than using put, otherwise the text field will not show up
in the view.

How can I both use puts and make some content appear around the text
field?


#13

David T. wrote:

How can I both use puts and make some content appear around the text
field?

I was wondering if concat might suit my needs?


#14

David A. Black wrote:

How can I both use puts and make some content appear around the text
field?

I’m not a HAML expert, but in general, you don’t want to use puts for
anything you’re outputting in a template. You just want to insert
strings. I’m not sure how HAML handles string concatenation of this
kind.

Phew, this got a little more complicated than I hoped. To make this
understandable I’ll just explain the problem. I want a method that
accepts a block. Inside the method I’m using the method fields_for that
also accepts a block. I want the f object provided by fields_for passed
over to the block that MY method accepts, like this:

fields_for_setting(“site”, “name”) do |f|
#--------------------------------------^
#This should be the f object that fields_for provides.
= f.text_field :value, :index => nil

I simply want to reuse the f object to make my view shorter and cleaner.
Both in the method where the fields_for is used and in the view, where
it should be reused.


#15

On Thu, 16 Oct 2008, David T. wrote:

always returns nil. You probably want your block just to contain a
The = is a short hand for puts as you pointed out. But I don’t see other
possibilities than using put, otherwise the text field will not show up
in the view.

How can I both use puts and make some content appear around the text
field?

I’m not a HAML expert, but in general, you don’t want to use puts for
anything you’re outputting in a template. You just want to insert
strings. I’m not sure how HAML handles string concatenation of this
kind.

David


#16

David T. wrote:

Actually I think I’m close to a solution now. I made these two methods:

def fields_for_setting(namespace, name)
id = “settings_#{namespace}#{name}
m = Builder::XmlMarkup.new :indent => 2
fields_for “settings[]”, setting =
Setting.find_or_initialize_by_namespace_and_name(namespace, name) do |f|
m.p do
unless setting.new_record?
m << (f.hidden_field :id, :index => nil, :id => id+“id”)
end
m << (f.hidden_field :namespace, :index => nil, :id =>
id+“namespace”)
m << (f.hidden_field :name, :index => nil, :id => id+“name”)
m << yield(f)
end
end
end

def text_field_for_setting(namespace, name, label=nil)
namespace = namespace.to_s
name = name.to_s
label ||= “#{namespace.capitalize} #{name}”
id = “settings_#{namespace}#{name}
fields_for_setting(namespace, name) do |f|
f.label :value, label, :index => nil, :for => id+“value”
f.text_field :value, :index => nil, :id => id+“value”
end
end

In my view I’m calling the second method (text_field_for_setting) like
this:

= text_field_for_setting(:site, :owner)

The method that is called here then calls my first method
(fields_for_setting) with a block. This actually works fine, but for
some reason that I can’t figure out (I hope you can help me) It only the
LAST line of the block that is passed to fields_for_setting that shows
up.

In other words, even though I declare both a label and a text field,
only the text field shows up. How comes?


#17

David A. Black wrote:

In other words, even though I declare both a label and a text field,
only the text field shows up. How comes?

Because a block, like a method call, evaluates to the last expression
inside it. For example:

Okay, I see.

So maybe inside your block you want to concatenate the two strings.

Okay, I’m using #concat instead. It works just fine, thanks!


#18

Hi –

On Thu, 16 Oct 2008, David T. wrote:

 unless setting.new_record?
   m << (f.hidden_field :id, :index => nil, :id => id+"id")
 end
 m << (f.hidden_field :namespace, :index => nil, :id =>

id+“namespace”)
m << (f.hidden_field :name, :index => nil, :id => id+“name”)

That syntax will work but it would be much more idiomatic to do:

m << f.hidden_field(:name, :index …)

fields_for_setting(namespace, name) do |f|
The method that is called here then calls my first method
(fields_for_setting) with a block. This actually works fine, but for
some reason that I can’t figure out (I hope you can help me) It only the
LAST line of the block that is passed to fields_for_setting that shows
up.

In other words, even though I declare both a label and a text field,
only the text field shows up. How comes?

Because a block, like a method call, evaluates to the last expression
inside it. For example:

def get_string
str = yield
puts str
end

get_string do
“First string”
“Second string”
end

This will print “Second string”. “First string” is just thrown away.

So maybe inside your block you want to concatenate the two strings.

David


#19

David T. wrote:

So maybe inside your block you want to concatenate the two strings.

Okay, I’m using #concat instead. It works just fine, thanks!

I must have overseen something. Today it doesn’t work very well. I do
get my label and text field but I don’t get the surrounding

tag and
the hidden fields. My methods looks like this:

def fields_for_setting(namespace, name)
id = “settings_#{namespace}#{name}
m = Builder::XmlMarkup.new :indent => 2
fields_for “settings[]”, setting =
Setting.find_or_initialize_by_namespace_and_name(namespace, name) do |f|
m.p do
unless setting.new_record?
m << (f.hidden_field :id, :index => nil, :id => id+“id”)
end
m << (f.hidden_field :namespace, :index => nil, :id =>
id+“namespace”)
m << (f.hidden_field :name, :index => nil, :id => id+“name”)
m << yield(f)
end
end
end

def text_field_for_setting(namespace, name, label=nil)
namespace = namespace.to_s
name = name.to_s
label ||= “#{namespace.capitalize} #{name}”
id = “settings_#{namespace}#{name}
fields_for_setting(namespace, name) do |f|
concat f.label :value, label, :index => nil, :for => id+“value”
concat f.text_field :value, :index => nil, :id => id+“value”
end
end

So, I’m concatenating the label and text field inside the
fields_for_setting block. But the fields_for_setting block doesn’t have
any effect at all! I think it’s because fields_for_setting is only
returning the

and hidden fields, but how is this solved?


#20

David A. Black wrote:

… but how is this solved?

Try this:

fields_for_setting(namespace, name) do |f|
f.label :value, label, :index => nil, :for => id+“value” +
f.text_field :value, :index => nil, :id => id+“value”
end

i.e., returning a string from the block.

syntax error, unexpected tSYMBEG, expecting kEND

But I grabbed your concept, this actually works:

def fields_for_setting(namespace, name)
output = “”
id = “settings_#{namespace}#{name}
m = Builder::XmlMarkup.new(:indent => 2, :target => output)
fields_for “settings[]”, setting =
Setting.find_or_initialize_by_namespace_and_name(namespace, name) do |f|
m.p do
unless setting.new_record?
m << f.hidden_field(:id, :index => nil, :id => id+“id”)
end
m << f.hidden_field(:namespace, :index => nil, :id =>
id+“namespace”)
m << f.hidden_field(:name, :index => nil, :id => id+“name”)
m << yield(f)
end
end
puts output
end

def text_field_for_setting(namespace, name, label=nil)
namespace = namespace.to_s
name = name.to_s
label ||= “#{namespace.capitalize} #{name}”
id = “settings_#{namespace}#{name}
fields_for_setting(namespace, name) do |f|
o = f.label(:value, label, :index => nil, :for => id+“value”)
o << f.text_field(:value, :index => nil, :id => id+“value”)
end
end

But, come on! There must be a better solution. This doesn’t seem very
idiomatic to me. Nobody got a better idea?