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 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,3 @@
1
+ # encoding: utf-8
2
+
3
+ require "manufactory"
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ require "hash_struct"
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
6
+
7
+ Spec::Runner.configure do |config|
8
+ config.before(:each) { Sham.reset }
9
+ end
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
+