page_record 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rubocop.yml +13 -0
- data/CHANGES.md +5 -0
- data/Gemfile.lock +46 -14
- data/Guardfile +24 -0
- data/README.md +27 -1
- data/bin/autospec +16 -0
- data/bin/guard +16 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/lib/page_record/attribute_accessors.rb +57 -54
- data/lib/page_record/base.rb +27 -22
- data/lib/page_record/class_actions.rb +47 -50
- data/lib/page_record/class_methods.rb +252 -261
- data/lib/page_record/errors.rb +81 -82
- data/lib/page_record/finders.rb +129 -131
- data/lib/page_record/form_builder.rb +4 -4
- data/lib/page_record/formtastic.rb +20 -9
- data/lib/page_record/helpers.rb +192 -131
- data/lib/page_record/inspector.rb +36 -0
- data/lib/page_record/instance_actions.rb +44 -46
- data/lib/page_record/rspec.rb +1 -1
- data/lib/page_record/validation.rb +46 -0
- data/lib/page_record/version.rb +1 -1
- data/lib/page_record.rb +13 -15
- data/page_record.gemspec +4 -1
- data/spec/.rubocop.yml +4 -0
- data/spec/helpers_spec.rb +109 -100
- data/spec/inspector_spec.rb +70 -0
- data/spec/page_record_spec.rb +357 -388
- data/spec/spec_helper.rb +1 -3
- data/spec/support/shared_contexts.rb +24 -2
- data/spec/support/shared_examples.rb +41 -45
- data/spec/support/team.rb +4 -4
- data/spec/support/test_app.rb +10 -13
- data/spec/support/views/page-with-1-error.erb +5 -0
- data/spec/support/views/page-with-2-errors-on-different-attributes.erb +9 -0
- data/spec/support/views/page-with-2-errors-on-same-attribute.erb +6 -0
- data/spec/support/views/page-without-errors.erb +4 -0
- data/spec/validation_spec.rb +142 -0
- data/tmp/rspec_guard_result +1 -0
- metadata +80 -5
@@ -1,262 +1,253 @@
|
|
1
1
|
module PageRecord
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
private
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
raise MultipleRecords, "Found multiple HTML segments with selector #{selector} on page"
|
255
|
-
rescue Capybara::ElementNotFound
|
256
|
-
raise RecordNotFound, "#{selector} not found on page"
|
257
|
-
end
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
end
|
262
|
-
end
|
2
|
+
class Base
|
3
|
+
|
4
|
+
##
|
5
|
+
# Set's the default selector for this class
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# ```ruby
|
10
|
+
# class TeamPage < PageRecord::Base
|
11
|
+
# selector "#first-table"
|
12
|
+
# end
|
13
|
+
# ```
|
14
|
+
# @param new_selector [String] The default selector to be used for all finders
|
15
|
+
#
|
16
|
+
def self.selector( new_selector = nil)
|
17
|
+
@selector = new_selector if new_selector
|
18
|
+
@selector
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Set's the default filter for this class
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
#
|
27
|
+
# ```ruby
|
28
|
+
# class TeamPage < PageRecord::Base
|
29
|
+
# filter ".champions-league"
|
30
|
+
# end
|
31
|
+
# ```
|
32
|
+
#
|
33
|
+
# @param new_filter [String] The default filter to be used for all finders
|
34
|
+
#
|
35
|
+
def self.filter( new_filter = nil)
|
36
|
+
@filter = new_filter if new_filter
|
37
|
+
@filter
|
38
|
+
end
|
39
|
+
|
40
|
+
# @private
|
41
|
+
def self.inherited(base)
|
42
|
+
base.class_eval do
|
43
|
+
set_type_name(base)
|
44
|
+
get_attribute_names
|
45
|
+
end
|
46
|
+
define_class_methods(base)
|
47
|
+
define_instance_methods(base)
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Set's the page {PageRecord::Base} uses for all page operations.
|
52
|
+
# when no parameter is given or the parameter is nil, just return the current value
|
53
|
+
#
|
54
|
+
# @param new_page [Cabybara::Session] The Capybara page
|
55
|
+
#
|
56
|
+
# @return [Capybara::Session]
|
57
|
+
#
|
58
|
+
# rubocop:disable AvoidClassVars:
|
59
|
+
def self.page (new_page = nil)
|
60
|
+
new_page ? @@page = new_page : @@page
|
61
|
+
end
|
62
|
+
# rubocop:enable AvoidClassVars:
|
63
|
+
|
64
|
+
##
|
65
|
+
# Set's the page host class
|
66
|
+
#
|
67
|
+
# @param new_host_class an ActiveRecord like class
|
68
|
+
#
|
69
|
+
# @return [Class]
|
70
|
+
#
|
71
|
+
def self.host_class (new_host_class = nil)
|
72
|
+
if new_host_class
|
73
|
+
@host_class = new_host_class
|
74
|
+
@host_name = new_host_class.to_s
|
75
|
+
@type = @host_name.underscore
|
76
|
+
get_attribute_names
|
77
|
+
define_class_methods(self)
|
78
|
+
define_instance_methods(self)
|
79
|
+
end
|
80
|
+
@host_class
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Set's the default type for this class
|
85
|
+
#
|
86
|
+
# @param new_type [Symbol] The default type to be used for all finders. If type is nil just return the current type
|
87
|
+
#
|
88
|
+
# @return [Symbol] the type set.
|
89
|
+
#
|
90
|
+
# Example:
|
91
|
+
#
|
92
|
+
# ```ruby
|
93
|
+
# class TopDivisonPage < PageRecord::Base
|
94
|
+
# type :team
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# TopDivisonPage.type # returns :team
|
98
|
+
# ```
|
99
|
+
#
|
100
|
+
#
|
101
|
+
def self.type(new_type = nil)
|
102
|
+
new_type ? @type = new_type : @type
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Set's the attributes this page recognises. This will override any types
|
107
|
+
# inherited from the host class. When you don't specify a parameter, or a nil parameter
|
108
|
+
# .attributes will return the current set of attributes
|
109
|
+
#
|
110
|
+
# @param new_attributes [Array] The attributes the page regognises
|
111
|
+
#
|
112
|
+
# @return [Array] returns the array of attributes the page recognises
|
113
|
+
# Example:
|
114
|
+
#
|
115
|
+
# ```ruby
|
116
|
+
# class TopDivisonPage < PageRecord::Base
|
117
|
+
# attributes [:name, :position, :ranking]
|
118
|
+
# end
|
119
|
+
# ```
|
120
|
+
#
|
121
|
+
#
|
122
|
+
def self.attributes(new_attributes = nil)
|
123
|
+
if new_attributes
|
124
|
+
undefine_class_methods(self)
|
125
|
+
undefine_instance_methods(self)
|
126
|
+
@attributes = new_attributes
|
127
|
+
define_class_methods(self)
|
128
|
+
define_instance_methods(self)
|
129
|
+
end
|
130
|
+
@attributes
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Add some new attributes to the already availabe attributes
|
135
|
+
#
|
136
|
+
# @param extra_attributes [Array] The additional attributes the page regognises
|
137
|
+
#
|
138
|
+
# Example:
|
139
|
+
#
|
140
|
+
# ```ruby
|
141
|
+
# class TopDivisonPage < PageRecord::Base
|
142
|
+
# add_attributes [:full_name, :address_line]
|
143
|
+
# end
|
144
|
+
# ```
|
145
|
+
#
|
146
|
+
#
|
147
|
+
def self.add_attributes(extra_attributes)
|
148
|
+
@attributes.concat(extra_attributes)
|
149
|
+
# TODO: check if we can optimise this to only add the new methods
|
150
|
+
define_class_methods(self)
|
151
|
+
define_instance_methods(self)
|
152
|
+
@attributes
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
# @private
|
158
|
+
def self.set_type_name(base)
|
159
|
+
@host_name = base.to_s.gsub('Page', '')
|
160
|
+
@type = @host_name.underscore
|
161
|
+
@host_class = @host_name.constantize
|
162
|
+
rescue NameError
|
163
|
+
@host_name = ''
|
164
|
+
@host_class = ''
|
165
|
+
end
|
166
|
+
|
167
|
+
# @private
|
168
|
+
def self.get_attribute_names
|
169
|
+
@attributes = @host_class.attribute_names.clone
|
170
|
+
@attributes.delete('id') # id is a special case attribute
|
171
|
+
rescue NameError
|
172
|
+
@attributes = []
|
173
|
+
end
|
174
|
+
|
175
|
+
# @private
|
176
|
+
def self.define_accessor_methods(base)
|
177
|
+
base.instance_eval do
|
178
|
+
@attributes.each do | attribute |
|
179
|
+
define_method("#{attribute}?") do
|
180
|
+
read_attribute?(attribute)
|
181
|
+
end
|
182
|
+
define_method(attribute) do
|
183
|
+
read_attribute(attribute)
|
184
|
+
end
|
185
|
+
define_method("#{attribute}=") do | value|
|
186
|
+
write_attribute(attribute, value)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# @private
|
193
|
+
def self.undefine_accessor_methods(base)
|
194
|
+
base.instance_eval do
|
195
|
+
@attributes.each do | attribute |
|
196
|
+
remove_method("#{attribute}?")
|
197
|
+
remove_method(attribute)
|
198
|
+
remove_method("#{attribute}=")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# @private
|
204
|
+
def self.define_instance_methods(base)
|
205
|
+
define_accessor_methods(base)
|
206
|
+
end
|
207
|
+
|
208
|
+
# @private
|
209
|
+
def self.undefine_instance_methods(base)
|
210
|
+
undefine_accessor_methods(base)
|
211
|
+
end
|
212
|
+
|
213
|
+
# @private
|
214
|
+
def self.define_class_methods(base)
|
215
|
+
eigenclass = class << base; self; end
|
216
|
+
attributes = base.instance_variable_get('@attributes')
|
217
|
+
eigenclass.instance_eval do
|
218
|
+
attributes.each do | attribute|
|
219
|
+
define_method "find_by_#{attribute}" do | value, selector = '', filter = ''|
|
220
|
+
find_by_attribute(attribute, value, selector, filter)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# @private
|
227
|
+
def self.undefine_class_methods(base)
|
228
|
+
eigenclass = class << base; self; end
|
229
|
+
attributes = base.instance_variable_get('@attributes')
|
230
|
+
eigenclass.instance_eval do
|
231
|
+
attributes.each do | attribute|
|
232
|
+
remove_method "find_by_#{attribute}"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# @private
|
238
|
+
def self.context_for_selector(selector)
|
239
|
+
if selector.blank?
|
240
|
+
page
|
241
|
+
else
|
242
|
+
begin
|
243
|
+
page.find(selector).find(:xpath, '..')
|
244
|
+
rescue Capybara::Ambiguous
|
245
|
+
raise MultipleRecords, "Found multiple HTML segments with selector #{selector} on page"
|
246
|
+
rescue Capybara::ElementNotFound
|
247
|
+
raise RecordNotFound, "#{selector} not found on page"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
end
|