What's the cleanest way to define a constant string that involves a variable in Ruby?

StackOverflow https://stackoverflow.com/questions/19774577

  •  03-07-2022
  •  | 
  •  

Question

For some context, a lot of my code has the same lines of text throughout it (we are using Calabash to do iOS automation, if that gives you an idea).

For example: "all label marked:'#{name}'" is used 8 times in a particular class.

I would prefer to be able to have a constant that uses that text, but if I throw it at the top of the class, of course the variable "name" has not been set yet. Without defining a method that takes a parameter and returns a string, is there a way to do something essentially like this that can exist at the top of the class, but not be evaluated until it's used?:

class ClassName
  extend Calabash::Cucumber::Operations

  @NAME_CONSTANT = "all label marked:'#{name}'"

  def self.method_name(name)
    query("#{@NAME_CONSTANT} sibling label marked:'anotherLabel' isHidden:0")
  end
end

If you use the syntax I mentioned, you get this error: undefined local variable or method `name' for ClassName

Was it helpful?

Solution

You could use String#% to insert the string later.

class ClassName
    @NAME_CONSTANT = "all label marked:'%{name}'"

    def self.method_name(insert_name)
        query("#{@NAME_CONSTANT} sibling label marked:'anotherLabel' isHidden:0" % {name: insert_name})
    end

    def self.query(string)
        puts string
    end 
end 

ClassName.method_name('test')
#=> "all label marked:'test' sibling label marked:'anotherLabel' isHidden:0"

OTHER TIPS

I agree with @Sergio. Don't define a constant string that includes a variable. Just use a method. Including a variable in a constant seems like a bad idea. Constants shouldn't be dynamic, by definition.

If you really want to include a variable in a constant string, you can assign a lambda to a contstant, like so:

class ClassName
  extend Calabash::Cucumber::Operations

  NAME_CONSTANT = ->(name) { "all label marked:'#{name}'" }

  def self.method_name(name)
    query("#{NAME_CONSTANT.call(name)} sibling label marked:'anotherLabel' isHidden:0")
  end
end

I removed the @ before the constant, since including it creates a class-level instance variable, not a constant.

I really wouldn't use the code sample I posted, though. Just use a method. Avdi Grimm has a good post called "Do we need constants?" where he describes some of the benefits of using methods instead of constants.

The fundamental issue you're facing is that string interpolation occurs at the time the literal is interpreted and the scope of any referenced variables is determined by the location of the string in the code.

If you put the interpolated string in a method, then it won't have access to the local definition of any variables used in the string. You'd have to pass in the value of any variables used, as in:

def name_constant(name)
  "all label marked:'#{name}'"
end

Alternatively, you'd need to declare the "constant" as an uninterpreted string as follows:

@name_constant = '"all label marked:''#{name}''"'

and then interpret it when you reference it, as follows:

eval(@name_constant)

BTW, I've ignored the issue of this not really being a "constant" and using instance variables vs. class variables.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top