manufactory 0.0.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/CHANGELOG +0 -0
- data/LICENSE +20 -0
- data/README.textile +31 -0
- data/Rakefile +1 -0
- data/lib/manufactory/adapters/datamapper.rb +3 -0
- data/lib/manufactory/adapters/object.rb +46 -0
- data/lib/manufactory/sham.rb +81 -0
- data/lib/manufactory.rb +132 -0
- data/spec/manufactory/sham_spec.rb +97 -0
- data/spec/manufactory_spec.rb +47 -0
- data/spec/spec_helper.rb +9 -0
- metadata +73 -0
data/CHANGELOG
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jakub Stastny
|
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/README.textile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
h1. About
|
2
|
+
|
3
|
+
Manufactory is a minimalistic and clean rewrite of machinist with some improvements.
|
4
|
+
|
5
|
+
h1. What is different?
|
6
|
+
|
7
|
+
h2. Real DataMapper Support
|
8
|
+
|
9
|
+
Machinist doesn't work with DataMapper right now. It's not a machinist problem, it's DataMapper issue, but who cares, it just doesn't work.
|
10
|
+
|
11
|
+
h2. Initialization
|
12
|
+
|
13
|
+
initialization: model_class.new(attrs), so the hooks for default will be fired. it would be even if we do just modelclass.new, but if we have title and slug is created from the title ....
|
14
|
+
|
15
|
+
h2. Named Blueprints
|
16
|
+
|
17
|
+
# 2) You have to have one unnamed blueprint, otherwise you can't create the named ones
|
18
|
+
|
19
|
+
h2. Inheritance isn't implicit
|
20
|
+
|
21
|
+
# 3) It uses the default blueprint
|
22
|
+
# PaymentType.make(:paypal)
|
23
|
+
# => #<PaymentType @id=15 @name="PayPal" @shortcut="cod">
|
24
|
+
# PaymentType.new(name: "PayPal")
|
25
|
+
# => #<PaymentType @id=nil @name="PayPal" @shortcut="paypal">
|
26
|
+
|
27
|
+
|
28
|
+
PaymentType.blueprint(:paypel, PaymentType.blueprint)
|
29
|
+
name "PayPal"
|
30
|
+
shortcut nil
|
31
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# TODO: runcoderun
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "manufactory"
|
4
|
+
|
5
|
+
module Manufactory
|
6
|
+
module ObjectMixin
|
7
|
+
# Post.make
|
8
|
+
# Post.make(:named)
|
9
|
+
# Post.make(first_name: "Jakub")
|
10
|
+
# Post.make(named, first_name: "Jakub")
|
11
|
+
# Post.make(named, first_name: "Jakub") do
|
12
|
+
# self.whatever = whatever
|
13
|
+
# end
|
14
|
+
def make(name = :default, attributes = Hash.new, &block)
|
15
|
+
name, attributes = :default, name if name.is_a?(Hash) && attributes.empty?
|
16
|
+
callables = self.blueprints[name]
|
17
|
+
adapter = ObjectAdapter.new(self, name, callables)
|
18
|
+
instance = adapter.run(attributes)
|
19
|
+
instance.instance_eval(&block) if block_given?
|
20
|
+
return instance
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# ObjectAdapter.new(Object.new, *args)
|
25
|
+
# NOTE: first argument is an object, not a class!
|
26
|
+
class ObjectAdapter < Adapter
|
27
|
+
def has_association?(object, attribute)
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
def initialize_object(klass, default_attributes, attributes)
|
33
|
+
attributes = default_attributes.merge(attributes)
|
34
|
+
instance = klass.new
|
35
|
+
attributes.each do |attribute, value|
|
36
|
+
instance.send("#{attribute}=", value)
|
37
|
+
end
|
38
|
+
return instance
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Object
|
44
|
+
extend Manufactory::Blueprints
|
45
|
+
extend Manufactory::ObjectMixin
|
46
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Manufactory
|
4
|
+
class Sham
|
5
|
+
@@shams = Hash.new
|
6
|
+
|
7
|
+
# Over-ride module's built-in name method, so we can re-use it for
|
8
|
+
# generating names. This is a bit of a no-no, but we get away with
|
9
|
+
# it in this context.
|
10
|
+
def self.name(*args, &block)
|
11
|
+
method_missing(:name, *args, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.method_missing(symbol, *args, &block)
|
15
|
+
if block_given?
|
16
|
+
@@shams[symbol] = Sham.new(symbol, args.pop || {}, &block)
|
17
|
+
else
|
18
|
+
sham = @@shams[symbol]
|
19
|
+
raise "No sham defined for #{symbol}" if sham.nil?
|
20
|
+
sham.fetch_value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.clear
|
25
|
+
@@shams = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.reset(scope = :before_all)
|
29
|
+
@@shams.values.each { |sham| sham.reset(scope) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.define(&block)
|
33
|
+
Sham.instance_eval(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(name, options = {}, &block)
|
37
|
+
@name = name
|
38
|
+
@generator = block
|
39
|
+
@offset = 0
|
40
|
+
@unique = options.has_key?(:unique) ? options[:unique] : true
|
41
|
+
generate_values(12)
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset(scope)
|
45
|
+
if scope == :before_all
|
46
|
+
@offset, @before_offset = 0, nil
|
47
|
+
elsif @before_offset
|
48
|
+
@offset = @before_offset
|
49
|
+
else
|
50
|
+
@before_offset = @offset
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch_value
|
55
|
+
# Generate more values if we need them.
|
56
|
+
if @offset >= @values.length
|
57
|
+
generate_values(2 * @values.length)
|
58
|
+
raise "Can't generate more unique values for Sham.#{@name}" if @offset >= @values.length
|
59
|
+
end
|
60
|
+
result = @values[@offset]
|
61
|
+
@offset += 1
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def generate_values(count)
|
68
|
+
@values = seeded { (1..count).map(&@generator) }
|
69
|
+
@values.uniq! if @unique
|
70
|
+
end
|
71
|
+
|
72
|
+
def seeded
|
73
|
+
begin
|
74
|
+
srand(1)
|
75
|
+
yield
|
76
|
+
ensure
|
77
|
+
srand
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/manufactory.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "manufactory/sham"
|
4
|
+
|
5
|
+
module Manufactory
|
6
|
+
# ActiveRecord::Base.extend(Manufactory::Blueprints)
|
7
|
+
# Post.blueprint(&block)
|
8
|
+
module Blueprints
|
9
|
+
def blueprints
|
10
|
+
@blueprints ||= Hash.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def blueprint(name = :default, parent = nil, &block)
|
14
|
+
self.blueprints[name] ||= Array.new
|
15
|
+
self.blueprints[name].push(*parent)
|
16
|
+
self.blueprints[name].push(block) if block_given?
|
17
|
+
self.blueprints[name]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Adapter.new(User, :admin, age: 24)
|
22
|
+
class Adapter
|
23
|
+
attr_reader :klass, :name, :callables
|
24
|
+
def initialize(klass, name, callables)
|
25
|
+
@klass = klass
|
26
|
+
@name = name
|
27
|
+
@callables = callables
|
28
|
+
end
|
29
|
+
|
30
|
+
def run(attributes = Hash.new)
|
31
|
+
blueprint = klass.blueprints[name.to_sym]
|
32
|
+
raise "No blueprint #{name} for class #{klass}" if blueprint.nil?
|
33
|
+
default_attributes = Hash.new
|
34
|
+
self.callables.each do |callable|
|
35
|
+
default_attributes.merge!(DSL.new(self, callable).run)
|
36
|
+
end
|
37
|
+
self.initialize_object(klass, default_attributes, attributes)
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def initialize_object(klass, default_attributes, attributes)
|
42
|
+
klass.new(default_attributes.merge(attributes))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# DSL is used to execute the blueprint and construct an object.
|
47
|
+
# The blueprint is instance_eval'd against the Lathe.
|
48
|
+
class DSL
|
49
|
+
attr_reader :adapter, :blueprint
|
50
|
+
def initialize(adapter, blueprint)
|
51
|
+
@adapter = adapter
|
52
|
+
@blueprint = blueprint
|
53
|
+
end
|
54
|
+
|
55
|
+
def run
|
56
|
+
self.instance_eval(&self.blueprint)
|
57
|
+
assigned_attributes
|
58
|
+
end
|
59
|
+
|
60
|
+
def object
|
61
|
+
yield @object if block_given?
|
62
|
+
@object
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(symbol, *args, &block)
|
66
|
+
if attribute_assigned?(symbol)
|
67
|
+
# If we've already assigned the attribute, return that.
|
68
|
+
@object.send(symbol)
|
69
|
+
elsif @adapter.has_association?(@object, symbol) && !nil_or_empty?(@object.send(symbol))
|
70
|
+
# If the attribute is an association and is already assigned, return that.
|
71
|
+
@object.send(symbol)
|
72
|
+
else
|
73
|
+
# Otherwise generate a value and assign it.
|
74
|
+
assigned_attributes[symbol] = generate_attribute_value(symbol, *args, &block)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def assigned_attributes
|
79
|
+
@assigned_attributes ||= {}
|
80
|
+
end
|
81
|
+
|
82
|
+
# Undef a couple of methods that are common ActiveRecord attributes.
|
83
|
+
# (Both of these are deprecated in Ruby 1.8 anyway.)
|
84
|
+
undef_method :id if respond_to?(:id)
|
85
|
+
undef_method :type if respond_to?(:type)
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def nil_or_empty?(object)
|
90
|
+
object.respond_to?(:empty?) ? object.empty? : object.nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
def attribute_assigned?(key)
|
94
|
+
assigned_attributes.has_key?(key.to_sym)
|
95
|
+
end
|
96
|
+
|
97
|
+
def generate_attribute_value(attribute, *args)
|
98
|
+
if block_given?
|
99
|
+
# If we've got a block, use that to generate the value.
|
100
|
+
yield
|
101
|
+
else
|
102
|
+
# Otherwise, look for an association or a sham.
|
103
|
+
if @adapter.has_association?(object, attribute)
|
104
|
+
@adapter.class_for_association(object, attribute).make(args.first || {})
|
105
|
+
elsif args.empty?
|
106
|
+
Sham.send(attribute)
|
107
|
+
else
|
108
|
+
# If we've got a constant, just use that.
|
109
|
+
args.first
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
# This sets a flag that stops make from saving objects, so
|
117
|
+
# that calls to make from within a blueprint don't create
|
118
|
+
# anything inside make_unsaved.
|
119
|
+
def self.with_save_nerfed
|
120
|
+
begin
|
121
|
+
@@nerfed = true
|
122
|
+
yield
|
123
|
+
ensure
|
124
|
+
@@nerfed = false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
@@nerfed = false
|
129
|
+
def self.nerfed?
|
130
|
+
@@nerfed
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
4
|
+
require 'sham'
|
5
|
+
|
6
|
+
describe Sham do
|
7
|
+
it "should ensure generated values are unique" do
|
8
|
+
Sham.clear
|
9
|
+
Sham.half_index {|index| index/2 }
|
10
|
+
values = (1..10).map { Sham.half_index }
|
11
|
+
values.should == (0..9).to_a
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should generate non-unique values when asked" do
|
15
|
+
Sham.clear
|
16
|
+
Sham.coin_toss(:unique => false) {|index| index % 2 == 1 ? 'heads' : 'tails' }
|
17
|
+
values = (1..4).map { Sham.coin_toss }
|
18
|
+
values.should == ['heads', 'tails', 'heads', 'tails']
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should generate more than a dozen values" do
|
22
|
+
Sham.clear
|
23
|
+
Sham.index {|index| index }
|
24
|
+
values = (1..25).map { Sham.index }
|
25
|
+
values.should == (1..25).to_a
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should generate the same sequence of values after a reset" do
|
29
|
+
Sham.clear
|
30
|
+
Sham.random { rand }
|
31
|
+
values1 = (1..10).map { Sham.random }
|
32
|
+
Sham.reset
|
33
|
+
values2 = (1..10).map { Sham.random }
|
34
|
+
values2.should == values1
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should alias reset with reset(:before_all)" do
|
38
|
+
Sham.clear
|
39
|
+
Sham.random { rand }
|
40
|
+
values1 = (1..10).map { Sham.random }
|
41
|
+
Sham.reset(:before_all)
|
42
|
+
values2 = (1..10).map { Sham.random }
|
43
|
+
values2.should == values1
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should generate the same sequence of values after each reset(:before_each)" do
|
47
|
+
Sham.clear
|
48
|
+
Sham.random { rand }
|
49
|
+
values1 = (1..10).map { Sham.random }
|
50
|
+
Sham.reset(:before_each)
|
51
|
+
values2 = (1..10).map { Sham.random }
|
52
|
+
Sham.reset(:before_each)
|
53
|
+
values3 = (1..10).map { Sham.random }
|
54
|
+
values2.should_not == values1
|
55
|
+
values3.should == values2
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should generate a different sequence of values after reset(:before_all) followed by reset(:before_each)" do
|
59
|
+
Sham.clear
|
60
|
+
Sham.random { rand }
|
61
|
+
(1..10).map { Sham.random }
|
62
|
+
Sham.reset(:before_each)
|
63
|
+
values1 = (1..10).map { Sham.random }
|
64
|
+
Sham.reset(:before_all)
|
65
|
+
(1..5).map { Sham.random }
|
66
|
+
Sham.reset(:before_each)
|
67
|
+
values2 = (1..10).map { Sham.random }
|
68
|
+
values2.should_not == values1
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should die when it runs out of unique values" do
|
72
|
+
Sham.clear
|
73
|
+
Sham.limited {|index| index%10 }
|
74
|
+
lambda {
|
75
|
+
(1..100).map { Sham.limited }
|
76
|
+
}.should raise_error(RuntimeError)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should allow over-riding the name method" do
|
80
|
+
Sham.clear
|
81
|
+
Sham.name {|index| index }
|
82
|
+
Sham.name.should == 1
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "define method" do
|
86
|
+
it "should repeat messages in its block to Sham" do
|
87
|
+
block = Proc.new {}
|
88
|
+
Sham.should_receive(:name).with(&block).once.ordered
|
89
|
+
Sham.should_receive(:slug).with(:arg, &block).once.ordered
|
90
|
+
Sham.define do
|
91
|
+
name &block
|
92
|
+
slug :arg, &block
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative "spec_helper"
|
4
|
+
require "manufactory/adapters/object"
|
5
|
+
|
6
|
+
Post = HashStruct.new(:title, :body, :published)
|
7
|
+
|
8
|
+
describe Manufactory do
|
9
|
+
before(:each) do
|
10
|
+
Post.blueprints.clear
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "set attribute" do
|
14
|
+
it "should set an attribute if value for the attribute is provided" do
|
15
|
+
Post.blueprint { title "Hello World!" }
|
16
|
+
Post.make.title.should eql("Hello World!")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should set an attribute to return value of block if block is provided" do
|
20
|
+
Post.blueprint { title {"Hello World!"} }
|
21
|
+
Post.make.title.should eql("Hello World!")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "inheritance" do
|
26
|
+
it "should be able to inherit attributes" do
|
27
|
+
Post.blueprint { published false }
|
28
|
+
Post.blueprint(:first, Post.blueprint) do
|
29
|
+
title "Hello World!"
|
30
|
+
end
|
31
|
+
Post.make(:first).published.should be_false
|
32
|
+
Post.make(:first).title.should eql("Hello World!")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should " do
|
36
|
+
Post.blueprint { published false }
|
37
|
+
Post.blueprint(:first, Post.blueprint) do
|
38
|
+
published true
|
39
|
+
end
|
40
|
+
Post.make(:first).published.should be_true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise for make on a class with no blueprint" do
|
45
|
+
lambda { Post.make }.should raise_error(RuntimeError)
|
46
|
+
end
|
47
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: manufactory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Jakub \xC5\xA0\xC5\xA5astn\xC3\xBD aka Botanicus"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
date: 2009-11-28 00:00:00 +00:00
|
12
|
+
default_executable:
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: hash_struct
|
16
|
+
type: :development
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
description: ""
|
25
|
+
email: knava.bestvinensis@gmail.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- lib/manufactory/adapters/datamapper.rb
|
34
|
+
- lib/manufactory/adapters/object.rb
|
35
|
+
- lib/manufactory/sham.rb
|
36
|
+
- lib/manufactory.rb
|
37
|
+
- spec/manufactory/sham_spec.rb
|
38
|
+
- spec/manufactory_spec.rb
|
39
|
+
- spec/spec_helper.rb
|
40
|
+
- CHANGELOG
|
41
|
+
- LICENSE
|
42
|
+
- Rakefile
|
43
|
+
- README.textile
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/botanicus/manufactory
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 1.9.1
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project: manufactory
|
68
|
+
rubygems_version: 1.3.5
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Manufactory is a minimalistic and clean rewrite of machinist with some improvements.
|
72
|
+
test_files: []
|
73
|
+
|