Forum: Ruby A Ruby block question

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 13:20
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?
C40020a47c6b625af6422b5b1302abaf?d=identicon&s=25 Stefano Crocco (crocco)
on 2008-10-16 13:25
(Received via mailing list)
Alle Thursday 16 October 2008, David Trasbo 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
3f4de376ba800ba092d9e1e332ede421?d=identicon&s=25 Nathan Powell (bignate)
on 2008-10-16 13:28
(Received via mailing list)
On Thu, Oct 16, 2008 at 08:20:34PM +0900, David Trasbo 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
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 13:34
Stefano Crocco 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 <p> 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?
C40020a47c6b625af6422b5b1302abaf?d=identicon&s=25 Stefano Crocco (crocco)
on 2008-10-16 13:52
(Received via mailing list)
Alle Thursday 16 October 2008, David Trasbo 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 <p> 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
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 13:55
Stefano Crocco 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
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 13:58
David Trasbo 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?
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2008-10-16 13:59
(Received via mailing list)
Hi --

On Thu, 16 Oct 2008, David Trasbo 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
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2008-10-16 14:05
(Received via mailing list)
Hi --

On Thu, 16 Oct 2008, David Trasbo 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
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 14:06
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
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2008-10-16 14:11
(Received via mailing list)
On Thu, 16 Oct 2008, David Trasbo 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 :-)


David
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 14:11
David A. Black wrote:
> Hi --
>
> On Thu, 16 Oct 2008, David Trasbo 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?
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 14:27
David Trasbo 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?
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2008-10-16 14:47
(Received via mailing list)
On Thu, 16 Oct 2008, David Trasbo 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
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 14:54
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.
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 15:48
David Trasbo 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?
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2008-10-16 16:30
(Received via mailing list)
Hi --

On Thu, 16 Oct 2008, David Trasbo 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
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-16 16:37
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!
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-17 09:18
David Trasbo 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 <p> 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 <p> and hidden fields, but how is this solved?
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2008-10-17 12:27
(Received via mailing list)
Hi --

On Fri, 17 Oct 2008, David Trasbo wrote:

> def fields_for_setting(namespace, name)
> id+"namespace")
>  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 <p> and hidden fields, 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.


David
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-17 19:33
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?
4a551074ddba4460f95d011c47190d0e?d=identicon&s=25 Henrik --- (malesca)
on 2008-10-18 10:15
(Received via mailing list)
On Fri, Oct 17, 2008 at 7:32 PM, David Trasbo <davidtrasbo@gmail.com>
wrote:

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

I skipped through most of this thread, so this might be off target,
but have you looked into using Rails' "capture" helper to get a block
of Haml into a string?
http://api.rubyonrails.com/classes/ActionView/Help...
941120dac2e97cf8bc1cbe0216159104?d=identicon&s=25 David Trasbo (datra)
on 2008-10-18 18:39
Henrik --- wrote:

>> But, come on! There must be a better solution. This doesn't seem very
>> idiomatic to me. Nobody got a better idea?
>
> I skipped through most of this thread, so this might be off target,
> but have you looked into using Rails' "capture" helper to get a block
> of Haml into a string?
> http://api.rubyonrails.com/classes/ActionView/Help...

No, actually I haven't. Thank you very much for your suggestion. I'm
using that now. (:
This topic is locked and can not be replied to.