schlick-pickle 0.1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +71 -0
- data/License.txt +20 -0
- data/Todo.txt +0 -0
- data/lib/pickle/adapter.rb +85 -0
- data/lib/pickle/config.rb +48 -0
- data/lib/pickle/injector.rb +18 -0
- data/lib/pickle/parser/matchers.rb +87 -0
- data/lib/pickle/parser/with_session.rb +24 -0
- data/lib/pickle/parser.rb +65 -0
- data/lib/pickle/session.rb +109 -0
- data/lib/pickle/version.rb +9 -0
- data/lib/pickle.rb +39 -0
- data/rails_generators/pickle/pickle_generator.rb +23 -0
- data/rails_generators/pickle/templates/env.rb +10 -0
- data/rails_generators/pickle/templates/pickle_steps.rb +41 -0
- data/spec/lib/pickle_adapter_spec.rb +134 -0
- data/spec/lib/pickle_config_spec.rb +97 -0
- data/spec/lib/pickle_injector_spec.rb +22 -0
- data/spec/lib/pickle_parser_matchers_spec.rb +70 -0
- data/spec/lib/pickle_parser_spec.rb +154 -0
- data/spec/lib/pickle_session_spec.rb +300 -0
- data/spec/lib/pickle_spec.rb +24 -0
- metadata +83 -0
data/History.txt
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
== edge
|
2
|
+
|
3
|
+
* 1 major enhancement
|
4
|
+
* generate page steps with script/generate pickle page
|
5
|
+
page steps allow you to do things like this
|
6
|
+
When I go to the comment's page
|
7
|
+
Then I should be at the user's new comment page
|
8
|
+
|
9
|
+
* 3 minor enhancements
|
10
|
+
* Improved documentation
|
11
|
+
* absract models no longer kill pickle
|
12
|
+
* Actually test that the generators work
|
13
|
+
|
14
|
+
== 0.1.5
|
15
|
+
|
16
|
+
* API change
|
17
|
+
* CaptureModel, etc are now 'capture_model' methods
|
18
|
+
|
19
|
+
* 3 major enhancements
|
20
|
+
* Steps for asserting that <n> models exist, matching certain criteria
|
21
|
+
* Steps for asserting associations added to generated pickle steps
|
22
|
+
'Then the user should be in the post's commenters'
|
23
|
+
'Then the forum: "awesome" should be the 2nd post's forum'
|
24
|
+
* configuration can now occur any time before a step is defined, which makes
|
25
|
+
for much more intuitive env.rb
|
26
|
+
|
27
|
+
* 2 minor enhancement
|
28
|
+
* predicate matching is less prone to step conflicts because we preload a
|
29
|
+
big list of all the predicate and column methods
|
30
|
+
* field values now handle booleans and numerics
|
31
|
+
|
32
|
+
|
33
|
+
== 0.1.4
|
34
|
+
|
35
|
+
* 1 major enhancement
|
36
|
+
* You can create multiple models with ease, for eg.
|
37
|
+
'Given 10 users exist with role: "admin"'
|
38
|
+
|
39
|
+
* 1 minor enhancement
|
40
|
+
* You can do Pickle.configure (just like Webrat.configure)
|
41
|
+
|
42
|
+
|
43
|
+
== 0.1.3 - Bugfix release
|
44
|
+
|
45
|
+
* 1 minor enhancement
|
46
|
+
* make generated steps compatible with Rails 2.1
|
47
|
+
|
48
|
+
|
49
|
+
== 0.1.2
|
50
|
+
|
51
|
+
* 2 major enhancements
|
52
|
+
* create your pickle steps with script/generate pickle
|
53
|
+
* Adapter based architecture, supports Machinist, FactoryGirl, and vanilla ActiveRecord
|
54
|
+
|
55
|
+
* 1 minor enhancement
|
56
|
+
* model_names now defaults to subclasses of AR::Base
|
57
|
+
* #original_model => #created_model
|
58
|
+
|
59
|
+
|
60
|
+
== 0.1.1
|
61
|
+
|
62
|
+
* 1 major enhancement:
|
63
|
+
* made pickle a github gem
|
64
|
+
|
65
|
+
* 1 minor enhancement:
|
66
|
+
* Added intentions for pickle in README.textile
|
67
|
+
|
68
|
+
|
69
|
+
== Prior to gems
|
70
|
+
|
71
|
+
* Initial release: everything is subject to sweeping change
|
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008-2009 Ian White - ian.w.white@gmail.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Todo.txt
ADDED
File without changes
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Pickle
|
2
|
+
# Abstract Factory adapter class, if you have a factory type setup, you
|
3
|
+
# can easily create an adaptor to make it work with Pickle.
|
4
|
+
#
|
5
|
+
# The factory adaptor must have a #factories class method that returns
|
6
|
+
# its instances, and each instance must respond to:
|
7
|
+
#
|
8
|
+
# #name : identifies the factory by name (default is attr_reader)
|
9
|
+
# #klass : returns the associated model class for this factory (default is attr_reader)
|
10
|
+
# #create(attrs = {}) : returns a newly created object
|
11
|
+
class Adapter
|
12
|
+
attr_reader :name, :klass
|
13
|
+
|
14
|
+
def self.factories
|
15
|
+
raise NotImplementedError, "return an array of factory adapter objects"
|
16
|
+
end
|
17
|
+
|
18
|
+
def create(attrs = {})
|
19
|
+
raise NotImplementedError, "create and return an object with the given attributes"
|
20
|
+
end
|
21
|
+
|
22
|
+
cattr_writer :model_classes
|
23
|
+
self.model_classes = nil
|
24
|
+
|
25
|
+
def self.model_classes
|
26
|
+
@@model_classes ||= ::ActiveRecord::Base.send(:subclasses).reject do |klass|
|
27
|
+
klass.abstract_class? || [CGI::Session::ActiveRecordStore::Session].include?(klass)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# machinist adapter
|
32
|
+
class Machinist < Adapter
|
33
|
+
def self.factories
|
34
|
+
factories = []
|
35
|
+
model_classes.each do |klass|
|
36
|
+
factories << new(klass, "make") if klass.instance_variable_get('@blueprint')
|
37
|
+
# if there are make_<special> methods, add blueprints for them
|
38
|
+
klass.methods.select{|m| m =~ /^make_/ && m !~ /_unsaved$/}.each do |method|
|
39
|
+
factories << new(klass, method)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
factories
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(klass, method)
|
46
|
+
@klass, @method = klass, method
|
47
|
+
@name = (@method =~ /make_/ ? "#{@method.sub('make_','')}_" : "") + @klass.name.underscore.gsub('/','_')
|
48
|
+
end
|
49
|
+
|
50
|
+
def create(attrs = {})
|
51
|
+
@klass.send(@method, attrs)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# factory-girl adapter
|
56
|
+
class FactoryGirl < Adapter
|
57
|
+
def self.factories
|
58
|
+
(::Factory.factories.values rescue []).map {|factory| new(factory)}
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(factory)
|
62
|
+
@klass, @name = factory.build_class, factory.factory_name.to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def create(attrs = {})
|
66
|
+
Factory.create(@name, attrs)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# fallback active record adapter
|
71
|
+
class ActiveRecord < Adapter
|
72
|
+
def self.factories
|
73
|
+
model_classes.map {|klass| new(klass) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize(klass)
|
77
|
+
@klass, @name = klass, klass.name.underscore.gsub('/','_')
|
78
|
+
end
|
79
|
+
|
80
|
+
def create(attrs = {})
|
81
|
+
@klass.send(:create!, attrs)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Pickle
|
4
|
+
class Config
|
5
|
+
attr_writer :adapters, :factories, :mappings, :predicates
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
configure(&block) if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure(&block)
|
12
|
+
yield(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def adapters
|
16
|
+
@adapters ||= [:machinist, :factory_girl, :active_record]
|
17
|
+
end
|
18
|
+
|
19
|
+
def adapter_classes
|
20
|
+
adapters.map {|a| a.is_a?(Class) ? a : "pickle/adapter/#{a}".classify.constantize}
|
21
|
+
end
|
22
|
+
|
23
|
+
def factories
|
24
|
+
@factories ||= adapter_classes.reverse.inject({}) do |factories, adapter|
|
25
|
+
factories.merge(adapter.factories.inject({}){|h, f| h.merge(f.name => f)})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def predicates
|
30
|
+
@predicates ||= Pickle::Adapter.model_classes.map do |k|
|
31
|
+
k.public_instance_methods.select{|m| m =~ /\?$/} + k.column_names
|
32
|
+
end.flatten.uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
def mappings
|
36
|
+
@mappings ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Usage: map 'me', 'myself', 'I', :to => 'user: "me"'
|
40
|
+
def map(*args)
|
41
|
+
options = args.extract_options!
|
42
|
+
raise ArgumentError, "Usage: map 'search' [, 'search2', ...] :to => 'replace'" unless args.any? && options[:to].is_a?(String)
|
43
|
+
args.each do |search|
|
44
|
+
self.mappings << OpenStruct.new(:search => search, :replace => options[:to])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Pickle
|
2
|
+
module Injector
|
3
|
+
def self.inject(delegate_class, options = {})
|
4
|
+
target_class = options[:into] || raise('inject requires a target class specified with :into')
|
5
|
+
delegate_name = options[:name] || delegate_class.name.underscore.gsub('/','_')
|
6
|
+
init_delegate = options[:init] || lambda { new }
|
7
|
+
|
8
|
+
# create a session object on demand (in target)
|
9
|
+
target_class.send(:define_method, delegate_name) do
|
10
|
+
instance_variable_get("@#{delegate_name}") || instance_variable_set("@#{delegate_name}", delegate_class.instance_eval(&init_delegate))
|
11
|
+
end
|
12
|
+
|
13
|
+
# in the target, delegate the public instance methods of delegate_class to the delegate_name method
|
14
|
+
delegate_methods = delegate_class.public_instance_methods - Object.instance_methods
|
15
|
+
target_class.delegate *(delegate_methods + [{:to => delegate_name}])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Pickle
|
2
|
+
class Parser
|
3
|
+
module Matchers
|
4
|
+
def match_ordinal
|
5
|
+
'(?:\d+(?:st|nd|rd|th))'
|
6
|
+
end
|
7
|
+
|
8
|
+
def match_index
|
9
|
+
"(?:first|last|#{match_ordinal})"
|
10
|
+
end
|
11
|
+
|
12
|
+
def match_prefix
|
13
|
+
'(?:(?:1|a|an|another|the|that) )'
|
14
|
+
end
|
15
|
+
|
16
|
+
def match_quoted
|
17
|
+
'(?:[^\\"]|\\.)*'
|
18
|
+
end
|
19
|
+
|
20
|
+
def match_label
|
21
|
+
"(?::? \"#{match_quoted}\")"
|
22
|
+
end
|
23
|
+
|
24
|
+
def match_value
|
25
|
+
"(?:\"#{match_quoted}\"|true|false|\\d+(?:\\.\\d+)?)"
|
26
|
+
end
|
27
|
+
|
28
|
+
def match_field
|
29
|
+
"(?:\\w+: #{match_value})"
|
30
|
+
end
|
31
|
+
|
32
|
+
def match_fields
|
33
|
+
"(?:#{match_field}, )*#{match_field}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def match_mapping
|
37
|
+
"(?:#{config.mappings.map(&:search).join('|')})"
|
38
|
+
end
|
39
|
+
|
40
|
+
def match_factory
|
41
|
+
"(?:#{config.factories.keys.map{|n| n.gsub('_','[_ ]')}.join('|')})"
|
42
|
+
end
|
43
|
+
|
44
|
+
def match_plural_factory
|
45
|
+
"(?:#{config.factories.keys.map{|n| n.pluralize.gsub('_','[_ ]')}.join('|')})"
|
46
|
+
end
|
47
|
+
|
48
|
+
def match_indexed_model
|
49
|
+
"(?:(?:#{match_index} )?#{match_factory})"
|
50
|
+
end
|
51
|
+
|
52
|
+
def match_labeled_model
|
53
|
+
"(?:#{match_factory}#{match_label})"
|
54
|
+
end
|
55
|
+
|
56
|
+
def match_model
|
57
|
+
"(?:#{match_mapping}|#{match_prefix}?(?:#{match_indexed_model}|#{match_labeled_model}))"
|
58
|
+
end
|
59
|
+
|
60
|
+
def match_predicate
|
61
|
+
"(?:#{config.predicates.map{|m| m.sub(/\?$/,'').gsub('_','[_ ]')}.join('|')})"
|
62
|
+
end
|
63
|
+
|
64
|
+
# create capture analogues of match methods
|
65
|
+
instance_methods.select{|m| m =~ /^match_/}.each do |method|
|
66
|
+
eval <<-end_eval
|
67
|
+
def #{method.sub('match_', 'capture_')} # def capture_field
|
68
|
+
"(" + #{method} + ")" # "(" + match_field + ")"
|
69
|
+
end # end
|
70
|
+
end_eval
|
71
|
+
end
|
72
|
+
|
73
|
+
# special capture methods
|
74
|
+
def capture_number_in_ordinal
|
75
|
+
'(?:(\d+)(?:st|nd|rd|th))'
|
76
|
+
end
|
77
|
+
|
78
|
+
def capture_name_in_label
|
79
|
+
"(?::? \"(#{match_quoted})\")"
|
80
|
+
end
|
81
|
+
|
82
|
+
def capture_key_and_value_in_field
|
83
|
+
"(?:(\\w+): #{capture_value})"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Pickle
|
2
|
+
class Parser
|
3
|
+
# add ability to parse model names as fields, using a session
|
4
|
+
module WithSession
|
5
|
+
def self.included(parser_class)
|
6
|
+
parser_class.alias_method_chain :parse_field, :model
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :session
|
10
|
+
|
11
|
+
def match_field
|
12
|
+
"(?:\\w+: (?:#{match_model}|#{match_value}))"
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_field_with_model(field)
|
16
|
+
if session && field =~ /^(\w+): #{capture_model}$/
|
17
|
+
{$1 => session.model($2)}
|
18
|
+
else
|
19
|
+
parse_field_without_model(field)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'pickle/parser/matchers'
|
2
|
+
|
3
|
+
module Pickle
|
4
|
+
class Parser
|
5
|
+
include Matchers
|
6
|
+
|
7
|
+
attr_reader :config
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@config = options[:config] || raise(ArgumentError, "Parser.new requires a :config")
|
11
|
+
end
|
12
|
+
|
13
|
+
# given a string like 'foo: "bar", bar: "baz"' returns {"foo" => "bar", "bar" => "baz"}
|
14
|
+
def parse_fields(fields)
|
15
|
+
if fields.blank?
|
16
|
+
{}
|
17
|
+
elsif fields =~ /^#{match_fields}$/
|
18
|
+
fields.scan(/(#{match_field})(?:,|$)/).inject({}) do |m, match|
|
19
|
+
m.merge(parse_field(match[0]))
|
20
|
+
end
|
21
|
+
else
|
22
|
+
raise ArgumentError, "The fields string is not in the correct format.\n\n'#{fields}' did not match: #{match_fields}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# given a string like 'foo: expr' returns {key => value}
|
27
|
+
def parse_field(field)
|
28
|
+
if field =~ /^#{capture_key_and_value_in_field}$/
|
29
|
+
{ $1 => eval($2) }
|
30
|
+
else
|
31
|
+
raise ArgumentError, "The field argument is not in the correct format.\n\n'#{field}' did not match: #{match_field}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# returns really underscored name
|
36
|
+
def canonical(str)
|
37
|
+
str.to_s.underscore.gsub(' ','_').gsub('/','_')
|
38
|
+
end
|
39
|
+
|
40
|
+
# return [factory_name, name or integer index]
|
41
|
+
def parse_model(model_name)
|
42
|
+
apply_mappings!(model_name)
|
43
|
+
if /#{capture_index} #{capture_factory}$/ =~ model_name
|
44
|
+
[canonical($2), parse_index($1)]
|
45
|
+
elsif /#{capture_factory}#{capture_name_in_label}?$/ =~ model_name
|
46
|
+
[canonical($1), canonical($2)]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_index(index)
|
51
|
+
case index
|
52
|
+
when '', /last/ then -1
|
53
|
+
when /#{capture_number_in_ordinal}/ then $1.to_i - 1
|
54
|
+
when /first/ then 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def apply_mappings!(string)
|
60
|
+
config.mappings.each do |mapping|
|
61
|
+
string.sub! /^#{mapping.search}$/, mapping.replace
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Pickle
|
2
|
+
class Session
|
3
|
+
def initialize(options = {})
|
4
|
+
self.parser = options[:parser] || Pickle.parser
|
5
|
+
@config = parser.config
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_model(a_model_name, fields = nil)
|
9
|
+
factory, label = *parser.parse_model(a_model_name)
|
10
|
+
raise ArgumentError, "Can't create with an ordinal (e.g. 1st user)" if label.is_a?(Integer)
|
11
|
+
record = config.factories[factory].create(parser.parse_fields(fields))
|
12
|
+
store_model(factory, label, record)
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_model(a_model_name, fields = nil)
|
16
|
+
factory, name = *parser.parse_model(a_model_name)
|
17
|
+
raise ArgumentError, "Can't find a model with an ordinal (e.g. 1st user)" if name.is_a?(Integer)
|
18
|
+
model_class = config.factories[factory].klass
|
19
|
+
if record = model_class.find(:first, :conditions => convert_models_to_attributes(model_class, parser.parse_fields(fields)))
|
20
|
+
store_model(factory, name, record)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_models(factory, fields = nil)
|
25
|
+
models_by_index(factory).clear
|
26
|
+
model_class = config.factories[factory].klass
|
27
|
+
records = model_class.find(:all, :conditions => convert_models_to_attributes(model_class, parser.parse_fields(fields)))
|
28
|
+
records.each {|record| store_model(factory, nil, record)}
|
29
|
+
end
|
30
|
+
|
31
|
+
# return the original model stored by create_model or find_model
|
32
|
+
def created_model(name)
|
33
|
+
factory, name_or_index = *parser.parse_model(name)
|
34
|
+
|
35
|
+
if name_or_index.blank?
|
36
|
+
models_by_index(factory).last
|
37
|
+
elsif name_or_index.is_a?(Integer)
|
38
|
+
models_by_index(factory)[name_or_index]
|
39
|
+
else
|
40
|
+
models_by_name(factory)[name_or_index] or raise "model: #{name} does not refer to known model in this scenario"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# predicate version which raises no errors
|
45
|
+
def created_model?(name)
|
46
|
+
(created_model(name) rescue nil) ? true : false
|
47
|
+
end
|
48
|
+
|
49
|
+
# return a newly selected model
|
50
|
+
def model(name)
|
51
|
+
(model = created_model(name)) && model.class.find(model.id)
|
52
|
+
end
|
53
|
+
|
54
|
+
# predicate version which raises no errors
|
55
|
+
def model?(name)
|
56
|
+
(model(name) rescue nil) ? true : false
|
57
|
+
end
|
58
|
+
|
59
|
+
# return all original models of specified type
|
60
|
+
def created_models(factory)
|
61
|
+
models_by_index(factory)
|
62
|
+
end
|
63
|
+
|
64
|
+
# return all models of specified type (freshly selected from the database)
|
65
|
+
def models(factory)
|
66
|
+
created_models(factory).map{|model| model.class.find(model.id) }
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
attr_reader :parser, :config
|
71
|
+
delegate :canonical, :to => :parser
|
72
|
+
|
73
|
+
def parser=(parser)
|
74
|
+
parser.session = self
|
75
|
+
@parser = parser
|
76
|
+
end
|
77
|
+
|
78
|
+
def convert_models_to_attributes(ar_class, attrs)
|
79
|
+
attrs.each do |key, val|
|
80
|
+
if val.is_a?(ActiveRecord::Base) && ar_class.column_names.include?("#{key}_id")
|
81
|
+
attrs["#{key}_id"] = val.id
|
82
|
+
attrs["#{key}_type"] = val.class.name if ar_class.column_names.include?("#{key}_type")
|
83
|
+
attrs.delete(key)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def models_by_name(factory)
|
89
|
+
@models_by_name ||= {}
|
90
|
+
@models_by_name[canonical(factory)] ||= {}
|
91
|
+
end
|
92
|
+
|
93
|
+
def models_by_index(factory)
|
94
|
+
@models_by_index ||= {}
|
95
|
+
@models_by_index[canonical(factory)] ||= []
|
96
|
+
end
|
97
|
+
|
98
|
+
# if the factory name != the model name, store under both names
|
99
|
+
def store_model(factory, name, record)
|
100
|
+
store_record(record.class.name, name, record) unless canonical(factory) == canonical(record.class.name)
|
101
|
+
store_record(factory, name, record)
|
102
|
+
end
|
103
|
+
|
104
|
+
def store_record(factory, name, record)
|
105
|
+
models_by_name(factory)[name] = record
|
106
|
+
models_by_index(factory) << record
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/pickle.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'pickle/version'
|
3
|
+
require 'pickle/adapter'
|
4
|
+
require 'pickle/config'
|
5
|
+
require 'pickle/parser'
|
6
|
+
require 'pickle/parser/with_session'
|
7
|
+
require 'pickle/session'
|
8
|
+
require 'pickle/injector'
|
9
|
+
require 'pickle/page'
|
10
|
+
|
11
|
+
# make the parser aware of models in the session (for fields refering to models)
|
12
|
+
Pickle::Parser.send :include, Pickle::Parser::WithSession
|
13
|
+
|
14
|
+
module Pickle
|
15
|
+
class << self
|
16
|
+
def config
|
17
|
+
@config ||= Config.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure(&block)
|
21
|
+
config.configure(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def parser(options = {})
|
25
|
+
@parser ||= Parser.new({:config => config}.merge(options))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# shortcuts to regexps for use in step definition regexps, *and* in steps
|
31
|
+
delegations = %w(capture_model capture_fields capture_factory capture_plural_factory capture_predicate)
|
32
|
+
(class << self; self; end).delegate *(delegations + [{:to => 'Pickle.parser'}])
|
33
|
+
Pickle::Session.delegate *(delegations + [{:to => :parser}])
|
34
|
+
|
35
|
+
# inject the pickle session into integration session if we have one (TODO: inject into merb etc?)
|
36
|
+
if defined?(ActionController::Integration::Session)
|
37
|
+
Pickle::Injector.inject Pickle::Session, :into => ActionController::Integration::Session
|
38
|
+
ActionController::Integration::Session.send :include, Pickle::Page
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class PickleGenerator < Rails::Generator::Base
|
2
|
+
def initialize(args, options)
|
3
|
+
super(args, options)
|
4
|
+
@generate_page_steps = args.include?('page')
|
5
|
+
File.exists?('features/support/env.rb') or raise "features/support/env.rb not found, try running script/generate cucumber"
|
6
|
+
end
|
7
|
+
|
8
|
+
def manifest
|
9
|
+
record do |m|
|
10
|
+
m.directory File.join('features/step_definitions')
|
11
|
+
|
12
|
+
current_env = File.read('features/support/env.rb')
|
13
|
+
if current_env.include?("require 'pickle'")
|
14
|
+
logger.skipped "features/support/env.rb, as it already requires pickle"
|
15
|
+
else
|
16
|
+
m.template 'env.rb', File.join('features/support', "env.rb"), :assigns => {:current_env => current_env}, :collision => :force
|
17
|
+
end
|
18
|
+
|
19
|
+
m.template 'pickle_steps.rb', File.join('features/step_definitions', "pickle_steps.rb")
|
20
|
+
m.template 'page_steps.rb', File.join('features/step_definitions', "page_steps.rb") if @generate_page_steps
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# this file generated by script/generate pickle
|
2
|
+
|
3
|
+
# create a model
|
4
|
+
Given(/^#{capture_model} exists?(?: with #{capture_fields})?$/) do |name, fields|
|
5
|
+
create_model(name, fields)
|
6
|
+
end
|
7
|
+
|
8
|
+
# create n models
|
9
|
+
Given(/^(\d+) #{capture_plural_factory} exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
|
10
|
+
count.to_i.times { create_model(plural_factory.singularize, fields) }
|
11
|
+
end
|
12
|
+
|
13
|
+
# find a model
|
14
|
+
Then(/^#{capture_model} should exist(?: with #{capture_fields})?$/) do |name, fields|
|
15
|
+
find_model(name, fields).should_not be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# find exactly n models
|
19
|
+
Then(/^(\d+) #{capture_plural_factory} should exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
|
20
|
+
find_models(plural_factory.singularize, fields).size.should == count.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
# assert model is in another model's has_many assoc
|
24
|
+
Then(/^#{capture_model} should be (?:in|one of|amongst) #{capture_model}'s (\w+)$/) do |target, owner, association|
|
25
|
+
model(owner).send(association).should include(model(target))
|
26
|
+
end
|
27
|
+
|
28
|
+
# assert model is another model's has_one/belongs_to assoc
|
29
|
+
Then(/^#{capture_model} should be #{capture_model}'s (\w+)$/) do |target, owner, association|
|
30
|
+
model(owner).send(association).should == model(target)
|
31
|
+
end
|
32
|
+
|
33
|
+
# assert model.predicate?
|
34
|
+
Then(/^#{capture_model} should (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
|
35
|
+
model(name).should send("be_#{predicate.gsub(' ', '_')}")
|
36
|
+
end
|
37
|
+
|
38
|
+
# assert not model.predicate?
|
39
|
+
Then(/^#{capture_model} should not (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
|
40
|
+
model(name).should_not send("be_#{predicate.gsub(' ', '_')}")
|
41
|
+
end
|