Reputation: 915
I have a rails generator that creates files based on some erb templates.
My goal is:
To have not too complicated template files. Something like this:
# example of erb template file
<%= wrap_in_modules do
"class Engine < ::Rails::Engine"
if mountable?
"isolate_namespace #{modules.first}"
end
"end"
end %>
And after parsing and creating, the example above should become:
module Naming
module Extension
class Engine < ::Rails::Engine
isolate_namespace Naming
end
end
end
The method that defines the wrap_in_modules
should take all the lines between the do..end
in the template file to process it with that module nesting.
But currently it does not. It only takes the last line.
This is my actual output for the generated file (for the same example as above)
module Naming
module Extension
end
end
end
As you can see, only the last line between the do..end
is being taken and wrapped in to the modules.
Okay, now here comes the actual wrap_in_modules
method.
def wrap_in_modules
modules.reverse.inject(yield) do |content, mod|
"module #{mod}\n" << content.lines.map { |line| " #{line}" }.join << "\nend"
end
end
I know, its documented: yield only returns the last expression evaluated on the block. And also does the proc.call
.
I took a look into the source of the rails content_tag
helper and tried to copy it because it does basically the same what I want here. But passing the &block
with an ampersand to the method and capturing it also does not work. Its just empty!?
Btw: another thing is that this notation will fail with a syntax error coming from erb:
<%= wrap_in_modules do -%>
Test test
<% end %>
lib/ruby/2.0.0/erb.rb:849:in `eval': (erb):1: syntax error, unexpected ')' (SyntaxError) ...r.concat(( wrap_in_modules do ).to_s); @output_buffer.concat...
... ^
(erb):4: syntax error, unexpected end-of-input, expecting ')'
; @output_buffer.force_encoding(ENCODING)
I wrack my brain for many hours now... how can I get more than just that last line to the method... Does someone has an idea?
Upvotes: 1
Views: 1332
Reputation: 1108
This will work:
<%= wrap_in_modules do
<<-STRING
class Engine < ::Rails::Engine
#{mountable? ? ('isolate_namespace ' + modules.first.to_s) : ''}
end
STRING
end %>
Upvotes: 0
Reputation: 303136
You can't implement any Ruby code to use your code block exactly as written to do what you want. You have multiple literal values but don't do anything with them. It would be the same as asking how to do this:
sum do
1
2
3
end #=> 6
It can't be done. Instead, you need to invoke some sort of method to accumulate the strings. For example:
# Evaluate the block in a context where `s` is defined to add strings to an array
wrap_in_modules do
s "class Engine < ::Rails::Engine"
if mountable?
s "isolate_namespace #{modules.first}"
end
s "end"
end
or
# Monkeypatch String class unary plus during this block invocation
# to add to your own collection
wrap_in_modules do
+"class Engine < ::Rails::Engine"
if mountable?
+"isolate_namespace #{modules.first}"
end
+"end"
end
Here's an example using refinements and global variable (bleah!):
module UglyAggregator
refine String do
def +@
$all << self
end
end
end
# Do this within your actual scope
using UglyAggregator
def many_strings(&block)
$all = []
yield
$all.join
end
p many_strings{
+"a"
+"b"
}
#=> "ab"
Upvotes: 1
Reputation: 6025
I have customized several generators myself, and you basically use them similar to rails views: all text that is "fixed" is just put "as is". The ruby around it is meant to create the variable stuff
Something like :
require 'spec_helper'
<% module_namespacing do -%>
describe <%= class_name %> do
<% for attribute in attributes -%>
it { should validate_presence_of(:<%= attribute.name %>) }
it { should validate_uniqueness_of(:<%= attribute.name %>) }
it { should ensure_length_of(:<%= attribute.name %>).is_at_least(3).is_at_most(255) }
<% end -%>
end
<% end -%>
This is a totally different approach, but much easier, as you can simply write your code, and "customize" it afterwards using ruby...
Upvotes: 0