model-builder 1.0.2 → 1.1.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.
- checksums.yaml +4 -4
- data/README.rdoc +41 -32
- data/lib/model_builder/class_builder.rb +20 -5
- data/lib/model_builder/version.rb +1 -1
- data/lib/model_builder.rb +2 -0
- data/spec/examples/name_utils_spec.rb +39 -0
- data/spec/lib/model_builder/class_builder_spec.rb +45 -13
- data/spec/lib/model_builder_spec.rb +49 -21
- data/spec/support/dummy_module.rb +11 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 755f69f2759cbef81f648647073101f1e832d18e
|
4
|
+
data.tar.gz: 918e0c1720b2a2349e1430d39bb1be546cbde95d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c55813b933ad427308bc20e31b61b0ce6a9146795df9ec7908c89e8cc5bed85de58759828d22e22dca5fd1cf57bd8549a3808ce4dc3b5d8b115bed75ad2f9a31
|
7
|
+
data.tar.gz: 47c71a7ef9a882b0cee79ce1d7ba4484c0d486cc08c37f8d7da8de0b2c84dac8972c9f09ebf32a55651f44aef9e34f96d674547d4b779e301701ba1ae7e9f218
|
data/README.rdoc
CHANGED
@@ -1,51 +1,60 @@
|
|
1
1
|
= Model Builder
|
2
2
|
|
3
3
|
Build active record models on the fly.
|
4
|
-
Handy during tests creation, when the development is not related to business intelligence (and should not reference it).
|
5
4
|
|
6
|
-
|
5
|
+
Handy when using TDD to develop a reusable component, that is not strictly related to the business intelligence of a project (and should not reference it).
|
7
6
|
|
8
|
-
|
7
|
+
== Scenario
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
9
|
+
Consider this case of code duplication:
|
10
|
+
|
11
|
+
class User < ActiveRecord::Base
|
12
|
+
def map_all_names
|
13
|
+
to_a.map &:name
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
19
|
-
|
17
|
+
class Topic < ActiveRecord::Base
|
18
|
+
def map_all_names
|
19
|
+
to_a.map &:name
|
20
|
+
end
|
21
|
+
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
We can easily improve this through an abstraction:
|
24
|
+
|
25
|
+
class User < ActiveRecord::Base
|
26
|
+
include NameUtils
|
24
27
|
end
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
attributes: {
|
30
|
-
name: :string,
|
31
|
-
age: { type: :integer, default: 18 }
|
32
|
-
},
|
33
|
-
validates: [
|
34
|
-
[:name, :age, presence: true],
|
35
|
-
[:age, numericality: true]
|
36
|
-
]
|
37
|
-
}
|
29
|
+
class Topic < ActiveRecord::Base
|
30
|
+
include NameUtils
|
31
|
+
end
|
38
32
|
|
39
|
-
|
33
|
+
module NameUtils extend ActiveSupport::Concern
|
34
|
+
included do
|
35
|
+
define_singleton_method :map_all_names do
|
36
|
+
all.map &:name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
40
|
|
41
|
-
|
41
|
+
Nice. Now we have a reusable abstraction, that could even fit in a gem to be applied at another projects.
|
42
42
|
|
43
|
-
|
43
|
+
But thinking about the gem context, we will not be able to use user or topic models at our tests.
|
44
|
+
They belong to our business intelligence. So, how to test it?
|
44
45
|
|
45
|
-
|
46
|
+
Here comes the necessity of don't rely on our business intelligence at abstractions tests.
|
47
|
+
|
48
|
+
== Solution
|
49
|
+
|
50
|
+
ModelBuilder.build 'MappableName', {
|
51
|
+
includes: NameUtils,
|
52
|
+
attributes: { name: :string },
|
53
|
+
validates: [:name, presence: true]
|
54
|
+
}
|
46
55
|
|
47
|
-
|
56
|
+
Now your 'MappableName' model is able to receive NameUtils tests:
|
48
57
|
|
49
|
-
|
58
|
+
MappableName.map_all_names
|
50
59
|
|
51
|
-
|
60
|
+
See the full test example here[https://github.com/r4z3c/model-builder/blob/master/spec/examples/name_utils_spec.rb].
|
@@ -4,12 +4,13 @@ module ModelBuilder
|
|
4
4
|
@@dynamic_classes ||= []
|
5
5
|
|
6
6
|
def self.build(name, opts={})
|
7
|
-
return
|
7
|
+
return Object.const_get name if Object.const_defined? name
|
8
8
|
|
9
9
|
klass = get_class_from_options opts
|
10
10
|
Object.const_set name, klass
|
11
11
|
|
12
12
|
add_class klass
|
13
|
+
include_modules klass, opts[:includes]
|
13
14
|
create_accessors klass, opts[:accessors]
|
14
15
|
|
15
16
|
klass
|
@@ -25,14 +26,19 @@ module ModelBuilder
|
|
25
26
|
superclass.is_a?(String) ? superclass.constantize : superclass
|
26
27
|
end
|
27
28
|
|
28
|
-
def self.list
|
29
|
-
@@dynamic_classes
|
30
|
-
end
|
31
|
-
|
32
29
|
def self.add_class(klass)
|
33
30
|
@@dynamic_classes << klass
|
34
31
|
end
|
35
32
|
|
33
|
+
def self.include_modules(klass, modules)
|
34
|
+
return if modules.nil?
|
35
|
+
modules = [modules] unless modules.is_a? Array
|
36
|
+
modules.each do |m|
|
37
|
+
m_const = m.kind_of?(String) ? Object.const_get(m) : m
|
38
|
+
klass.send :include, m_const
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
36
42
|
def self.create_accessors(klass, accessors=[])
|
37
43
|
return if accessors.nil?
|
38
44
|
accessors = [accessors] unless accessors.is_a? Array
|
@@ -43,5 +49,14 @@ module ModelBuilder
|
|
43
49
|
klass.send 'attr_accessor', accessor unless accessor.nil?
|
44
50
|
end
|
45
51
|
|
52
|
+
def self.clean
|
53
|
+
list.map {|c| Object.send :remove_const, c.to_s }
|
54
|
+
@@dynamic_classes = []
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.list
|
58
|
+
@@dynamic_classes
|
59
|
+
end
|
60
|
+
|
46
61
|
end
|
47
62
|
end
|
data/lib/model_builder.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/database_connection'
|
3
|
+
|
4
|
+
Spec::Support::DatabaseConnection.establish_sqlite_connection
|
5
|
+
|
6
|
+
module NameUtils extend ActiveSupport::Concern
|
7
|
+
included do
|
8
|
+
define_singleton_method :map_all_names do
|
9
|
+
all.map &:name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe NameUtils do
|
15
|
+
|
16
|
+
before do
|
17
|
+
create_mappable_names_model
|
18
|
+
populate_mappable_names_table
|
19
|
+
end
|
20
|
+
|
21
|
+
after { ModelBuilder.clean }
|
22
|
+
|
23
|
+
subject { MappableName.map_all_names }
|
24
|
+
|
25
|
+
it { is_expected.to match %w(Name0 Name1 Name2) }
|
26
|
+
|
27
|
+
def create_mappable_names_model
|
28
|
+
ModelBuilder.build 'MappableName', {
|
29
|
+
includes: NameUtils,
|
30
|
+
attributes: { name: :string },
|
31
|
+
validates: [:name, presence: true]
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def populate_mappable_names_table
|
36
|
+
3.times {|i| MappableName.create! name: "Name#{i}" }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -1,28 +1,60 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'support/dummy_module'
|
2
3
|
|
3
4
|
describe ModelBuilder::ClassBuilder do
|
4
5
|
|
5
6
|
let(:builder) { ModelBuilder::ClassBuilder }
|
6
7
|
let(:name) { 'ClassBuilderTest' }
|
7
|
-
let(:options) { { superclass: Array, accessors: %w(a1 a2) } }
|
8
|
-
let(:constant) {
|
8
|
+
let(:options) { { superclass: Array, includes: [Spec::Support::DummyModule], accessors: %w(a1 a2) } }
|
9
|
+
let(:constant) { Object.const_get name }
|
9
10
|
|
10
|
-
|
11
|
+
before { @build_result = builder.build name, options }
|
11
12
|
|
12
|
-
|
13
|
-
it { expect(builder.list).to include constant }
|
13
|
+
after { ModelBuilder::ClassBuilder.clean }
|
14
14
|
|
15
|
-
|
15
|
+
describe '.build' do
|
16
16
|
|
17
|
-
|
17
|
+
subject { @build_result }
|
18
18
|
|
19
|
-
it {
|
20
|
-
it { expect(
|
21
|
-
it { expect(constant.new).to_not respond_to :a3 }
|
19
|
+
it { is_expected.to eq constant }
|
20
|
+
it { expect(builder.list).to include constant }
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
context 'includes validations' do
|
23
|
+
|
24
|
+
before { subject }
|
25
|
+
|
26
|
+
it { expect(constant.new).to respond_to :dummy_method }
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'accessors validations' do
|
31
|
+
|
32
|
+
before { subject }
|
33
|
+
|
34
|
+
it { expect(constant.new).to respond_to :a1 }
|
35
|
+
it { expect(constant.new).to respond_to :a2 }
|
36
|
+
it { expect(constant.new).to_not respond_to :a3 }
|
37
|
+
|
38
|
+
it { expect(constant.new).to respond_to :a1= }
|
39
|
+
it { expect(constant.new).to respond_to :a2= }
|
40
|
+
it { expect(constant.new).to_not respond_to :a3= }
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.clean' do
|
47
|
+
|
48
|
+
it { expect{constant}.to_not raise_error }
|
49
|
+
|
50
|
+
context 'after build' do
|
51
|
+
|
52
|
+
before { builder.clean }
|
53
|
+
|
54
|
+
it { expect{constant}.to raise_error(NameError, "uninitialized constant #{name}") }
|
55
|
+
it { expect(builder.list.empty?).to be true }
|
56
|
+
|
57
|
+
end
|
26
58
|
|
27
59
|
end
|
28
60
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'support/database_connection'
|
3
|
+
require 'support/dummy_module'
|
3
4
|
|
4
5
|
Spec::Support::DatabaseConnection.establish_sqlite_connection
|
5
6
|
|
@@ -11,6 +12,7 @@ describe ModelBuilder do
|
|
11
12
|
let(:default_age) { 17 }
|
12
13
|
let(:options) do
|
13
14
|
{
|
15
|
+
includes: [Spec::Support::DummyModule],
|
14
16
|
attributes: {
|
15
17
|
name: :string,
|
16
18
|
age: {
|
@@ -25,44 +27,70 @@ describe ModelBuilder do
|
|
25
27
|
}
|
26
28
|
end
|
27
29
|
|
28
|
-
|
30
|
+
before { @build_result = builder.build name, options }
|
29
31
|
|
30
|
-
|
31
|
-
it { expect(builder.list).to include constant }
|
32
|
+
after { ModelBuilder.clean }
|
32
33
|
|
33
|
-
|
34
|
+
describe '.build' do
|
34
35
|
|
35
|
-
|
36
|
+
subject { @build_result }
|
36
37
|
|
37
|
-
|
38
|
+
it { is_expected.to eq constant }
|
39
|
+
it { expect(builder.list).to include constant }
|
38
40
|
|
39
|
-
context '
|
41
|
+
context 'options validations' do
|
40
42
|
|
41
|
-
|
42
|
-
it { is_expected.to respond_to :age }
|
43
|
+
before { subject }
|
43
44
|
|
44
|
-
|
45
|
-
it { is_expected.to respond_to :age= }
|
45
|
+
subject(:instance) { constant.new }
|
46
46
|
|
47
|
-
|
47
|
+
context 'includes validations' do
|
48
48
|
|
49
|
-
|
49
|
+
it { expect(instance).to respond_to :dummy_method }
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'attributes validations' do
|
54
|
+
|
55
|
+
it { is_expected.to respond_to :name }
|
56
|
+
it { is_expected.to respond_to :age }
|
50
57
|
|
51
|
-
|
58
|
+
it { is_expected.to respond_to :name= }
|
59
|
+
it { is_expected.to respond_to :age= }
|
60
|
+
|
61
|
+
it { expect(instance.age).to eq default_age }
|
52
62
|
|
53
|
-
before do
|
54
|
-
instance.age = 'noop'
|
55
|
-
instance.valid?
|
56
63
|
end
|
57
64
|
|
58
|
-
|
59
|
-
|
60
|
-
|
65
|
+
context 'validations validations' do
|
66
|
+
|
67
|
+
before do
|
68
|
+
instance.age = 'noop'
|
69
|
+
instance.valid?
|
70
|
+
end
|
71
|
+
|
72
|
+
it { expect(instance.valid?).to be false }
|
73
|
+
it { expect(instance.errors.messages.keys).to include :name }
|
74
|
+
it { expect(instance.errors.messages.keys).to include :age }
|
75
|
+
|
76
|
+
end
|
61
77
|
|
62
78
|
end
|
63
79
|
|
64
80
|
end
|
65
81
|
|
66
|
-
|
82
|
+
describe '.clean' do
|
83
|
+
|
84
|
+
it { expect(constant.all.count).to eq 0 }
|
85
|
+
|
86
|
+
context 'after build' do
|
87
|
+
|
88
|
+
before { builder.clean }
|
89
|
+
it { expect{constant}.to raise_error(NameError, "uninitialized constant #{name}") }
|
90
|
+
it { expect(builder.list.empty?).to be true }
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
67
95
|
|
68
96
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: model-builder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- r4z3c
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -97,10 +97,12 @@ files:
|
|
97
97
|
- lib/model_builder/class_builder.rb
|
98
98
|
- lib/model_builder/version.rb
|
99
99
|
- model_builder.gemspec
|
100
|
+
- spec/examples/name_utils_spec.rb
|
100
101
|
- spec/lib/model_builder/class_builder_spec.rb
|
101
102
|
- spec/lib/model_builder_spec.rb
|
102
103
|
- spec/spec_helper.rb
|
103
104
|
- spec/support/database_connection.rb
|
105
|
+
- spec/support/dummy_module.rb
|
104
106
|
homepage: https://github.com/r4z3c/model-builder.git
|
105
107
|
licenses:
|
106
108
|
- MIT
|
@@ -126,7 +128,9 @@ signing_key:
|
|
126
128
|
specification_version: 4
|
127
129
|
summary: Active record runtime model builder
|
128
130
|
test_files:
|
131
|
+
- spec/examples/name_utils_spec.rb
|
129
132
|
- spec/lib/model_builder/class_builder_spec.rb
|
130
133
|
- spec/lib/model_builder_spec.rb
|
131
134
|
- spec/spec_helper.rb
|
132
135
|
- spec/support/database_connection.rb
|
136
|
+
- spec/support/dummy_module.rb
|