rubyvariablesinstance-variablessite-prism

How to create and use variables dynamically named by string values in Ruby?


I'm using SitePrism to create some POM tests. One of my page classes looks like this:

class HomePage < SitePrism::Page
    set_url '/index.html'
    element :red_colour_cell, "div[id='colour-cell-red']"
    element :green_colour_cell, "div[id='colour-cell-green']"
    element :blue_colour_cell, "div[id='colour-cell-blue']"

    def click_colour_cell(colour)
        case colour
            when 'red'
                has_red_colour_cell?
                red_colour_cell.click
            when 'green'
                has_green_colour_cell?
                green_colour_cell.click
            when 'blue'
                has_blue_colour_cell?
                blue_colour_cell.click
        end
    end
end

The method click_colour_cell() get its string value passed from a Capybara test step that calls this method. If I need to create additional similar methods in the future, it can become rather tedious and unwieldy having so many case switches to determine the code flow.

Is there some way I can create a variable that is dynamically named by the string value of another variable? For example, I would like to do something for click_colour_cell() that resembles the following:

    def click_colour_cell(colour)
        has_@colour_colour_cell?
        @colour_colour_cell.click
    end

where @colour represents the value of the passed value, colour and would be interpreted by Ruby:

    def click_colour_cell('blue')
        has_blue_colour_cell?
        blue_colour_cell.click
    end

Isn't this what instance variables are used for? I've tried the above proposal as a solution, but I receive the ambiguous error:

syntax error, unexpected end, expecting ':'
    end
    ^~~ (SyntaxError)

If it is an instance variable that I need to use, then I'm not sure I'm using it correctly. if it's something else I need to use, please advise.


Solution

  • Instance variables are used define properties of an object.

    Instead you can achieve through the method send and string interpolation.

    Try the below:

    def click_colour_cell(colour)
      send("has_#{colour}_colour_cell?")
      send("#{colour}_colour_cell").click
    end
    

    About Send:

    send is the method defined in the Object class (parent class for all the classes).

    As the documentation says, it invokes the method identified by the given String or Symbol. You can also pass arguments to the methods you are trying to invoke.

    On the below snippet, send will search for a method named testing and invokes it.

    class SendTest
      def testing
        puts 'Hey there!'
      end
    end
    
    
    obj = SendTest.new
    obj.send("testing")
    obj.send(:testing)
    

    OUTPUT

    Hey there!
    Hey there!
    

    In your case, Consider the argument passed for colour is blue,

    "has_#{colour}_colour_cell?" will return the string"has_blue_colour_cell?" and send will dynamically invoke the method named has_blue_colour_cell?. Same is the case for method blue_colour_cell