-
Notifications
You must be signed in to change notification settings - Fork 142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
String interpolation list item text #71
Comments
Hi, Andy. Scoping is an fairly gnarly issue for a gem like Caracal, but I suspect there is an existing way around your issue. What You Actually Asked Me AboutFirst, a small bit of background. The main difference between a Word document and an HTML document (for example) is that the Word document actually requires several output streams to be written and collected to form the "output". This complicates the rendering process for Caracal because it can't really just "render as it goes" like, say, Prawn can. For better or worse, Caracal was designed to model all DSL instructions first and only when all the instructions are stored does it attempt to produce the various output files and zip them into the final document. The reason this complicates things is because Caracal is fundamentally turning all your instructions into a series of nested model objects as it parses your commands, which by default rapidly destroys the idea of a single context in which the commands can be evaluated. Having written that, there is generally a way to preserve outer context. In an effort to give programmers some degree of control over how their instructions are interpreted, Caracal's BaseModel implements an arity strategy I discovered in similar gems when I was designing Caracal--it chooses a block evaluation strategy based on the arity of the block. So, if you call a Caracal method without a block argument (i.e., arity < 1), the gem assumes all the variables in the block are local and it uses Consider the following example, which removes Caracal from the equation: class InnerClass
attr_reader :birth_year
def initialize(age)
@birth_year = 2017 - age
end
def format(&block)
(block.arity < 1) ? instance_eval(&block) : block[self]
end
end
class OuterClass
attr_reader :name, :age, :inner
def initialize(name, age)
@name = name
@age = age
@inner = InnerClass.new(age)
end
# This evaluates self in the context of the caller
# so it explicitly prefixes the value coming from
# receiver.
#
def print_cool
inner.format do |ic|
puts "#{ name } - #{ age } - #{ ic.birth_year }"
end
end
# This evaluates self in the context of the caller
# but it fails to identify the birth year's owner.
# So, ruby thinks the caller owns birth_year, which
# is not true, hence the error.
#
def print_notcool
inner.format do |ic|
puts "#{ name } - #{ age } - #{ birth_year }"
end
end
# This evaluates self in the context of the receiver,
# so the birth year is cool but ruby has no idea what
# name and age mean now, hence the error.
#
def print_alsonotcool
inner.format do
puts "#{ name } - #{ age } - #{ birth_year }"
end
end
end You Didn't Ask, But I'd Appreciate Your OpinionFWIW, I realise this feature of Caracal is not documented in any way. Probably it should be. But I genuinely don't think there's a great, auto-magical way to spare advanced users from managing scope explicitly, but I'm 100% open to a comprehensively designed alternative to what I've implemented. I'm also not clear how best to even write up this discussion in a way that an ordinary ruby user would understand. From what I can tell, most Caracal users are building relatively simple, top-down Word documents through Rails, so I typically advise them to localise their values into simple strings/objects and reference those in their blocks. (The |
A Practical SolutionI'm going to close this issue, so I wanted to follow-up in case you were still interested. (If not, that's also 100% fine.) I added a link to this discussion in the README to help others with lexical scoping questions. I'm also going to explicitly answer your immediate question so that the issue will include a "solution" per se. The ruby issue in your example is that the variables are implicitly referencing To get the example to work, we need to tell the interpreter explicitly which object owns class MyClass
attr_reader :name, :age
def initialize(name, age)
@name = name
@age = age
end
def create_doc
obj = self
Caracal::Document.save('my_file.docx') do |document|
document.h1 'Example'
document.ul do
li "Name: #{obj.name}"
li "Age: #{obj.age}"
end
end
end
end
MyClass.new('john', 30).create_doc |
hey, I had the same problem and had a hard time finding this issue, I believe it would be nice to add that to the readme. |
Hi, @Uelb. There is a direct link to this issue in the README under a section named Using Variables. Were you recommending something other than that? |
Actually I missed that and it may be enough ! Thanks. Maybe a better way would be to directly include the bad and the good example in the readme. |
Hi, this could also work using
inside the initialize method and then using |
Consider the following:
String interpolation into a list item seems like a pretty general need for anyone attempting to dynamically generate a document. As one possible alternative, I would not mind being able to pass an enumerable
data
attribute to thedocument.ul
call and let Caracal add the list items from the enumerable (much like#table
). This could be done as one alternative to list generation with a fall back to the current, explicit method.The text was updated successfully, but these errors were encountered: