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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: badad59df7fa705f88cd6c2a46528b6e2c7ba99a
4
- data.tar.gz: 3854a69631e6c472fbe0785ab8e7b42eacff7d5d
3
+ metadata.gz: 755f69f2759cbef81f648647073101f1e832d18e
4
+ data.tar.gz: 918e0c1720b2a2349e1430d39bb1be546cbde95d
5
5
  SHA512:
6
- metadata.gz: 075df025384ee090201b909a2c36104df271279362df11ab132ab65215acf2063a65759ebe6c30a2320a30b1b7eb6a80d502532b4341b5e8be5ab6e4ca3b4599
7
- data.tar.gz: f07b7c319e6b5af8e52519f6f247779266b113f06ecdafc2900a40cd273b1841c362b0c5ab47c6d1644c970f805b785315f32d8cdbd03bc418de7d0abd9eb17b
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
- Something like this:
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
- # migration
7
+ == Scenario
9
8
 
10
- class CreatePlayers < ActiveRecord::Migration
11
- def change
12
- create_table :players do |t|
13
- t.string :name
14
- t.integer :age, default: 18
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
- # model
17
+ class Topic < ActiveRecord::Base
18
+ def map_all_names
19
+ to_a.map &:name
20
+ end
21
+ end
20
22
 
21
- class Player < ActiveRecord::Base
22
- validates :name, :age, presence: true
23
- validates :age, numericality: true
23
+ We can easily improve this through an abstraction:
24
+
25
+ class User < ActiveRecord::Base
26
+ include NameUtils
24
27
  end
25
28
 
26
- Can be done at runtime:
27
-
28
- options = {
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
- ModelBuilder.build 'Player', options
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
- After all, you can drop tables with:
41
+ Nice. Now we have a reusable abstraction, that could even fit in a gem to be applied at another projects.
42
42
 
43
- ModelBuilder.clean
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
- Simple classes are welcome too:
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
- ModelBuilder::ClassBuilder.build 'Duck', superclass: 'Animal', accessors: %w(age size)
56
+ Now your 'MappableName' model is able to receive NameUtils tests:
48
57
 
49
- If you want your object to quack like active record, see this:
58
+ MappableName.map_all_names
50
59
 
51
- * https://github.com/makandra/active_type
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 name.constantize if Object.const_defined? name
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
@@ -1,3 +1,3 @@
1
1
  module ModelBuilder
2
- VERSION = '1.0.2'
2
+ VERSION = '1.1.0'
3
3
  end
data/lib/model_builder.rb CHANGED
@@ -62,6 +62,8 @@ module ModelBuilder
62
62
 
63
63
  def self.clean
64
64
  list.map {|c| drop_table c }
65
+ @@dynamic_models = []
66
+ ClassBuilder.clean
65
67
  end
66
68
 
67
69
  def self.list
@@ -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) { name.constantize }
8
+ let(:options) { { superclass: Array, includes: [Spec::Support::DummyModule], accessors: %w(a1 a2) } }
9
+ let(:constant) { Object.const_get name }
9
10
 
10
- subject { builder.build name, options }
11
+ before { @build_result = builder.build name, options }
11
12
 
12
- it { is_expected.to eq constant }
13
- it { expect(builder.list).to include constant }
13
+ after { ModelBuilder::ClassBuilder.clean }
14
14
 
15
- context 'accessors validations' do
15
+ describe '.build' do
16
16
 
17
- before { subject }
17
+ subject { @build_result }
18
18
 
19
- it { expect(constant.new).to respond_to :a1 }
20
- it { expect(constant.new).to respond_to :a2 }
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
- it { expect(constant.new).to respond_to :a1= }
24
- it { expect(constant.new).to respond_to :a2= }
25
- it { expect(constant.new).to_not respond_to :a3= }
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
- subject { builder.build name, options }
30
+ before { @build_result = builder.build name, options }
29
31
 
30
- it { is_expected.to eq constant }
31
- it { expect(builder.list).to include constant }
32
+ after { ModelBuilder.clean }
32
33
 
33
- context 'options validations' do
34
+ describe '.build' do
34
35
 
35
- before { subject }
36
+ subject { @build_result }
36
37
 
37
- subject(:instance) { constant.new }
38
+ it { is_expected.to eq constant }
39
+ it { expect(builder.list).to include constant }
38
40
 
39
- context 'attributes validations' do
41
+ context 'options validations' do
40
42
 
41
- it { is_expected.to respond_to :name }
42
- it { is_expected.to respond_to :age }
43
+ before { subject }
43
44
 
44
- it { is_expected.to respond_to :name= }
45
- it { is_expected.to respond_to :age= }
45
+ subject(:instance) { constant.new }
46
46
 
47
- it { expect(instance.age).to eq default_age }
47
+ context 'includes validations' do
48
48
 
49
- end
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
- context 'validations validations' do
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
- it { expect(instance.valid?).to be false }
59
- it { expect(instance.errors.messages.keys).to include :name }
60
- it { expect(instance.errors.messages.keys).to include :age }
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
- after(:all) { ModelBuilder.clean }
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
@@ -0,0 +1,11 @@
1
+ require 'model_builder'
2
+
3
+ module Spec
4
+ module Support
5
+ module DummyModule
6
+
7
+ def dummy_method; :dummy_return; end
8
+
9
+ end
10
+ end
11
+ 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.2
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-26 00:00:00.000000000 Z
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