Как создавать и использовать переменные, динамически именуемые строковыми значениями в Ruby?

#ruby #variables #instance-variables #site-prism

#ruby #переменные #экземпляр-переменные #сайт-prism

Вопрос:

Я использую SitePrism для создания некоторых тестов POM. Один из моих классов page выглядит следующим образом:

 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
  

Метод click_colour_cell() получает свое строковое значение, переданное с шага тестирования Capybara, который вызывает этот метод.
Если мне понадобится создать дополнительные подобные методы в будущем, это может стать довольно утомительным и громоздким из-за такого количества переключений регистра для определения потока кода.

Есть ли какой-нибудь способ, которым я могу создать переменную, которой динамически присваивается строковое значение другой переменной? Например, я хотел бы сделать что-то для click_colour_cell() , похожее на следующее:

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

где @colour представляет значение переданного значения, colour и будет интерпретироваться Ruby:

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

Разве не для этого используются переменные экземпляра? Я попробовал вышеупомянутое предложение в качестве решения, но получаю неоднозначную ошибку:

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

Если мне нужно использовать переменную экземпляра, то я не уверен, что использую ее правильно. если мне нужно использовать что-то еще, пожалуйста, посоветуйте.

Ответ №1:

Переменные экземпляра используются для определения свойств объекта.

Вместо этого вы можете достичь этого с помощью метода отправки и интерполяции строк.

Попробуйте следующее:

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

Об отправке:

send определен ли метод в Object классе (родительский класс для всех классов).

Как говорится в документации, он вызывает метод, идентифицируемый заданной строкой или символом. Вы также можете передавать аргументы методам, которые пытаетесь вызвать.

В приведенном ниже фрагменте send выполнит поиск метода с именем testing и вызовет его.

 class SendTest
  def testing
    puts 'Hey there!'
  end
end


obj = SendTest.new
obj.send("testing")
obj.send(:testing)
  

ВЫВОД

 Hey there!
Hey there!
  

В вашем случае рассмотрим аргумент, переданный для colour является blue ,

"has_#{colour}_colour_cell?" вернет строку "has_blue_colour_cell?" , а send будет динамически вызывать метод с именем has_blue_colour_cell? . То же самое относится и к методу blue_colour_cell

Комментарии:

1. Я могу подтвердить, что это действительно работает. Но как? Я не знаком с send методом, и предоставленная документация описывает другую цель для send того, для чего я могу его использовать. Мне действительно интересно больше понять, как это на самом деле работает.

2. Спасибо за объяснение. Я нашел это решение наиболее полезным для моей ситуации на данный момент. Однако я хотел бы ограничить свое использование send() в будущем и рассмотреть другие решения, если они будут предложены.

Ответ №2:

Прямой ответ на ваш вопрос

Вы можете динамически получать / устанавливать переменные экземпляра с помощью:

 instance_variable_get("@build_string_as_you_see_fit")
instance_variable_set("@build_string_as_you_see_fit", value_for_ivar)
  

Но…

Предупреждение!

Я думаю, что динамическое создание переменных здесь и / или использование таких вещей, как имена методов построения строк в send , являются плохой идеей, которая сильно затруднит будущую ремонтопригодность.

Подумайте об этом так: каждый раз, когда вы видите имена методов, подобные этому:

 click_blue_button
click_red_button
click_green_button
  

это то же самое, что делать:

 add_one_to(1)   // instead of 1   1, i.e. 1. (1)
add_two_to(1)   // instead of 1   2, i.e. 1. (2)
add_three_to(1) // instead of 1   3, i.e. i. (3)
  

Вместо того, чтобы передавать значимый аргумент в метод, вы в конечном итоге жестко кодируете значения в имени метода! Продолжайте в том же духе, и в конечном итоге всей вашей кодовой базе придется иметь дело со «значениями», которые были жестко запрограммированы в именах методов.

Лучший способ

Вот что вы должны сделать вместо этого:

 class HomePage < SitePrism::Page
  set_url '/index.html'

  elements :color_cells, "div[id^='colour-cell-']"

  def click_cell(color)
    cell = color_cells.find_by(id: "colour-cell-#{color}") # just an example, I don't know how to do element queries in site-prism
    cell.click
  end
end
  

Или, если вы должны иметь их как отдельные элементы:

 class HomePage < SitePrism::Page
  set_url '/index.html'

  COLORS = %i[red green blue]

  COLORS.each do |color|
    element :"#{color}_colour_cell", "div[id='colour-cell-#{color}']"
  end

  def cell(color:)                 # every other usage should call this method instead
    @cells ||= COLORS.index_with do |color|
      send("#{color}_colour_cell") # do the dynamic `send` in only ONE place
    end
    @cells.fetch(color)
  end
end

home_page.cell(color: :red).click
  

Комментарии:

1. Я думаю, что это может быть более подходящим подходом в ситуации, когда у меня есть несколько элементов, которые следуют шаблону, или один элемент, который может получать динамически именованный CSS-селектор в качестве своего идентификатора. У меня уже была такая ситуация. Я согласен, что в целях удобства сопровождения это может быть более эффективным (и элегантным) в будущем. Я попробую это и сообщу о своих результатах.

2. Единственная проблема с этим заключается в том, что это уменьшает значение того, для чего предназначен SitePrism. Хотя это может помочь в обслуживании методов, становится все труднее поддерживать элементы, если в шаблон вводятся новые или изменяются существующие.

3. Как текущий сопровождающий siteprism, я бы сказал, что это разумный путь. Это не идеально, и это не тот способ, которым я бы точно это кодировал. Но это хорошее начало. Если вы обнаружите, что у вас есть 5/10/15 цветов, и вы хотите сгенерировать для них большое количество элементов, я бы сохранил их независимо от кода (например, в файле yml), а затем извлек их (вместо использования константы). Таким образом, вы сохраняете бит, который менее релевантен коду (сколько у вас цветов), вдали от бита, который релевантен коду (элементы и помощники для каждого из них).