page element definition for cloned rows - page-object-gem

I am using the page-object-gem and trying find the best way to define my page elements when a set of text_field have an infinite number of occurrences.
The HTML on page load is similar to the following:
<div><input id="dx_1_code" value=""/> <input id="dx_1_dos" onblur="clone($(this),false)" value=""/></div>
If the user tabs out of the last input then a new row is cloned with id values that increment with HTML like follows:
<div><input id="dx_2_code" value=""/> <input id="dx_2_dos" onblur="clone($(this),false)" value=""/></div>
<div><input id="dx_3_code" value=""/> <input id="dx_3_dos" onblur="clone($(this),false)" value=""/></div>
My first try was to define my class as follows:
class SamplePage
include PageObject
include DataMagic
text_field(:dx_1, :id => "dx_1_code")
text_field(:dx_2, :id => "dx_2_code")
text_field(:dos_1, :id => "dx_1_dos")
text_field(:dos_2, :id => "dx_2_dos")
end
However I quickly ended up with a lot of redundant entries.
Is there a better way to handle an unknown number or entries like this in terms of element setups and use of the populate_page_with method?

The elements are indexed, which makes them a good candidate for the indexed properties feature. The indexed_property lets you define locators where a number is substituted in when accessing the element. The page object would look like:
class MyPage
include PageObject
indexed_property(:dx, [
[:text_field, :code, {id: 'dx_%s_code'}],
[:text_field, :dos, {id: 'dx_%s_dos'}],
])
end
The first two rows would then be inputted using:
page = MyPage.new(browser)
page.dx[1].code = 'a'
page.dx[1].dos = 'b'
page.dx[2].code = 'c'
page.dx[2].dos = 'd'
Unfortunately there is no built-in way for the populate_page_with method to work with indexed properties. As with anything, you could hack in something. The populate_page_with method looks for an "element" method as well as a setter method. By adding your own to the page object, the method could be used.
class MyPage
include PageObject
indexed_property(:dx, [
[:text_field, :code, {id: 'dx_%s_code'}],
[:text_field, :dos, {id: 'dx_%s_dos'}],
])
# Method for inputting the various dx code/dos values based on a Hash
def dx=(values)
values.each_pair do |index, fields|
fields.each_pair do |field, value|
dx[index].send("#{field}=", value)
end
end
end
# This is so that populate_page_with can check that the element is enabled/visible
def dx_element
dx[1].code_element
end
end
This would give you the ability to use populate_page_with by sending a Hash where the keys are the index and the values are the fields/values for that index. The same inputting of the page that we did before can now be written as:
page = MyPage.new(browser)
page.populate_page_with(dx: {
1 => {code: 'a', dos: 'b'},
2 => {code: 'c', dos: 'd'}
})

Related

How to update by passing array values from HTML to Rails controller

I am trying to update a record from an html.haml form.
HTML.HAML:
%input{:name => "post[my_card_ids][]", :type => "hidden",value: "#{post["my_card_ids"]}",id: "my_card_ids"}
When I inspect the HTML page, the values are exactly like they should be.
HTML inspection:
<input id="my_card_ids" name="post[my_card_ids][]" type="hidden" value="[5d1b83a6616b6523a9020000, 5d1b9893616b653abd0b0000]">
But when I receive the same params in the controller, they become like this:
Rails Console:
<ActionController::Parameters {"my_card_ids"=>["[\"5d1b83a6616b6523a9020000\", \"5d1b9893616b653abd0b0000\"]"]
The record is being saved with all the extra paranthesis and quotation marks, because it is being passed as a string I'm guessing.
How do I pass the array cleanly, as shown in the HTML inspection and update the record?
In order to create an array in the parameters you want to create a hidden input for each value in the array:
- post["my_card_ids"].each do |card_id|
%input{ name: "post[my_card_ids][]", type: "hidden", value: card_id, id: "my_card_ids_#{card_id}" }
This works since Rack merges parameters with names ending with [] into an array. Its also how the built in FormOptionsHelper works.
You put array as string to hidden field.
Try:
%input{:name => "post[my_card_ids]", :type => "hidden",value: "#{post["my_card_ids"].join(','}",id: "my_card_ids"}
And in the controller
params[:my_card_ids].split(',')

How to add the variables of array to database table

I have a form. Posting array variables from this form in the same name. (Sorry for my bad English.)
Here is the example:
<input type="text" name="name[]" class="form-control">
It is coming like ["a", "b", "c"]
I need save this variables to mysql from array. Not with array. Just one by one.
Create from my post controller
array_length = params[:type].size
for i in 0..array_lenth
type = params[:type][i]
name = params[:name][i]
title = params[:title][i]
value = params[:value][i]
#And save code.
end
I know i can save with some normal sql query but i dont know should i use or how i can use params require permit . something like
def form_params
params.require(:form).permit(:id, :type, :name, :value, :title, :post_id)
end
If i use this, how can i determine the necessary variables. Please help me and sorry for my English :)
Note: I have new post form and i have to add some variables to another table in post form (it is those arrays)
You have to specify that the param is an actual array, something like:
def form_params
params.require(:form).permit(:id, :type, :value, :title, :post_id, name: [:name_of_attribute, :another_name_for_attribute])
end
And then for your form:
<div class="field">
<%= form.text_field "name[name_for_attribute]" %>
</div>
Where the name_for_attribute is the name of the attribute you want it to be, so on the controller you will read the params like:
"form" => {"name"=>{"name_for_attribute"=>"The entered value"}
You can change the name_for_attribute for whatever you want and if for some reason someone tries to add another weird thing into the array by inspecting the HTML, you will filter all the attributes you want anyway.
Hope this helps! Let me know how it goes

How to pass a value to the index during runtime for the indexed_property?

I have multiple sets of select_list and check boxes with the same properties. While I can differentiate them using index => 0, index => 1 and so on, is there a way to achieve this using an indexed_property?
When I tried:
indexed_property(:my_index_prop,
[[:select_list, :my_select, {:name => 'my_select',:index => '%d'}],
[:checkbox, :my_check, {:name => 'my_check',:index => '%d'}]]
)
It resulted in the error 'expected fixnum got string "0"'.
If indexed_property cannot be used, is there a way to pass the index during runtime and identify an element? For example:
for cnt in 0 .. 6
# identify element using index
end
The problem is that the Page-Object is sending the :index value as a String when Watir-WebDriver requires the :index value to be a number.
Solution 1 - Use Element Collections
Given that you are only using the indexed property to specify the :index locator, you could define element collections instead.
Assuming that your page is something like:
<html>
<body>
<select name="my_select" id="0"></select>
<input name="my_check" id="a" type="checkbox">
<select name="my_select" id="1"></select>
<input name="my_check" id="b" type="checkbox">
<select name="my_select" id="2"></select>
<input name="my_check" id="c" type="checkbox">
</body>
</html>
You could define the page object as:
class MyPage
include PageObject
select_lists(:my_index_prop_select_list, :name => 'my_select')
checkboxes(:my_index_prop_checkbox, :name => 'my_check')
end
The two element collections are arrays, meaning you can specify the index by using the [] method:
page = MyPage.new(browser)
p page.my_index_prop_select_list_elements[1].attribute('id')
#=> "1"
p page.my_index_prop_checkbox_elements[1].attribute('id')
#=> "b"
The problem with this solution is that you do not get the benefit of the different accessor methods.
Solution 2 - Use XPath
Another option would be to use the :xpath locator with the indexed_property:
class MyPage
include PageObject
indexed_property(:my_index_prop, [
[:select_list, :my_select, {:xpath => '//select[#name="my_select"][%s + 1]'}],
[:checkbox, :my_check, {:xpath => '//input[#type="checkbox"][#name="my_check"][%s + 1]'}]
])
end
The benefit of this is that you get the usual accessor methods created:
page = MyPage.new(browser)
p page.my_index_prop[1].my_select_element.attribute('id')
#=> "1"
p page.my_index_prop[1].my_check_element.attribute('id')
#=> "b"
page.my_index_prop[1].check_my_check
p page.my_index_prop[1].my_check_checked?
#=> true
The disadvantage here is that you have to write XPaths, which is not a big deal when it is simple.
Solution 3 - Modify Page-Object
Lastly, you could modify the Page-Object gem to turn the :index into a number before passing it to Watir. This can be done by adding the monkey-patch:
class PageObject::IndexedProperties::RowOfElements
def initialize (browser, index, identifier_list)
initialize_browser(browser)
identifier_list.each do |identifier|
type = identifier[0]
name = identifier[1]
how_and_what = identifier[2].clone # Cannot modify the original...
how_and_what.each do |key, value|
if key == :index
how_and_what[key] = (value % index).to_i
else
how_and_what[key] = value % index
end
end
self.class.send type, name, how_and_what unless self.class.instance_methods.include? name
end
end
end
This would allow the page object to be defined as expected:
class MyPage
include PageObject
indexed_property(:my_index_prop, [
[:select_list, :my_select, {:name => 'my_select', :index => '%d'}],
[:checkbox, :my_check, {:name => 'my_check', :index => '%d'}]
])
end
Which is used the same as the prior solution:
page = MyPage.new(browser)
p page.my_index_prop[1].my_select_element.attribute('id')
#=> "1"
p page.my_index_prop[1].my_check_element.attribute('id')
#=> "b"
page.my_index_prop[1].check_my_check
p page.my_index_prop[1].my_check_checked?
#=> true
The disadvantage of course is that you have to patch the Page-Object gem. Though you could request the change be made to the project.

How to Fix Rails Code when Moving it From Controller to Helpers?

On each text_area on my site, I have a select_box for selecting the language. It often uses certain default languages, but sometimes checks for custom options. I initially had the array setup in the controller:
#language_array = [ ["english", 1], ["french", 2], ["spanish", 3] ]
#language_array = get_custom_array if custom_language?
And it would then be accessed by the view:
<%= select_tag(:language, options_for_select(language_array, default_language) )%>
This worked fine, and the text_area would display the language and pass on the number. However, I would now like to add multiple text_areas to each page, so I moved the code into a helper, and access the helper from every text_area in the view:
language_array = get_language_array(thing.id)
<%= select_tag(:language, options_for_select(language_array, default_language) )%>
However, now the text_area comes out messed up, and displays the array instead of just the language:
["english", 1]
How can I fix it (without changing the currently stored arrays)? I also want variables from the helper to be available to javascript on the page. Is there a better way to deal with multiple "things" than to move everything from the controller to the helpers?
You could have them inside the model as a hash like this
user.rb
def User
def self.language
{
'English' => '1',
'French' => '2',
....
}
end
end
and in the controller obtain the hash into a variable and it will be accessible in both the html.erb and the js.erb
users_controller.rb
#language = User.language
Then while building the form you can do it as
<%= talent_field.select(:language, #language, {}, {:class => 'language-select'} %>

What's the right way to define an anchor tag in rails?

It's obvious from the documentation (and google) how to generate a link with a segment e.g. podcast/5#comments. You just pass a value for :anchor to link_to.
My concern is about the much simpler task of generating the <a name="comments">Comments</a> tag i.e. the destination of the first link.
I've tried the following, and although they seemed to work, the markup was not what I expected:
link_to "Comments", :name => "comments"
link_to "Comments", :anchor => "comments"
I think I'm missing something obvious. Thanks.
You are getting confused by Ruby's syntactic sugar (which Rails uses profusely). Let me explain this briefly before answering your question.
When a ruby function takes a single parameter that is a hash:
def foo(options)
#options is a hash with parameters inside
end
You can 'forget' to put the parenthesis/brackets, and call it like this:
foo :param => value, :param2 => value
Ruby will fill out the blanks and understand that what you are trying to accomplish is this:
foo({:param => value, :param2 => value})
Now, to your question: link_to takes two optional hashes - one is called options and the other html_options. You can imagine it defined like this (this is an approximation, it is much more complex)
def link_to(name, options, html_options)
...
end
Now, if you invoke it this way:
link_to 'Comments', :name => 'Comments'
Ruby will get a little confused. It will try to "fill out the blanks" for you, but incorrectly:
link_to('Comments', {:name => 'Comments'}, {}) # incorrect
It will think that name => 'Comments' part belongs to options, not to html_options!
You have to help ruby by filling up the blanks yourself. Put all the parenthesis in place and it will behave as expected:
link_to('Comments', {}, {:name => 'Comments'}) # correct
You can actually remove the last set of brackets if you want:
link_to("Comments", {}, :name => "comments") # also correct
In order to use html_options, you must leave the first set of brackets, though. For example, you will need to do this for a link with confirmation message and name:
link_to("Comments", {:confirm => 'Sure?'}, :name => "comments")
Other rails helpers have a similar construction (i.e. form_for, collection_select) so you should learn this technique. In doubt, just add all the parenthesis.
If you want to go through rails, I suggest content_tag (docs).
Example:
content_tag(:a, 'Comments', :name => 'comments')
<%= link_to('new button', action: 'login' , class: "text-center") %>
created an anchor tag for login.html i.g
new button
and for
new button
use
<%= link_to('new button', controller: 'admin',
action: 'login' , class: "text-center") %>