bricks 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.2"
12
+ gem "rcov", ">= 0"
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Mojo Tech
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.md ADDED
@@ -0,0 +1,221 @@
1
+ Bricks
2
+ ======
3
+
4
+ Bricks is a hybrid Object Builder/Factory implementation. It aims to be a more flexible alternative to the existing Object Factory solutions while retaining as much simplicity as possible.
5
+
6
+ Usage
7
+ -----
8
+
9
+ Let's assume you have the following class:
10
+
11
+ # Only ActiveRecord objects are supported right now.
12
+
13
+ # == Schema Information
14
+ #
15
+ # Table name: articles
16
+ #
17
+ # id :integer(4) not null, primary key
18
+ # title :string(255)
19
+ # author :string(255)
20
+ # formatted_title :string(510)
21
+ # publication_id :integer(4)
22
+ #
23
+ class Article < ActiveRecord::Base
24
+ belongs_to :publication
25
+ has_many :readers
26
+ end
27
+
28
+ # == Schema Information
29
+ #
30
+ # Table name: publications
31
+ #
32
+ # id :integer(4) not null, primary key
33
+ # name :string(255)
34
+ # type :string(255)
35
+ #
36
+ class Publication < ActiveRecord::Base
37
+ end
38
+
39
+ class Newspaper < Publication
40
+ end
41
+
42
+ # == Schema Information
43
+ #
44
+ # Table name: publications
45
+ #
46
+ # id :integer(4) not null, primary key
47
+ # name :string(255)
48
+ # birth_date :date
49
+ #
50
+ class Reader < ActiveRecord::Base
51
+ end
52
+
53
+ At its simplest, you can start using Bricks without declaring any builder (*note:* it gets less verbose).
54
+
55
+ article_builder = build(Article)
56
+
57
+ This will give you a builder for the Article class, which you can then use to build an Article
58
+
59
+ article_builder.
60
+ title("Why I hate Guybrush Threepwood").
61
+ author("Ghost Pirate LeChuck")
62
+
63
+ Contrary to the original pattern, builders are stateful (i.e., you don't get a new builder every time you call a method on the current builder).
64
+
65
+ You can get the underlying instance by calling _#generate_.
66
+
67
+ article = article_builder.generate
68
+
69
+ This will initialize an Article with the attributes you passed the builder. If, instead of initializing, you'd prefer the record to be created right away, use _#create_ instead.
70
+
71
+ If you don't really care about the builder and just want the underlying instance you can instead use.
72
+
73
+ article = build(Article).
74
+ title("Why I hate Guybrush Threepwood").
75
+ author!("Ghost Pirate LeChuck") # Note the "!"
76
+
77
+ When you want to use the default builder, without customizing it any further, you can tack the "!" at the end of the builder method:
78
+
79
+ build!(Article)
80
+ create!(Article)
81
+
82
+ ### Building builders
83
+
84
+ Of course, using builders like described above isn't of much use. Let's create a builder for _Article_:
85
+
86
+ Bricks do
87
+ builder Article do
88
+ title "Why I hate Guybrush Threepwood"
89
+ author "Ghost Pirate LeChuck"
90
+ end
91
+ end
92
+
93
+ You can then use it as you'd expect:
94
+
95
+ # initializes an Article with default attributes set, and saves it
96
+ article = create!(Article)
97
+
98
+ ### Deferred initialization
99
+
100
+ builder Article do
101
+ # ...
102
+
103
+ formatted_title { "The formatted title at #{Date.now}." }
104
+ end
105
+
106
+ You can get at the underlying instance from deferred blocks:
107
+
108
+ builder Article do
109
+ # ...
110
+
111
+ formatted_title { |obj| obj.title + " by " + obj.author }
112
+ end
113
+
114
+ ### Associations
115
+
116
+ Bricks supports setting association records.
117
+
118
+ #### Many-to-one (belongs to)
119
+
120
+ Bricks do
121
+ builder Publication do
122
+ name "The Caribbean Times"
123
+ end
124
+
125
+ builder Article do
126
+ # ...
127
+
128
+ publication # instantiate a publication with the default attributes set
129
+ end
130
+ end
131
+
132
+ You can also customize the association builder instance:
133
+
134
+ builder Article do
135
+ # ...
136
+ publication.name("The Caribeeaneer")
137
+ end
138
+
139
+ If you prepend a "~" to the association declaration, the record will be initialized/created *only* if a record with the given attributes doesn't exist yet:
140
+
141
+ builder Article do
142
+ # ...
143
+ ~publication # will search for a record with name "The Caribbean Times"
144
+ end
145
+
146
+ #### One-to-many, Many-to-many (has many, has and belongs to many)
147
+
148
+ Bricks do
149
+ builder Article do
150
+ # ...
151
+
152
+ # readers association will have 3 records
153
+ %w(Tom Dick Harry).each { |r| readers.name(r) }
154
+ end
155
+ end
156
+
157
+ Each call to the *-to-many association name will add a new builder, which you can then further customize:
158
+
159
+ readers.name("Tom").birth_date(30.years.ago)
160
+
161
+ (Note that you don't use "!" here. That's only when building the records in your tests.)
162
+
163
+ ### Builder Inheritance
164
+
165
+ Given the builder:
166
+
167
+ builder Publication do
168
+ name "The Caribbean Times"
169
+ end
170
+
171
+ you can do something like:
172
+
173
+ np = build!(Newspaper)
174
+ np.name # => "The Caribbean Times"
175
+
176
+ ### Traits
177
+
178
+ The real power of the Builder pattern comes from the use of traits. Instead of declaring name factories in a single-inheritance model, you instead declare traits, which you can then mix and match:
179
+
180
+ builder Article
181
+ # ...
182
+
183
+ trait :alternative_publication do |name|
184
+ publication.name(name)
185
+ end
186
+
187
+ trait :by_elaine do
188
+ title "Why I love Guybrush Threepwood"
189
+ author "Elaine Marley-Threepwood"
190
+ end
191
+ end
192
+
193
+ Use it like this:
194
+
195
+ build(Article).alternative_publication("The Caribeaneer").by_elaine
196
+
197
+ Note that if you want to override a *-to-many association inside a trait, you need to clear it first:
198
+
199
+ builder Article
200
+ # ...
201
+
202
+ # this will reset the readers association
203
+ trait :new_readers do
204
+ readers.clear
205
+
206
+ %(Charlotte Emily Anne).each { |r| readers.name(r) }
207
+ end
208
+
209
+ # this will add to the readers association
210
+ trait :more_readers do
211
+ readers.name("Groucho")
212
+ end
213
+ end
214
+
215
+ For an executable version of this documentation, please see spec/bricks_spec.rb.
216
+
217
+ Copyright
218
+ ---------
219
+
220
+ Copyright (c) 2011 Mojo Tech. See LICENSE.txt for further details.
221
+
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "bricks"
18
+ gem.homepage = "http://github.com/mojotech/bricks"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Hybrid object builder/factory.}
21
+ gem.email = "david@mojotech.com"
22
+ gem.author = "David Leal"
23
+ # dependencies defined in Gemfile
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rspec/core'
28
+ require 'rspec/core/rake_task'
29
+ RSpec::Core::RakeTask.new(:spec) do |spec|
30
+ spec.pattern = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+ task :default => :spec
39
+
40
+ require 'rake/rdoctask'
41
+ Rake::RDocTask.new do |rdoc|
42
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
43
+
44
+ rdoc.rdoc_dir = 'rdoc'
45
+ rdoc.title = "bricks #{version}"
46
+ rdoc.rdoc_files.include('README*')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/bricks.gemspec ADDED
@@ -0,0 +1,67 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{bricks}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["David Leal"]
12
+ s.date = %q{2011-06-16}
13
+ s.email = %q{david@mojotech.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE.txt",
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".rspec",
21
+ "Gemfile",
22
+ "LICENSE.txt",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "bricks.gemspec",
27
+ "lib/bricks.rb",
28
+ "lib/bricks/adapters/active_record.rb",
29
+ "lib/bricks/builder.rb",
30
+ "lib/bricks/builder_set.rb",
31
+ "lib/bricks/dsl.rb",
32
+ "rails/init.rb",
33
+ "spec/bricks/adapters/active_record_spec.rb",
34
+ "spec/bricks/builder_spec.rb",
35
+ "spec/bricks_spec.rb",
36
+ "spec/spec_helper.rb",
37
+ "spec/support/active_record.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/mojotech/bricks}
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.7}
43
+ s.summary = %q{Hybrid object builder/factory.}
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ s.add_development_dependency(%q<rspec>, ["~> 2.0"])
51
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
52
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
53
+ s.add_development_dependency(%q<rcov>, [">= 0"])
54
+ else
55
+ s.add_dependency(%q<rspec>, ["~> 2.0"])
56
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
57
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
58
+ s.add_dependency(%q<rcov>, [">= 0"])
59
+ end
60
+ else
61
+ s.add_dependency(%q<rspec>, ["~> 2.0"])
62
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
63
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
64
+ s.add_dependency(%q<rcov>, [">= 0"])
65
+ end
66
+ end
67
+
@@ -0,0 +1,43 @@
1
+ require 'bricks'
2
+ require 'active_record'
3
+
4
+ module Bricks
5
+ module Adapters
6
+ class ActiveRecord
7
+ class Association
8
+ attr_reader :type
9
+
10
+ def initialize(klass, kind)
11
+ @class = klass
12
+ @type = case kind
13
+ when :belongs_to; :one
14
+ when :has_many, :has_and_belongs_to_many; :many
15
+ else "Unknown AR association type: #{kind}."
16
+ end
17
+ end
18
+
19
+ def klass
20
+ @class
21
+ end
22
+ end
23
+
24
+ def association?(klass, name, type = nil)
25
+ association(klass, name, type)
26
+ end
27
+
28
+ def association(klass, name, type = nil)
29
+ if ar = klass.reflect_on_association(name.to_sym)
30
+ a = Association.new(ar.klass, ar.macro)
31
+
32
+ a if type.nil? || a.type == type
33
+ end
34
+ end
35
+
36
+ def find(klass, obj)
37
+ klass.find(:first, :conditions => obj.attributes)
38
+ end
39
+
40
+ Bricks::Builder.adapter = self.new
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,150 @@
1
+ require 'bricks/dsl'
2
+ require 'bricks/builder_set'
3
+
4
+ module Bricks
5
+ class Builder
6
+ include Bricks::DSL
7
+
8
+ def self.adapter
9
+ @@adapter
10
+ end
11
+
12
+ def self.adapter=(adapter)
13
+ @@adapter = adapter
14
+ end
15
+
16
+ def self.instances
17
+ @@instances ||= {}
18
+ end
19
+
20
+ def ~@()
21
+ @search = true
22
+
23
+ self
24
+ end
25
+
26
+ def derive(klass = @class, save = @save)
27
+ Builder.new(klass, @attrs, @traits, save)
28
+ end
29
+
30
+ def dup_as_builder
31
+ derive(@class, false)
32
+ end
33
+
34
+ def dup_as_creator
35
+ derive(@class, true)
36
+ end
37
+
38
+ def initialize(klass, attrs = nil, traits = nil, save = false, &block)
39
+ @class = klass
40
+ @attrs = attrs ? deep_copy(attrs) : []
41
+ @traits = traits ? Module.new { include traits } : Module.new
42
+ @save = save
43
+
44
+ extend @traits
45
+
46
+ instance_eval &block if block_given?
47
+ end
48
+
49
+ def generate
50
+ obj = initialize_object
51
+
52
+ obj = adapter.find(@class, obj) || obj if @search
53
+ save_object(obj) if @save
54
+
55
+ obj
56
+ end
57
+
58
+ def trait(name, &block)
59
+ @traits.class_eval do
60
+ define_method "__#{name}", &block
61
+
62
+ define_method name do |*args|
63
+ send "__#{name}", *args
64
+
65
+ self
66
+ end
67
+ end
68
+ end
69
+
70
+ def method_missing(name, *args, &block)
71
+ attr = (return_object = name.to_s =~ /!$/) ? name.to_s.chop : name
72
+
73
+ result = if respond_to?(attr)
74
+ send(attr, *args)
75
+ elsif settable?(attr)
76
+ set attr, *args, &block
77
+ else
78
+ raise Bricks::NoAttributeOrTrait, "Can't find `#{name}'."
79
+ end
80
+
81
+ if return_object
82
+ generate
83
+ else
84
+ result
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def subject
91
+ Builder.instances[@class] ||= @class.new
92
+ end
93
+
94
+ def adapter
95
+ Builder.adapter
96
+ end
97
+
98
+ def deep_copy(attrs)
99
+ attrs.inject([]) { |a, (k, v)|
100
+ a.tap { a << [k, Builder === v ? v.derive : v] }
101
+ }
102
+ end
103
+
104
+ def save_object(obj)
105
+ obj.save!
106
+ end
107
+
108
+ def initialize_object
109
+ obj = @class.new
110
+
111
+ @attrs.each { |(k, v)|
112
+ val = case v
113
+ when Proc
114
+ v.call *[obj].take([v.arity, 0].max)
115
+ when Builder, BuilderSet
116
+ v.generate
117
+ else
118
+ v
119
+ end
120
+
121
+ obj.send "#{k}=", val
122
+ }
123
+
124
+ obj
125
+ end
126
+
127
+ def settable?(name)
128
+ subject.respond_to?("#{name}=")
129
+ end
130
+
131
+ def set(name, val = nil, &block)
132
+ raise Bricks::BadSyntax, "Block and value given" if val && block_given?
133
+
134
+ pair = @attrs.assoc(name) || (@attrs << [name, nil]).last
135
+
136
+ if block_given?
137
+ pair[-1] = block
138
+ elsif val
139
+ pair[-1] = val
140
+ elsif adapter.association?(@class, name, :one)
141
+ pair[-1] = create(adapter.association(@class, name).klass)
142
+ elsif adapter.association?(@class, name, :many)
143
+ pair[-1] ||= BuilderSet.new(adapter.association(@class, name).klass)
144
+ else
145
+ raise Bricks::BadSyntax,
146
+ "No value or block given and not an association: #{name}."
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,40 @@
1
+ require 'bricks/dsl'
2
+
3
+ module Bricks
4
+ class BuilderSet
5
+ include Bricks::DSL
6
+
7
+ def build(klass)
8
+ (@builders << super).last
9
+ end
10
+
11
+ def build!(klass)
12
+ (@builders << super).last
13
+ end
14
+
15
+ def clear
16
+ @builders.clear
17
+ end
18
+
19
+ def create(klass)
20
+ (@builders << super).last
21
+ end
22
+
23
+ def create!(klass)
24
+ (@builders << super).last
25
+ end
26
+
27
+ def initialize(klass)
28
+ @class = klass
29
+ @builders = []
30
+ end
31
+
32
+ def method_missing(name, *args)
33
+ build(@class).send(name, *args)
34
+ end
35
+
36
+ def generate
37
+ @builders.map { |b| b.generate }
38
+ end
39
+ end
40
+ end
data/lib/bricks/dsl.rb ADDED
@@ -0,0 +1,23 @@
1
+ module Bricks
2
+ module DSL
3
+ def build(klass)
4
+ builder(klass).dup_as_builder
5
+ end
6
+
7
+ def build!(klass)
8
+ build(klass).generate
9
+ end
10
+
11
+ def create(klass)
12
+ builder(klass).dup_as_creator
13
+ end
14
+
15
+ def create!(klass)
16
+ create(klass).generate
17
+ end
18
+
19
+ def builder(klass)
20
+ Bricks.builders[klass]
21
+ end
22
+ end
23
+ end
data/lib/bricks.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'bricks/builder'
2
+ require 'bricks/dsl'
3
+
4
+ module Bricks
5
+ class << self
6
+ attr_writer :builders
7
+
8
+ def builders
9
+ @builders ||= BuilderHashSet.new
10
+ end
11
+ end
12
+
13
+ class NoAttributeOrTrait < StandardError; end
14
+ class BadSyntax < StandardError; end
15
+
16
+ class BuilderHashSet
17
+ def initialize(&block)
18
+ @builders = {}
19
+ end
20
+
21
+ def [](key)
22
+ if @builders[key]
23
+ @builders[key]
24
+ elsif Class === key
25
+ @builders[key] = if builder = @builders[key.superclass]
26
+ builder.derive(key)
27
+ else
28
+ builder(key)
29
+ end
30
+ end
31
+ end
32
+
33
+ def builder(klass, &block)
34
+ @builders[klass] = Bricks::Builder.new(klass, &block)
35
+ end
36
+ end
37
+ end
38
+
39
+ def Bricks(&block)
40
+ Bricks::builders = Bricks::BuilderHashSet.new
41
+
42
+ Bricks::builders.instance_eval(&block)
43
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,5 @@
1
+ if defined?(ActiveRecord)
2
+ require 'bricks/adapters/active_record'
3
+ else
4
+ Rails.logger.warn "No suitable Brick adapter found."
5
+ end
@@ -0,0 +1,13 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Bricks::Adapters::ActiveRecord do
4
+ subject { Bricks::Adapters::ActiveRecord.new }
5
+
6
+ it "gracefully handles a missing association" do
7
+ subject.association?(Reader, :birth_date, :one).should be_nil
8
+ end
9
+
10
+ it "gracefully handles a missing association of the given type" do
11
+ subject.association?(Article, :newspaper, :many).should be_nil
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Bricks::Builder do
4
+ before :all do
5
+ Bricks::Builder.adapter = Class.new {
6
+ def association(*args)
7
+ nil
8
+ end
9
+
10
+ alias_method :association?, :association
11
+ }.new
12
+
13
+ class Person
14
+ attr_accessor :name
15
+ end
16
+ end
17
+
18
+ it "fails if the model is missing the given attribute" do
19
+ lambda {
20
+ Bricks::Builder.new(Person).birth_date(Date.new(1978, 5, 3))
21
+ }.should raise_error(Bricks::NoAttributeOrTrait)
22
+ end
23
+
24
+ it "forbids passing a block and an initial value" do
25
+ lambda {
26
+ Bricks::Builder.new(Person).name("Jack") { "heh" }
27
+ }.should raise_error(Bricks::BadSyntax)
28
+ end
29
+
30
+ it "forbids passing no value or block to a non-association attribute" do
31
+ lambda {
32
+ Bricks::Builder.new(Person).name
33
+ }.should raise_error(Bricks::BadSyntax)
34
+ end
35
+
36
+ it "always generates a new object" do
37
+ b = Bricks::Builder.new(Person)
38
+
39
+ b.generate.object_id.should_not == b.generate.object_id
40
+ end
41
+ end
@@ -0,0 +1,189 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Bricks do
4
+ include Bricks::DSL
5
+
6
+ before :all do
7
+ Bricks::Builder.adapter = Bricks::Adapters::ActiveRecord.new
8
+
9
+ Bricks do
10
+ builder PrintMedium do
11
+ start_date Date.new(1900, 1, 1)
12
+ end
13
+
14
+ builder Newspaper do
15
+ name "The Daily Planet"
16
+
17
+ trait :daily_bugle do
18
+ name "The Daily Bugle"
19
+ end
20
+ end
21
+
22
+ builder Article do
23
+ author 'Jack Jupiter'
24
+ title 'a title'
25
+ body 'the body'
26
+ formatted_title { |obj| obj.title + " by " + obj.author }
27
+ deferred { Time.now }
28
+ newspaper
29
+
30
+ %w(Socrates Plato Aristotle).each { |n| readers.name(n) }
31
+
32
+ trait :in_english do
33
+ language "English"
34
+ end
35
+
36
+ trait :by_jove do
37
+ author "Jack Jupiter"
38
+ end
39
+
40
+ trait :maybe_bugle do
41
+ ~newspaper.daily_bugle
42
+ end
43
+
44
+ trait :on_the_bugle do
45
+ newspaper.daily_bugle
46
+ end
47
+
48
+ trait :with_alternative_readers do
49
+ readers.clear
50
+
51
+ %w(Tom Dick Harry).each { |n| readers.name(n) }
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ after do
58
+ Reader.delete_all
59
+ Article.delete_all
60
+ PrintMedium.delete_all
61
+ Newspaper.delete_all
62
+ end
63
+
64
+ it "#build returns the constructor" do
65
+ build(Article).should be_kind_of(Bricks::Builder)
66
+ end
67
+
68
+ it "#create returns the constructor" do
69
+ create(Article).should be_kind_of(Bricks::Builder)
70
+ end
71
+
72
+ it "initializes a model" do
73
+ a = build!(Article)
74
+
75
+ a.should be_instance_of(Article)
76
+ a.should be_new_record
77
+ end
78
+
79
+ it "creates a model" do
80
+ a = create!(Article)
81
+
82
+ a.should be_instance_of(Article)
83
+ a.should be_saved
84
+ end
85
+
86
+ describe "with simple fields" do
87
+ it "initializes model fields" do
88
+ a = build!(Article)
89
+
90
+ a.title.should == 'a title'
91
+ a.body.should == 'the body'
92
+ end
93
+
94
+ it "defers field initialization" do
95
+ time = Time.now
96
+ a = build!(Article)
97
+
98
+ a.deferred.should > time
99
+ end
100
+
101
+ it "uses the object being built in deferred initialization" do
102
+ build!(Article).formatted_title.should == "a title by Jack Jupiter"
103
+ end
104
+ end
105
+
106
+ describe "with traits" do
107
+ it "returns the builder after calling the trait" do
108
+ build(Article).in_english.should be_kind_of(Bricks::Builder)
109
+ end
110
+
111
+ it "#build returns the object if the trait is called with a bang" do
112
+ a = build(Article).in_english!
113
+
114
+ a.should be_kind_of(Article)
115
+ a.should be_new_record
116
+ end
117
+
118
+ it "#create creates the object if the trait is called with a bang" do
119
+ a = create(Article).in_english!
120
+
121
+ a.should be_kind_of(Article)
122
+ a.should be_saved
123
+ end
124
+
125
+ it "initializes the model fields" do
126
+ build(Article).in_english!.language.should == "English"
127
+ end
128
+
129
+ it "combines multiple traits" do
130
+ a = build(Article).in_english.by_jove!
131
+
132
+ a.language.should == "English"
133
+ a.author.should == "Jack Jupiter"
134
+ end
135
+ end
136
+
137
+ describe "with a many-to-one association" do
138
+ it "initializes an association with the default values" do
139
+ build!(Article).newspaper.name.should == 'The Daily Planet'
140
+ end
141
+
142
+ it "overrides the association" do
143
+ build(Article).on_the_bugle!.newspaper.name.
144
+ should == 'The Daily Bugle'
145
+ end
146
+
147
+ it "possibly looks for an existing record" do
148
+ n = create(Newspaper).daily_bugle!
149
+ a = create(Article).maybe_bugle!
150
+
151
+ a.newspaper.should == n
152
+ end
153
+
154
+ it "possibly looks for an existing record (and finds none)" do
155
+ a = create(Article).maybe_bugle!
156
+
157
+ a.newspaper.should_not be_new_record
158
+ a.newspaper.name.should == "The Daily Bugle"
159
+ end
160
+ end
161
+
162
+ describe "with a one-to-many association" do
163
+ it "initializes an association with the default values" do
164
+ build!(Article).readers.map { |r|
165
+ r.name
166
+ }.should == %w(Socrates Plato Aristotle)
167
+ end
168
+
169
+ it "overrides the association" do
170
+ build(Article).with_alternative_readers!.readers.map { |r|
171
+ r.name
172
+ }.should == %w(Tom Dick Harry)
173
+ end
174
+ end
175
+
176
+ describe "builder inheritance" do
177
+ it "uses the parent's builder if the model has none" do
178
+ mag = build!(Magazine)
179
+
180
+ mag.should be_a(Magazine)
181
+ mag.start_date.should == Date.new(1900, 1, 1)
182
+ end
183
+
184
+ it "creates a builder for models that don't have one" do
185
+ build!(Reader).should be_a(Reader)
186
+ end
187
+ end
188
+ end
189
+
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'bricks'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,52 @@
1
+ require 'active_support/core_ext/class/attribute_accessors'
2
+ require 'active_support/core_ext/module/delegation'
3
+ require 'benchmark'
4
+ require 'bricks/adapters/active_record'
5
+
6
+ ActiveRecord::Base.establish_connection(
7
+ :adapter => "sqlite3",
8
+ :database => ":memory:"
9
+ )
10
+
11
+ ActiveRecord::Schema.define(:version => 20110608204150) do
12
+ create_table "articles", :force => true do |t|
13
+ t.string "author"
14
+ t.string "body"
15
+ t.datetime "deferred"
16
+ t.string "formatted_title"
17
+ t.string "language"
18
+ t.integer "newspaper_id"
19
+ t.string "title"
20
+ end
21
+
22
+ create_table "newspapers", :force => true do |t|
23
+ t.string "name"
24
+ end
25
+
26
+ create_table "print_media", :force => true do |t|
27
+ t.date "start_date"
28
+ t.string "type"
29
+ end
30
+
31
+ create_table "readers", :force => true do |t|
32
+ t.integer "article_id"
33
+ t.string "name"
34
+ end
35
+ end
36
+
37
+ class Article < ActiveRecord::Base
38
+ belongs_to :newspaper
39
+ has_many :readers
40
+
41
+ def saved?
42
+ ! new_record?
43
+ end
44
+ end
45
+
46
+ class PrintMedium < ActiveRecord::Base; end
47
+
48
+ class Magazine < PrintMedium; end
49
+
50
+ class Newspaper < ActiveRecord::Base; end
51
+
52
+ class Reader < ActiveRecord::Base; end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bricks
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 0
10
+ version: 0.0.0
11
+ platform: ruby
12
+ authors:
13
+ - David Leal
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-16 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ prerelease: false
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 2
31
+ - 0
32
+ version: "2.0"
33
+ name: rspec
34
+ requirement: *id001
35
+ type: :development
36
+ - !ruby/object:Gem::Dependency
37
+ prerelease: false
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 23
44
+ segments:
45
+ - 1
46
+ - 0
47
+ - 0
48
+ version: 1.0.0
49
+ name: bundler
50
+ requirement: *id002
51
+ type: :development
52
+ - !ruby/object:Gem::Dependency
53
+ prerelease: false
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 11
60
+ segments:
61
+ - 1
62
+ - 6
63
+ - 2
64
+ version: 1.6.2
65
+ name: jeweler
66
+ requirement: *id003
67
+ type: :development
68
+ - !ruby/object:Gem::Dependency
69
+ prerelease: false
70
+ version_requirements: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ name: rcov
80
+ requirement: *id004
81
+ type: :development
82
+ description:
83
+ email: david@mojotech.com
84
+ executables: []
85
+
86
+ extensions: []
87
+
88
+ extra_rdoc_files:
89
+ - LICENSE.txt
90
+ - README.md
91
+ files:
92
+ - .document
93
+ - .rspec
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - VERSION
99
+ - bricks.gemspec
100
+ - lib/bricks.rb
101
+ - lib/bricks/adapters/active_record.rb
102
+ - lib/bricks/builder.rb
103
+ - lib/bricks/builder_set.rb
104
+ - lib/bricks/dsl.rb
105
+ - rails/init.rb
106
+ - spec/bricks/adapters/active_record_spec.rb
107
+ - spec/bricks/builder_spec.rb
108
+ - spec/bricks_spec.rb
109
+ - spec/spec_helper.rb
110
+ - spec/support/active_record.rb
111
+ has_rdoc: true
112
+ homepage: http://github.com/mojotech/bricks
113
+ licenses:
114
+ - MIT
115
+ post_install_message:
116
+ rdoc_options: []
117
+
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ hash: 3
126
+ segments:
127
+ - 0
128
+ version: "0"
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ hash: 3
135
+ segments:
136
+ - 0
137
+ version: "0"
138
+ requirements: []
139
+
140
+ rubyforge_project:
141
+ rubygems_version: 1.3.7
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: Hybrid object builder/factory.
145
+ test_files: []
146
+