fixture_overlord 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +3 -0
- data/License.txt +22 -0
- data/Rakefile +9 -0
- data/Readme.mkd +70 -0
- data/bin/bundler +16 -0
- data/bin/erubis +16 -0
- data/bin/rackup +16 -0
- data/bin/rails +16 -0
- data/bin/rake +16 -0
- data/bin/ri +16 -0
- data/bin/sprockets +16 -0
- data/bin/thor +16 -0
- data/bin/tilt +16 -0
- data/bin/tt +16 -0
- data/fixture_overlord.gemspec +26 -0
- data/lib/fixture_overlord/fixture_accessor.rb +40 -0
- data/lib/fixture_overlord/hashish.rb +75 -0
- data/lib/fixture_overlord/helpers.rb +35 -0
- data/lib/fixture_overlord/mock.rb +132 -0
- data/lib/fixture_overlord/model.rb +55 -0
- data/lib/fixture_overlord/object_coercion.rb +7 -0
- data/lib/fixture_overlord/read_fixture.rb +62 -0
- data/lib/fixture_overlord/string_extension.rb +29 -0
- data/lib/fixture_overlord/version.rb +10 -0
- data/lib/fixture_overlord.rb +17 -0
- data/test/fixture_accessor_test.rb +97 -0
- data/test/fixture_overlord_test.rb +4 -0
- data/test/fixtures/company.yml +3 -0
- data/test/fixtures/error.yaml +2 -0
- data/test/fixtures/features.yml +2 -0
- data/test/fixtures/hotel.yml +7 -0
- data/test/fixtures/person.yaml +7 -0
- data/test/fixtures/phone.yml +7 -0
- data/test/hashish_test.rb +56 -0
- data/test/mock_test.rb +65 -0
- data/test/model_test.rb +72 -0
- data/test/read_fixture_test.rb +41 -0
- data/test/test_helper.rb +6 -0
- metadata +165 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'ostruct'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
# TODO: refactor to a class
|
6
|
+
module FixtureOverlord
|
7
|
+
module Helpers
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# reading the yaml filename
|
11
|
+
def yaml_filename(file)
|
12
|
+
::File.basename(file).split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
# take the yaml filename and convert it to a model classname
|
16
|
+
def to_model(file)
|
17
|
+
model = yaml_filename(file).to_s.classify.constantize
|
18
|
+
model
|
19
|
+
end
|
20
|
+
|
21
|
+
# check to see if the model is inherited from ActiveRecord
|
22
|
+
#
|
23
|
+
# TODO: Add more than just ActiveRecord, specifically Sequel
|
24
|
+
#
|
25
|
+
def persisted_model?(file)
|
26
|
+
model = to_model(file)
|
27
|
+
model.respond_to?(:superclass) && model.superclass == ::ActiveRecord::Base
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to_model_methods?(model)
|
31
|
+
model.respond_to?(:create!) || model.respond_to?(:create) || model.respond_to?(:build)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'securerandom'
|
3
|
+
require_relative 'helpers'
|
4
|
+
require_relative 'hashish'
|
5
|
+
|
6
|
+
module FixtureOverlord
|
7
|
+
class Mock < OpenStruct
|
8
|
+
|
9
|
+
def self.setup(hash)
|
10
|
+
new(hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(hash)
|
14
|
+
super(hash.merge(generate_id))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Converts attributes back to a hash (Hashish).
|
18
|
+
# Beacuse this is still a Hashish Hash, we can covert
|
19
|
+
# it back to a mock.
|
20
|
+
#
|
21
|
+
# ==== Examples
|
22
|
+
#
|
23
|
+
# e.g.
|
24
|
+
# blog.to_attributes => { title: 'Blog' }
|
25
|
+
#
|
26
|
+
def to_attributes
|
27
|
+
Hashish[self.to_h].symbolize_keys
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# add a child association
|
32
|
+
#
|
33
|
+
# ==== Examples
|
34
|
+
#
|
35
|
+
# e.g. (has_many)
|
36
|
+
#
|
37
|
+
# blog.child(posts: post)
|
38
|
+
# blog.posts.first.title
|
39
|
+
#
|
40
|
+
# There are 4 methods aliased to this one to provide
|
41
|
+
# the developer ActiveRecord & a Sequel (ORM) like
|
42
|
+
# interface.
|
43
|
+
#
|
44
|
+
def child(options)
|
45
|
+
associations(options) do |k,v|
|
46
|
+
writer(k,[v])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
alias :has_many :child
|
50
|
+
alias :one_to_many :child
|
51
|
+
|
52
|
+
|
53
|
+
# add a parent association
|
54
|
+
#
|
55
|
+
# ==== Examples
|
56
|
+
#
|
57
|
+
# e.g. (belongs_to)
|
58
|
+
#
|
59
|
+
# post.parent(blog: blog)
|
60
|
+
# post.blog.name
|
61
|
+
#
|
62
|
+
# There are 4 methods aliased to this one to provide
|
63
|
+
# the developer ActiveRecord & a Sequel (ORM) like
|
64
|
+
# interface.
|
65
|
+
#
|
66
|
+
def parent(options)
|
67
|
+
associations(options) do |k,v|
|
68
|
+
writer(k,v)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
alias :belongs_to :parent
|
72
|
+
alias :many_to_one :parent
|
73
|
+
alias :has_one :parent
|
74
|
+
alias :one_to_one :parent
|
75
|
+
|
76
|
+
|
77
|
+
# change a key/value or add one
|
78
|
+
#
|
79
|
+
# ==== Examples
|
80
|
+
#
|
81
|
+
# e.g.
|
82
|
+
# games(:donkey_kong).change(name: 'Jumpman Jr.').mock
|
83
|
+
# games(:donkey_kong).add(leader: 'Jacob').mock
|
84
|
+
#
|
85
|
+
def change(options = {})
|
86
|
+
options.each { |k,v| writer(k,v) }
|
87
|
+
self
|
88
|
+
end
|
89
|
+
alias :add :change
|
90
|
+
|
91
|
+
|
92
|
+
# remove an attribute from the class
|
93
|
+
#
|
94
|
+
# ==== Examples
|
95
|
+
#
|
96
|
+
# e.g.
|
97
|
+
# blog.remove(:name)
|
98
|
+
# blog.name == nil
|
99
|
+
#
|
100
|
+
def remove(key)
|
101
|
+
writer(key)
|
102
|
+
end
|
103
|
+
alias :delete :remove
|
104
|
+
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# generate a unique id and return the hash
|
109
|
+
#
|
110
|
+
def generate_id
|
111
|
+
{ id: ::SecureRandom.random_number(99999) }
|
112
|
+
end
|
113
|
+
|
114
|
+
# assign a value to a hash key
|
115
|
+
#
|
116
|
+
def writer(key, value = nil)
|
117
|
+
self.send("#{key}=", value)
|
118
|
+
end
|
119
|
+
|
120
|
+
def build_model_base
|
121
|
+
@build_model_base ||= Helpers.to_model(yaml_filename)
|
122
|
+
end
|
123
|
+
|
124
|
+
def associations(options, &block)
|
125
|
+
options.each do |k,v|
|
126
|
+
v = Mock.setup(v) if v.is_a?(Hash) || v.is_a?(Hashish)
|
127
|
+
yield k, v
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections.rb'
|
2
|
+
|
3
|
+
module FixtureOverlord
|
4
|
+
class Model
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def init(hash, filename)
|
8
|
+
new(hash, filename).new_model_with_values
|
9
|
+
end
|
10
|
+
|
11
|
+
def create(hash, filename)
|
12
|
+
new(hash, filename).create_as_model
|
13
|
+
end
|
14
|
+
alias_method :create!, :create
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(hash, filename)
|
18
|
+
@hash, @filename = hash, filename
|
19
|
+
end
|
20
|
+
|
21
|
+
def convert_to_model
|
22
|
+
model_name.new( sanitize_hash )
|
23
|
+
end
|
24
|
+
|
25
|
+
def new_model_with_values
|
26
|
+
model_name.new( sanitize_hash )
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_as_model
|
30
|
+
model_name.create( sanitize_hash )
|
31
|
+
end
|
32
|
+
|
33
|
+
def model_name
|
34
|
+
@model_name ||= @filename.to_s.singularize.classify.constantize
|
35
|
+
end
|
36
|
+
|
37
|
+
def can_convert_to_model?
|
38
|
+
model = model_name
|
39
|
+
model.respond_to?(:superclass) &&
|
40
|
+
model.superclass == ::ActiveRecord::Base
|
41
|
+
end
|
42
|
+
|
43
|
+
def respond_to_model_methods?
|
44
|
+
model = convert_to_model
|
45
|
+
model.respond_to?(:create!) ||
|
46
|
+
model.respond_to?(:create) ||
|
47
|
+
model.respond_to?(:build)
|
48
|
+
end
|
49
|
+
|
50
|
+
def sanitize_hash
|
51
|
+
@hash.delete_if { |key,value| key.to_sym == :id }
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative 'hashish'
|
2
|
+
require 'yaml'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module FixtureOverlord
|
6
|
+
FormattingError = Class.new(StandardError)
|
7
|
+
|
8
|
+
def self.read_fixture(yaml_file, key)
|
9
|
+
ReadFixture.new(yaml_file).read(key)
|
10
|
+
end
|
11
|
+
|
12
|
+
class ReadFixture
|
13
|
+
def initialize(file)
|
14
|
+
@file = file
|
15
|
+
end
|
16
|
+
|
17
|
+
def read(key = nil)
|
18
|
+
hash = read_file
|
19
|
+
hash = hash[key.to_s] unless key.nil?
|
20
|
+
Hashish[hash].symbolize_keys
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# CREDIT: The next three methods are almost identitical to those within Rails'
|
26
|
+
# ActiveRecord FixtureSet::File
|
27
|
+
#
|
28
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/fixture_set/file.rb
|
29
|
+
#
|
30
|
+
def read_file
|
31
|
+
begin
|
32
|
+
data = ::YAML.load(render)
|
33
|
+
rescue ::ArgumentError, ::Psych::SyntaxError => error
|
34
|
+
raise FormattingError, "a YAML error ocurred parsing #{@file}.\nThe error was:\n #{error.class}: #{error}", error.backtrace
|
35
|
+
end
|
36
|
+
validate(data)
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate(data)
|
40
|
+
unless valid_data?(data)
|
41
|
+
raise FormattingError, "fixture is not a Hash or YAML::Omap."
|
42
|
+
end
|
43
|
+
|
44
|
+
raise FormattingError unless data.all? { |name, row| ::Hash === row }
|
45
|
+
data
|
46
|
+
end
|
47
|
+
|
48
|
+
def render
|
49
|
+
erb_render(
|
50
|
+
IO.read(@file)
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def erb_render(content)
|
55
|
+
::ERB.new(content).result
|
56
|
+
end
|
57
|
+
|
58
|
+
def valid_data?(data)
|
59
|
+
::Hash === data || ::YAML::Omap === data
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
class String
|
3
|
+
def classify
|
4
|
+
if defined?(ActiveSupport::Inflector)
|
5
|
+
ActiveSupport::Inflector.classify(self)
|
6
|
+
else
|
7
|
+
self.gsub(/(_|-)/, ' ').
|
8
|
+
split(' ').each do |word|
|
9
|
+
word.capitalize!
|
10
|
+
end.join
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def constantize
|
15
|
+
if defined?(ActiveSupport::Inflector)
|
16
|
+
ActiveSupport::Inflector.constantize(self)
|
17
|
+
else
|
18
|
+
Object.const_get(self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def singularize
|
23
|
+
if defined?(ActiveSupport::Inflector)
|
24
|
+
ActiveSupport::Inflector.singularize(self, :en)
|
25
|
+
else
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'yaml'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
require_relative "fixture_overlord/string_extension" unless defined?(ActiveSupport::Inflector)
|
7
|
+
require_relative 'fixture_overlord/fixture_accessor'
|
8
|
+
|
9
|
+
module FixtureOverlord
|
10
|
+
def self.included(klass)
|
11
|
+
klass.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
include FixtureAccessor
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require './lib/fixture_overlord'
|
3
|
+
|
4
|
+
class Minitest::Test
|
5
|
+
include ::FixtureOverlord
|
6
|
+
fixture_overlord :rule
|
7
|
+
end
|
8
|
+
|
9
|
+
module FixtureOverlord
|
10
|
+
class FixtureAccessorTest < Minitest::Test
|
11
|
+
|
12
|
+
def hash
|
13
|
+
@hash ||= { name: 'Bob', age: 44 }
|
14
|
+
end
|
15
|
+
|
16
|
+
def bob
|
17
|
+
@bob ||= person(:bob).mock
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_hash_fixture
|
21
|
+
assert_equal hash, person(:bob)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_mock_fixture
|
25
|
+
assert bob
|
26
|
+
assert bob.id
|
27
|
+
assert_equal 'Bob', bob.name
|
28
|
+
assert_equal 44, bob.age
|
29
|
+
|
30
|
+
assert_equal hash.merge(id: bob.id), bob.to_attributes
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_mock_association
|
34
|
+
mobile = phone(:bob_mobile).mock
|
35
|
+
shared_mock_association_tests(bob, mobile)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_mock_assocation_when_assocation_is_hash
|
39
|
+
mobile = phone(:bob_mobile)
|
40
|
+
shared_mock_association_tests(bob, mobile)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_mock_association_belongs_to
|
44
|
+
cw = company(:cw).mock
|
45
|
+
assert cw.id
|
46
|
+
assert cw.name
|
47
|
+
assert cw.description
|
48
|
+
|
49
|
+
assert bob.belongs_to(company: cw)
|
50
|
+
assert_equal "Code Wranglers", bob.company.name
|
51
|
+
assert_equal "Custom Software Development", bob.company.description
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_mock_association_belongs_to_when_association_is_hash
|
55
|
+
cw = company(:cw)
|
56
|
+
|
57
|
+
assert bob.belongs_to(company: cw)
|
58
|
+
assert_equal "Code Wranglers", bob.company.name
|
59
|
+
assert_equal "Custom Software Development", bob.company.description
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_changing_a_mock_attribute
|
63
|
+
cw = company(:cw).mock
|
64
|
+
cw.change(name: 'Code Wranglers, Inc.')
|
65
|
+
assert_equal "Code Wranglers, Inc.", cw.name
|
66
|
+
|
67
|
+
cw.change(name: 'Code Wranglers - Software Development')
|
68
|
+
assert_equal 'Code Wranglers - Software Development', cw.name
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_adding_a_new_attribute
|
72
|
+
bob.add(gender: 'male')
|
73
|
+
assert_equal 'male', bob.gender
|
74
|
+
|
75
|
+
bob.change(gender: 'unicorn')
|
76
|
+
assert_equal 'unicorn', bob.gender
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_removing_attributes
|
80
|
+
bob.remove(:name)
|
81
|
+
assert_nil bob.name
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def shared_mock_association_tests(object, assoc)
|
87
|
+
assert object.has_many(phones: assoc)
|
88
|
+
|
89
|
+
assert_equal 18001234567, object.phones.first.number
|
90
|
+
assert_equal 1, object.phones.size
|
91
|
+
assert_equal "cell", object.phones.first.type
|
92
|
+
|
93
|
+
assert object.phones.first.id
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|