bricks 0.0.0

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/.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
+