page_record 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.coveralls.yml +1 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/.yardopts +2 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +22 -0
- data/README.md +210 -0
- data/Rakefile +1 -0
- data/lib/page_record/attribute_accessors.rb +60 -0
- data/lib/page_record/class_actions.rb +55 -0
- data/lib/page_record/class_methods.rb +214 -0
- data/lib/page_record/cucumber.rb +5 -0
- data/lib/page_record/errors.rb +88 -0
- data/lib/page_record/finders.rb +135 -0
- data/lib/page_record/instance_actions.rb +52 -0
- data/lib/page_record/page_record.rb +29 -0
- data/lib/page_record/rspec.rb +7 -0
- data/lib/page_record/version.rb +3 -0
- data/lib/page_record.rb +9 -0
- data/page_record.gemspec +29 -0
- data/spec/page_record_spec.rb +434 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/shared_contexts.rb +37 -0
- data/spec/support/shared_examples.rb +71 -0
- data/spec/support/team.rb +6 -0
- data/spec/support/test_app.rb +24 -0
- data/spec/support/views/page-one-record-in-a-form.erb +4 -0
- data/spec/support/views/page-one-record.erb +3 -0
- data/spec/support/views/page-with-duplicate-records.erb +8 -0
- data/spec/support/views/page-with-single-table-with-3-records.erb +17 -0
- data/spec/support/views/page-with-two-tables-with-3-records.erb +38 -0
- data/spec/support/views/page-without-records.erb +2 -0
- metadata +190 -0
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
page_record (0.0.1)
|
5
|
+
activesupport
|
6
|
+
capybara (~> 2.1.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (3.2.13)
|
12
|
+
i18n (= 0.6.1)
|
13
|
+
multi_json (~> 1.0)
|
14
|
+
capybara (2.1.0)
|
15
|
+
mime-types (>= 1.16)
|
16
|
+
nokogiri (>= 1.3.3)
|
17
|
+
rack (>= 1.0.0)
|
18
|
+
rack-test (>= 0.5.4)
|
19
|
+
xpath (~> 2.0)
|
20
|
+
colorize (0.5.8)
|
21
|
+
coveralls (0.6.7)
|
22
|
+
colorize
|
23
|
+
multi_json (~> 1.3)
|
24
|
+
rest-client
|
25
|
+
simplecov (>= 0.7)
|
26
|
+
thor
|
27
|
+
diff-lcs (1.2.4)
|
28
|
+
i18n (0.6.1)
|
29
|
+
mime-types (1.23)
|
30
|
+
mini_portile (0.5.0)
|
31
|
+
multi_json (1.7.7)
|
32
|
+
nokogiri (1.6.0)
|
33
|
+
mini_portile (~> 0.5.0)
|
34
|
+
rack (1.5.2)
|
35
|
+
rack-protection (1.5.0)
|
36
|
+
rack
|
37
|
+
rack-test (0.6.2)
|
38
|
+
rack (>= 1.0)
|
39
|
+
rake (10.0.4)
|
40
|
+
rest-client (1.6.7)
|
41
|
+
mime-types (>= 1.16)
|
42
|
+
rspec (2.13.0)
|
43
|
+
rspec-core (~> 2.13.0)
|
44
|
+
rspec-expectations (~> 2.13.0)
|
45
|
+
rspec-mocks (~> 2.13.0)
|
46
|
+
rspec-core (2.13.1)
|
47
|
+
rspec-expectations (2.13.0)
|
48
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
49
|
+
rspec-mocks (2.13.1)
|
50
|
+
simplecov (0.7.1)
|
51
|
+
multi_json (~> 1.0)
|
52
|
+
simplecov-html (~> 0.7.1)
|
53
|
+
simplecov-html (0.7.1)
|
54
|
+
sinatra (1.3.6)
|
55
|
+
rack (~> 1.4)
|
56
|
+
rack-protection (~> 1.3)
|
57
|
+
tilt (~> 1.3, >= 1.3.3)
|
58
|
+
thor (0.18.1)
|
59
|
+
tilt (1.4.1)
|
60
|
+
xpath (2.0.0)
|
61
|
+
nokogiri (~> 1.3)
|
62
|
+
|
63
|
+
PLATFORMS
|
64
|
+
ruby
|
65
|
+
|
66
|
+
DEPENDENCIES
|
67
|
+
bundler (~> 1.3)
|
68
|
+
coveralls
|
69
|
+
page_record!
|
70
|
+
rake
|
71
|
+
rspec
|
72
|
+
sinatra
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Bert Hajee
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
[![Code Climate](https://codeclimate.com/github/appdrones/page_record.png)](https://codeclimate.com/github/appdrones/page_record) [![Build Status](https://travis-ci.org/appdrones/page_record.png)](https://travis-ci.org/appdrones/page_record) [![Dependency Status](https://gemnasium.com/appdrones/page_record.png)](https://gemnasium.com/appdrones/page_record) [![Coverage Status](https://coveralls.io/repos/appdrones/page_record/badge.png)](https://coveralls.io/r/appdrones/page_record)
|
2
|
+
|
3
|
+
# PageRecord
|
4
|
+
|
5
|
+
You've probably been there. You're building your killer Web Application. You, being a responsible developer, your code is thoroughly tested. You are using the likes of cucumber and Rspec to do so. Besides your unit-tests, you also have integration tests doing a full stack test. You are using Capybara to read, parse and test the rendered pages. You use all kinds of selectors to select different parts of the page. Slowly but surely your test code becomes less and less readable.
|
6
|
+
|
7
|
+
## PageRecord can help.
|
8
|
+
There are a lot of ways you can do this. PageRecord is one of these ways. PageRecord is an ActiveRecord like abstraction for information on the HTML page. You can use `TeamRecord.find(1)` and `TeamRecord.find_by_name('Barcelona')` like functions to find a record on an HTML page. When you've found the record you need, you can use easy accessors to access the attributes. `TeamRecord.find(1).name` returns the name of the team.
|
9
|
+
|
10
|
+
TODO explain how it helps in testing
|
11
|
+
|
12
|
+
##Documentation
|
13
|
+
Look at {http://rubydoc.info/github/appdrones/page_record the yard documentation} for details.
|
14
|
+
|
15
|
+
##Markup
|
16
|
+
For {PageRecord} to recognise the data on the page, the page __must__ follow certain `html` formatting guidelines.
|
17
|
+
|
18
|
+
__Every__ record on the page __must__ have a `data-type-id='x'` attribute.
|
19
|
+
|
20
|
+
```html
|
21
|
+
<tag data-type-id='x'>
|
22
|
+
...
|
23
|
+
<tag>
|
24
|
+
```
|
25
|
+
Where `1` is the `id` of the record and `type` is the type of record. See {PageRecord::PageRecord.type} for infrmation regarding the `type`.
|
26
|
+
|
27
|
+
__Every__ attribute of a record, __must__ have a `data-attribute-for='name'` attribute. This tag __must__ be contained inside a tag contaning the `data-type-id='x'`
|
28
|
+
|
29
|
+
```html
|
30
|
+
<tag data-team-id='1'>
|
31
|
+
<tag data-attribute-for='attr'>Ajax</tag>
|
32
|
+
...
|
33
|
+
<tag>
|
34
|
+
```
|
35
|
+
|
36
|
+
Where `attr` is the name of the attribute.
|
37
|
+
|
38
|
+
##attributes
|
39
|
+
|
40
|
+
###reading the attributes
|
41
|
+
With {PageRecord}, you can easily access the attributes of a record on the page. After you've got a valid instance of {PageRecord::PageRecord}, you can access all attributes in a normal ruby-like way.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
champion = TeamPage.find(1)
|
45
|
+
expect(champion.name).to eq('Ajax')
|
46
|
+
```
|
47
|
+
###setting the attributes
|
48
|
+
Not only can you access the attributes for reading, you can also set the attributes. This only works if the attribute is modifiable, like in an `INPUT` or a `TEXTFIELD` tag.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
champion = TeamPage.find(1)
|
52
|
+
champion.name = 'Ajax'
|
53
|
+
```
|
54
|
+
|
55
|
+
###specifying the attributes
|
56
|
+
When you define you page class as a subclass of {PageRecord::PageRecord}, it automagicaly looks for a host class. To get the name of your host class, PageRecord removes the `Page` part of you class name and tries to use that class as a host class.
|
57
|
+
|
58
|
+
Example:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class TeamPage < PageRecord::PageRecord
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
will result in a host class of `Team`
|
66
|
+
|
67
|
+
####what is the host class
|
68
|
+
The host class is a class who's attributes are mirrored by PageRecord. At this point in time PageRecord only has support for `ActiveRecord` host classes. To be more specific: To be used as a host class, the class must support the method `attribute_names` to return an {::Array} of {::String} of {::Symbol}.
|
69
|
+
|
70
|
+
Example:
|
71
|
+
|
72
|
+
Given the following host class and PageRecord class
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
class Team < ActiveRecord::Base
|
76
|
+
# The database defines the following records
|
77
|
+
# name, position, points
|
78
|
+
#
|
79
|
+
end
|
80
|
+
|
81
|
+
class TeamPage < PageRecord::PageRecord
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
Then a record from the TeamPage responds to the attributes: `name`, `position`, `points`
|
86
|
+
|
87
|
+
##actions
|
88
|
+
PageRecord support record actions and page actions.
|
89
|
+
|
90
|
+
###record actions
|
91
|
+
A record action is an action that is found within a `data-type-id=` tag. To define an action, you can use the `data-action-for='create'` tag.
|
92
|
+
|
93
|
+
An example:
|
94
|
+
|
95
|
+
```html
|
96
|
+
<form data-team-id='1'>
|
97
|
+
Name: <input data-attribute-for='name'>
|
98
|
+
Position: <input data-attribute-for='position'>
|
99
|
+
<button data-action-for='save'>Create</button>
|
100
|
+
</form>
|
101
|
+
```
|
102
|
+
|
103
|
+
with this markup on your page, you can use the following ruby code:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
team = TeamPage.find(1)
|
107
|
+
team.name = 'Ajax'
|
108
|
+
team.position = 1
|
109
|
+
team.save
|
110
|
+
```
|
111
|
+
|
112
|
+
When you call an action method, PageRecord automagicaly clicks the specified HTML element.
|
113
|
+
|
114
|
+
Sometimes you don't want to click the element. PageRecord supports this by calling the action routine with a `?`. This returns the {http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Result Capybara Result}. You can do with this element whatever you can do with this element what you want.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
TeamPage.save?
|
118
|
+
```
|
119
|
+
|
120
|
+
###page actions
|
121
|
+
Sometimes you need to have an action that is not related to a record. An example is the creation of a new record. But in fact, any `data-action-for` tag, outside of a record, can be called as a page action.
|
122
|
+
|
123
|
+
An example:
|
124
|
+
|
125
|
+
```html
|
126
|
+
<form data-team-id='1'>
|
127
|
+
Name: <input data-attribute-for='name'>
|
128
|
+
Position: <input data-attribute-for='position'>
|
129
|
+
<button data-action-for='save'>
|
130
|
+
</form>
|
131
|
+
<button data-action-for='create'>New Team</button>
|
132
|
+
```
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
TeamPage.create
|
136
|
+
```
|
137
|
+
|
138
|
+
When you call an action method, PageRecord automagicaly clicks the specfied HTML element.
|
139
|
+
|
140
|
+
Sometimes you don't want to click the element. PageRecord supports this by calling the action routine with a `?`. This returns the Capybara Element. You can do with this element whatever you can do with this element what you want.
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
TeamPage.save?
|
144
|
+
```
|
145
|
+
|
146
|
+
##Using PageRecord together with Rspec
|
147
|
+
To use {PageRecord} with Rspec, you need to require it in your `spec_helper.rb` file. To make it work, it needs to be required __after__ capybara.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
require 'capybara'
|
151
|
+
require 'page_record/rspec'
|
152
|
+
```
|
153
|
+
|
154
|
+
Also, you need te make sure your page definitions are included. A good place to store them would be in your support directory.
|
155
|
+
|
156
|
+
##Using PageRecord together with Cucumber
|
157
|
+
To use {PageRecord} with Cucumber, you need to require it in your `env.rb` file. To make it work, it needs to be required __after__ capybara.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
require 'capybara'
|
161
|
+
require 'page_record/cucumber'
|
162
|
+
```
|
163
|
+
Also, you need te make sure your page definitions are included. A good place to store them would be in your support directory.
|
164
|
+
|
165
|
+
##Using PageRecord standalone
|
166
|
+
If you are using {PageRecord} outside of Rspec or Cucumber, you also need to require the right files.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
require 'capybara'
|
170
|
+
require 'page_record/rspec'
|
171
|
+
```
|
172
|
+
|
173
|
+
You also need te make sure, you set the page variable before you start.
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
PageRecord::PageRecord.page = session
|
177
|
+
```
|
178
|
+
|
179
|
+
Also, you need te make sure your page definitions are included.
|
180
|
+
|
181
|
+
|
182
|
+
## Installation
|
183
|
+
|
184
|
+
Add this line to your application's Gemfile:
|
185
|
+
|
186
|
+
gem 'page_record', :require => false
|
187
|
+
|
188
|
+
And then execute:
|
189
|
+
|
190
|
+
$ bundle
|
191
|
+
|
192
|
+
Or install it yourself as:
|
193
|
+
|
194
|
+
$ gem install page_record
|
195
|
+
|
196
|
+
## Usage
|
197
|
+
|
198
|
+
TODO explain how to use it
|
199
|
+
|
200
|
+
## Example
|
201
|
+
|
202
|
+
TODO show an example
|
203
|
+
|
204
|
+
## Contributing
|
205
|
+
|
206
|
+
1. Fork it
|
207
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
208
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
209
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
210
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module PageRecord
|
2
|
+
class PageRecord
|
3
|
+
##
|
4
|
+
# Searches the record for the specified attribute and returns the {http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Result Capybara Result}.
|
5
|
+
# This method is called when you access an attribute with a `?` of a record
|
6
|
+
#
|
7
|
+
# @return [Capybara::Result] the text content of the specified attribute
|
8
|
+
#
|
9
|
+
# @raise [AttributeNotFound] when the attribute is not found in the record
|
10
|
+
#
|
11
|
+
def read_attribute?(attribute)
|
12
|
+
begin
|
13
|
+
@record.find("[data-attribute-for='#{attribute}']")
|
14
|
+
rescue Capybara::ElementNotFound
|
15
|
+
raise AttributeNotFound, "#{@type} record with id #{@id} doesn't contain attribute #{attribute}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Searches the record for the specified attribute and returns the text content.
|
21
|
+
# This method is called when you access an attribute of a record
|
22
|
+
#
|
23
|
+
# @return [String] the text content of the specified attribute
|
24
|
+
#
|
25
|
+
# @raise [AttributeNotFound] when the attribute is not found in the record
|
26
|
+
#
|
27
|
+
def read_attribute( attribute)
|
28
|
+
element = self.send("#{attribute}?")
|
29
|
+
tag = element.tag_name
|
30
|
+
textelement?(tag) ? element.value : element.text
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Searches the record for the specified attribute and sets the value of the attribute
|
35
|
+
# This method is called when you set an attribute of a record
|
36
|
+
#
|
37
|
+
# @return [Capybara::Result] the text content of the specified attribute
|
38
|
+
#
|
39
|
+
# @raise [AttributeNotFound] when the attribute is not found in the record
|
40
|
+
# @raise [NotInputField] when the attribute is not a `TEXTAREA` or `INPUT` tag
|
41
|
+
#
|
42
|
+
def write_attribute( attribute, value)
|
43
|
+
element = self.send("#{attribute}?")
|
44
|
+
tag = element.tag_name
|
45
|
+
raise NotInputField unless textelement?(tag)
|
46
|
+
element.set(value)
|
47
|
+
element
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
private
|
52
|
+
# @private
|
53
|
+
def textelement?(tag)
|
54
|
+
case tag
|
55
|
+
when 'textarea', 'input' then true
|
56
|
+
else false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module PageRecord
|
2
|
+
class PageRecord
|
3
|
+
##
|
4
|
+
#
|
5
|
+
# This is the implementation of the page action routine. It has two variants.
|
6
|
+
# it has a `?` variant and a `normal` variant.
|
7
|
+
#
|
8
|
+
# normal variant:
|
9
|
+
# It checks the page for a data-action-for='action' tag somewhere on the page.
|
10
|
+
# If it finds it, it clicks it.
|
11
|
+
#
|
12
|
+
# `?` variant:
|
13
|
+
# It checks the page for a data-action-for='action' tag somewhere on the page.
|
14
|
+
# If it finds it, returns the Capybara result.
|
15
|
+
#
|
16
|
+
# @param action [Symbol] this is the name of the action
|
17
|
+
#
|
18
|
+
# @return [Capybara::Element]
|
19
|
+
#
|
20
|
+
# @raise [PageRecord::MultipleRecords] when there are more actions with
|
21
|
+
# this name on the page
|
22
|
+
#
|
23
|
+
def self.method_missing(action)
|
24
|
+
raw_action = /(.*)\?/.match(action)
|
25
|
+
begin
|
26
|
+
if raw_action
|
27
|
+
action_for?(raw_action[1])
|
28
|
+
else
|
29
|
+
action_for(action)
|
30
|
+
end
|
31
|
+
rescue Capybara::Ambiguous
|
32
|
+
raise MultipleRecords, "Found multiple #{action} tags for #{@type} on page"
|
33
|
+
rescue Capybara::ElementNotFound
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# @private
|
41
|
+
def self.action_for(action)
|
42
|
+
element = action_for?(action)
|
43
|
+
element.click
|
44
|
+
element
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# @private
|
49
|
+
def self.action_for?(action)
|
50
|
+
page.find("[data-action-for='#{action}']")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module PageRecord
|
2
|
+
class PageRecord
|
3
|
+
|
4
|
+
# @private
|
5
|
+
def self.inherited(base)
|
6
|
+
base.class_eval do
|
7
|
+
set_type_name(base)
|
8
|
+
get_attribute_names
|
9
|
+
end
|
10
|
+
define_class_methods(base)
|
11
|
+
define_instance_methods(base)
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
##
|
16
|
+
# Set's the page {PageRecord::PageRecord} uses for all page operations
|
17
|
+
#
|
18
|
+
# @param new_page [Cabybara::Session] The Capybara page
|
19
|
+
#
|
20
|
+
# @return [Capybara::Session]
|
21
|
+
#
|
22
|
+
def self.page=(new_page)
|
23
|
+
@@page = new_page
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Returns the page page {PageRecord::PageRecord} uses for all page operations
|
28
|
+
#
|
29
|
+
# @return [Capybara::Session]
|
30
|
+
#
|
31
|
+
def self.page
|
32
|
+
@@page
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Set's the default selector for this class
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
#
|
40
|
+
# ```ruby
|
41
|
+
# class TeamPage < PageRecord::PageRecord
|
42
|
+
# selector "#first-table"
|
43
|
+
# end
|
44
|
+
#```
|
45
|
+
# @param new_selector [String] The default selector to be used for all finders
|
46
|
+
#
|
47
|
+
def self.selector( new_selector)
|
48
|
+
@selector = new_selector
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Set's the default filter for this class
|
53
|
+
#
|
54
|
+
#
|
55
|
+
# Example:
|
56
|
+
#
|
57
|
+
# ```ruby
|
58
|
+
# class TeamPage < PageRecord::PageRecord
|
59
|
+
# filter ".champions-league"
|
60
|
+
# end
|
61
|
+
#```
|
62
|
+
#
|
63
|
+
# @param new_filter [String] The default filter to be used for all finders
|
64
|
+
#
|
65
|
+
def self.filter( new_filter)
|
66
|
+
@filter = new_filter
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Set's the default type for this class
|
71
|
+
#
|
72
|
+
# @param new_type [Symbol] The default type to be used for all finders
|
73
|
+
#
|
74
|
+
# Example:
|
75
|
+
#
|
76
|
+
# ```ruby
|
77
|
+
# class TopDivisonPage < PageRecord::PageRecord
|
78
|
+
# type :team
|
79
|
+
# end
|
80
|
+
#```
|
81
|
+
#
|
82
|
+
#
|
83
|
+
def self.type( new_type)
|
84
|
+
@type = new_type
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
##
|
89
|
+
# Set's the attributes this page recognises. This will override any types
|
90
|
+
# inherited from the host class
|
91
|
+
#
|
92
|
+
# @param new_attributes [Array] The attributes the page regognises
|
93
|
+
#
|
94
|
+
# Example:
|
95
|
+
#
|
96
|
+
# ```ruby
|
97
|
+
# class TopDivisonPage < PageRecord::PageRecord
|
98
|
+
# attributes [:name, :position, :ranking]
|
99
|
+
# end
|
100
|
+
#```
|
101
|
+
#
|
102
|
+
#
|
103
|
+
def self.attributes(new_attributes)
|
104
|
+
undefine_class_methods(self)
|
105
|
+
undefine_instance_methods(self)
|
106
|
+
@attributes = new_attributes
|
107
|
+
define_class_methods(self)
|
108
|
+
define_instance_methods(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
# @private
|
115
|
+
def self.set_type_name(base)
|
116
|
+
@base_name = base.to_s.gsub('Page', '')
|
117
|
+
@type = @base_name.underscore
|
118
|
+
end
|
119
|
+
|
120
|
+
# @private
|
121
|
+
def self.get_attribute_names
|
122
|
+
begin
|
123
|
+
@host_class = @base_name.constantize
|
124
|
+
@attributes = @host_class.attribute_names.clone
|
125
|
+
@attributes.delete('id') # id is a special case attribute
|
126
|
+
rescue NameError
|
127
|
+
@attributes = []
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
# @private
|
133
|
+
def self.define_accessor_methods(base)
|
134
|
+
base.instance_eval do
|
135
|
+
@attributes.each do | attribute |
|
136
|
+
define_method("#{attribute}?") do
|
137
|
+
read_attribute?(attribute)
|
138
|
+
end
|
139
|
+
define_method(attribute) do
|
140
|
+
read_attribute(attribute)
|
141
|
+
end
|
142
|
+
define_method("#{attribute}=") do | value|
|
143
|
+
write_attribute(attribute, value)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
# @private
|
151
|
+
def self.undefine_accessor_methods(base)
|
152
|
+
base.instance_eval do
|
153
|
+
@attributes.each do | attribute |
|
154
|
+
remove_method("#{attribute}?")
|
155
|
+
remove_method(attribute)
|
156
|
+
remove_method("#{attribute}=")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
# @private
|
163
|
+
def self.define_instance_methods(base)
|
164
|
+
define_accessor_methods(base)
|
165
|
+
end
|
166
|
+
|
167
|
+
# @private
|
168
|
+
def self.undefine_instance_methods(base)
|
169
|
+
undefine_accessor_methods(base)
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
# @private
|
174
|
+
def self.define_class_methods(base)
|
175
|
+
eigenclass = class << base; self; end
|
176
|
+
attributes = base.instance_variable_get('@attributes')
|
177
|
+
eigenclass.instance_eval do
|
178
|
+
attributes.each do | attribute|
|
179
|
+
define_method "find_by_#{attribute}" do | value, selector = "", filter = ""|
|
180
|
+
find_by_attribute( attribute, value, selector, filter)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# @private
|
187
|
+
def self.undefine_class_methods(base)
|
188
|
+
eigenclass = class << base; self; end
|
189
|
+
attributes = base.instance_variable_get('@attributes')
|
190
|
+
eigenclass.instance_eval do
|
191
|
+
attributes.each do | attribute|
|
192
|
+
remove_method "find_by_#{attribute}"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
# @private
|
199
|
+
def self.context_for_selector(selector)
|
200
|
+
if selector.blank?
|
201
|
+
page
|
202
|
+
else
|
203
|
+
begin
|
204
|
+
page.find(selector).find(:xpath, "..")
|
205
|
+
rescue Capybara::Ambiguous
|
206
|
+
raise MultipleRecords, "Found multiple HTML segments with selector #{selector} on page"
|
207
|
+
rescue Capybara::ElementNotFound
|
208
|
+
raise RecordNotFound, "#{selector} not found on page"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|