composition 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 87b706985a087f9b5576f1cd7d4b59e21ce83636
4
+ data.tar.gz: 620c33e2f2c1bdb8502334bcd63768a79caa3aa6
5
+ SHA512:
6
+ metadata.gz: f72b8800a0ee152194ee3e57aef74c9e076e11edb4526bcc6a370ac8e61a990a31585a25a556c2ece4029f472d54fd17cbe7313f69381cafadb87110d94ddbb3
7
+ data.tar.gz: 8979cd300b8dc32f81efb27aadd9fdee6e186b4671185ff94ee722578b69581f2e7242c77adc4ac0b99c47960f48b7e2a65824d42a623a174dfbaeddd113d24f
@@ -0,0 +1,9 @@
1
+ require 'composition/reflection'
2
+ require 'composition/macros/compose'
3
+ require 'composition/macros/composed_from'
4
+ require 'composition/compositions/composition'
5
+ require 'composition/compositions/compose'
6
+ require 'composition/compositions/composed_from'
7
+ require 'composition/builders/compose'
8
+ require 'composition/builders/composed_from'
9
+ require 'composition/base'
@@ -0,0 +1,7 @@
1
+ module Composition
2
+ class Base
3
+ include ActiveModel::Model
4
+ include Composition::Reflection
5
+ include Composition::Macros::ComposedFrom
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ module Composition
2
+ module Builders
3
+ class Compose
4
+ attr_reader :object
5
+ delegate :_composition_reflections, to: :object
6
+
7
+ def initialize(object)
8
+ @object = object
9
+ end
10
+
11
+ def def_composition_methods
12
+ _composition_reflections.each_value do |composition|
13
+ def_composition_getter(composition)
14
+ def_composition_setter(composition)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def def_composition_getter(composition)
21
+ define_method(composition.name) { composition.getter(self) }
22
+ end
23
+
24
+ def def_composition_setter(composition)
25
+ define_method("#{composition.name}=") { |setter_value| composition.setter(self, setter_value) }
26
+ end
27
+
28
+ def define_method(method_name, &block)
29
+ @object.class.send(:define_method, method_name, &block)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ module Composition
2
+ module Builders
3
+ class ComposedFrom
4
+ attr_reader :object
5
+ delegate :_composition_reflections, to: :object
6
+
7
+ def initialize(object)
8
+ @object = object
9
+ end
10
+
11
+ # TODO: add documentation
12
+ def def_composition_setters
13
+ _composition_reflections.each_value do |composition|
14
+ composition.aliases.each do |attr|
15
+ def_attr_reader(attr)
16
+ define_method("#{attr}=") { |setter_value| composition.setter(self, attr, setter_value) }
17
+ define_method(:attributes) { composition.attributes(self) }
18
+ define_method(:to_h) { composition.attributes(self) }
19
+ end
20
+ def_attr_accessor(composition.name)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def define_method(method_name, &block)
27
+ @object.class.send(:define_method, method_name, &block)
28
+ end
29
+
30
+ def def_attr_accessor(*attr)
31
+ @object.class.send(:attr_accessor, *attr)
32
+ end
33
+
34
+ def def_attr_reader(*attr)
35
+ @object.class.send(:attr_reader, *attr)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,84 @@
1
+ module Composition
2
+ module Compositions
3
+ class Compose < ::Composition::Compositions::Composition
4
+
5
+ # For a composition defined like:
6
+ #
7
+ # class User < ActiveRecord::Base
8
+ # compose :credit_card,
9
+ # mapping: {
10
+ # credit_card_name: :name,
11
+ # credit_card_brand: :brand
12
+ # }
13
+ # end
14
+ #
15
+ # The getter method will be in charge of implementing @user.credit_card.
16
+ #
17
+ # It is responsible for instantiating a new CreditCard object with the attributes
18
+ # from the de-normalized columns in User, and then return it.
19
+ def getter(ar)
20
+ attributes = attributes(ar)
21
+ klass.new(attributes.merge(composed_from.name => ar)).tap(&:valid?) unless all_blank?(attributes)
22
+ end
23
+
24
+ # For a composition defined like:
25
+ #
26
+ # class User < ActiveRecord::Base
27
+ # compose :credit_card,
28
+ # mapping: {
29
+ # credit_card_name: :name,
30
+ # credit_card_brand: :brand
31
+ # }
32
+ # end
33
+ #
34
+ # The setter method will be in charge of implementing @user.credit_card=.
35
+ #
36
+ # setter_value can be either a credit_card instance or a hash of attributes
37
+ # and the setter will only set the @credit_card attributes that are included
38
+ # in the hash. This means that if a credit_card attribute is not given in the hash
39
+ # then we'll set it with the value from the @user de-normalized column. The reason
40
+ # behind this is to imitate how ActiveRecord assign_attributes method works.
41
+ def setter(ar, setter_value)
42
+ nil_columns(ar) and return if setter_value.nil?
43
+ attributes = setter_value.to_h.with_indifferent_access
44
+
45
+ mapping.each do |actual_column, composed_alias|
46
+ ar.send("#{actual_column}=", attributes[composed_alias]) if attributes.key?(composed_alias)
47
+ end
48
+
49
+ setter_value
50
+ end
51
+
52
+ def mapping
53
+ @options[:mapping]
54
+ end
55
+
56
+ def actual_column_for(aliased_attribute)
57
+ mapping.key(aliased_attribute)
58
+ end
59
+
60
+ private
61
+
62
+ # Returns the hash of attributes for instantiating the composition defined for a given
63
+ # class.
64
+ def attributes(ar)
65
+ mapping.each_with_object({}) do |(actual_column, composed_alias), memo|
66
+ memo[composed_alias] = ar.send(actual_column)
67
+ end
68
+ end
69
+
70
+ def all_blank?(attributes = {})
71
+ attributes.all? { |_, value| value.blank? }
72
+ end
73
+
74
+ def nil_columns(ar)
75
+ mapping.each { |actual_column, _| ar.send("#{actual_column}=", nil) }
76
+ end
77
+
78
+ # TODO: Add descriptive error if find returns nil. "composed_from is missing"
79
+ def composed_from
80
+ klass._composition_reflections.find { |_, composition| composition.class_name == inverse_of }.last
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,72 @@
1
+ module Composition
2
+ module Compositions
3
+ class ComposedFrom < ::Composition::Compositions::Composition
4
+ delegate :mapping, to: :inverse_of_composition
5
+
6
+ # For a composition defined like:
7
+ #
8
+ # class User < ActiveRecord::Base
9
+ # compose :credit_card,
10
+ # mapping: {
11
+ # credit_card_name: :name,
12
+ # credit_card_brand: :brand
13
+ # }
14
+ # end
15
+ #
16
+ # class CreditCard < Composition::Base
17
+ # composed_from :user
18
+ # end
19
+ #
20
+ # The setter method will be in charge of implementing @credit_card.name= and @credit_card.brand=.
21
+ #
22
+ # If calling @credit_card.name= it will take care of updating the @name instance variable in
23
+ # @credit_card, but also will take care of keeping @user.credit_card_name in sync with it.
24
+ def setter(obj, setter_attr, setter_value)
25
+ set_instance_variable(obj, setter_attr, setter_value)
26
+ set_parent_attribute(obj, setter_attr, setter_value)
27
+ setter_value
28
+ end
29
+
30
+ #TODO: Add documentation
31
+ def attributes(obj)
32
+ aliases.each_with_object({}) do |attr, memo|
33
+ value = obj.send(attr)
34
+ if value.respond_to?(:attributes)
35
+ memo[attr] = value.send(:attributes)
36
+ else
37
+ memo[attr] = value
38
+ end
39
+ end
40
+ end
41
+
42
+ def aliases
43
+ mapping.values
44
+ end
45
+
46
+ private
47
+
48
+ def set_instance_variable(obj, setter_attr, setter_value)
49
+ obj.instance_variable_set("@#{setter_attr}", setter_value)
50
+ end
51
+
52
+ def set_parent_attribute(obj, setter_attr, setter_value)
53
+ parent = parent_for(obj)
54
+
55
+ if parent
56
+ parent_composition = parent._composition_reflections[inverse_of]
57
+ parent.send("#{parent_composition.actual_column_for(setter_attr)}=", setter_value)
58
+ end
59
+ end
60
+
61
+ def inverse_of_composition
62
+ klass._composition_reflections[inverse_of]
63
+ end
64
+
65
+ # A composition class can have more than one reference, but only one parent should be not nil
66
+ # at the same time.
67
+ def parent_for(obj)
68
+ obj._composition_reflections.map { |_, composition| obj.send(composition.name).presence }.compact.first
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ module Composition
2
+ module Compositions
3
+ class Composition
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(name, options = {})
8
+ @name = name
9
+ @options = options
10
+ end
11
+
12
+ def class_name
13
+ @options[:class_name]
14
+ end
15
+
16
+ def klass
17
+ class_name.constantize
18
+ end
19
+
20
+ def inverse_of
21
+ @options[:inverse_of]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ module Composition
2
+ module Macros
3
+ module Compose
4
+ extend ActiveSupport::Concern
5
+
6
+ def method_missing(method_name, *args, &block)
7
+ if match_composition?(method_name)
8
+ Composition::Builders::Compose.new(self).def_composition_methods
9
+ send(method_name, *args, &block)
10
+ else
11
+ super
12
+ end
13
+ end
14
+
15
+ def respond_to?(method_name, include_private = false)
16
+ if match_composition?(method_name)
17
+ Composition::Builders::Compose.new(self).def_composition_methods
18
+ true
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def match_composition?(method_id)
27
+ composition_name = method_id.to_s.gsub(/=$/, '')
28
+ _composition_reflections.any? { |_, composition| composition_name == composition.name.to_s }
29
+ end
30
+
31
+ class_methods do
32
+ def compose(*args)
33
+ composed_attribute = args.shift
34
+ options = args.last || {}
35
+ options = {
36
+ composed_attribute: composed_attribute,
37
+ mapping: options[:mapping],
38
+ class_name: options[:class_name] || composed_attribute.to_s.camelize,
39
+ inverse_of: options[:inverse_of] || model_name.name
40
+ }
41
+ composition = Compositions::Compose.new(options[:composed_attribute], options)
42
+ add_composition_reflection(self, options[:class_name], composition)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ ActiveRecord::Base.send(:include, Composition::Macros::Compose)
@@ -0,0 +1,49 @@
1
+ module Composition
2
+ module Macros
3
+ module ComposedFrom
4
+ extend ActiveSupport::Concern
5
+
6
+ def method_missing(method_name, *args, &block)
7
+ if match_attribute?(method_name)
8
+ Composition::Builders::ComposedFrom.new(self).def_composition_setters
9
+ send(method_name, *args, &block)
10
+ else
11
+ super
12
+ end
13
+ end
14
+
15
+ def respond_to?(method_name, include_private = false)
16
+ if match_attribute?(method_name)
17
+ Composition::Builders::ComposedFrom.new(self).def_composition_setters
18
+ true
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def match_attribute?(method_id)
27
+ if method_id.to_s.match(/=$/)
28
+ attribute = method_id.to_s.gsub(/=$/, '')
29
+ reflection = _composition_reflections.first.try(:last)
30
+ reflection.aliases.include?(attribute.to_sym)
31
+ end
32
+ end
33
+
34
+ class_methods do
35
+ def composed_from(*args)
36
+ composed_from = args.shift
37
+ options = args.last || {}
38
+ options = {
39
+ composed_from: composed_from,
40
+ class_name: options[:class_name] || composed_from.to_s.camelize,
41
+ inverse_of: options[:inverse_of] || model_name.name
42
+ }
43
+ composition = Compositions::ComposedFrom.new(options[:composed_from], options)
44
+ add_composition_reflection(self, options[:inverse_of], composition)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,19 @@
1
+ module Composition
2
+ module Reflection
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :_composition_reflections, instance_writer: false
7
+ self._composition_reflections = {}.with_indifferent_access
8
+ end
9
+
10
+ class_methods do
11
+ def add_composition_reflection(obj, name, reflection)
12
+ new_reflection = { name => reflection }.with_indifferent_access
13
+ obj._composition_reflections = obj._composition_reflections.merge(new_reflection)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ ActiveRecord::Base.send(:include, Composition::Reflection)
@@ -0,0 +1,3 @@
1
+ module Composition
2
+ VERSION = '1.0.0.beta1'.freeze
3
+ end
@@ -0,0 +1,251 @@
1
+ describe Composition::Macros::Compose do
2
+
3
+ describe 'compose getter' do
4
+ context 'when there is at least 1 value in the composed object' do
5
+ let(:user) { User.new(credit_card_name: 'Jon Snow', credit_card_brand: 'Visa') }
6
+
7
+ before do
8
+ create_table(:users) do |t|
9
+ t.string :credit_card_name
10
+ t.string :credit_card_brand
11
+ end
12
+
13
+ spawn_model(:User) do
14
+ compose :credit_card,
15
+ mapping: {
16
+ credit_card_name: :name,
17
+ credit_card_brand: :brand
18
+ }
19
+ end
20
+
21
+ spawn_composition(:CreditCard) do
22
+ composed_from :user
23
+ end
24
+ end
25
+
26
+ it { expect(user.credit_card).to be_an_instance_of(CreditCard) }
27
+ it { expect(user.credit_card.name).to eq 'Jon Snow' }
28
+ it { expect(user.credit_card.brand).to eq 'Visa' }
29
+ it { expect(user.credit_card.user).to eq user }
30
+ end
31
+
32
+ context 'when every attribute is nil' do
33
+ let(:user) { User.new(credit_card_name: nil, credit_card_brand: nil) }
34
+
35
+ before do
36
+ create_table(:users) do |t|
37
+ t.string :credit_card_name
38
+ t.string :credit_card_brand
39
+ end
40
+
41
+ spawn_model(:User) do
42
+ compose :credit_card,
43
+ mapping: {
44
+ credit_card_name: :name,
45
+ credit_card_brand: :brand
46
+ }
47
+ end
48
+
49
+ spawn_composition(:CreditCard) do
50
+ composed_from :user
51
+ end
52
+ end
53
+
54
+ it { expect(user.credit_card).to be_nil }
55
+ end
56
+
57
+ context 'when using class_name' do
58
+ let(:user) { AdminUser.new(credit_card_name: 'Jon Snow', credit_card_brand: 'Visa') }
59
+
60
+ before do
61
+ create_table(:admin_users) do |t|
62
+ t.string :credit_card_name
63
+ t.string :credit_card_brand
64
+ end
65
+
66
+ spawn_model(:AdminUser) do
67
+ compose :credit_card,
68
+ mapping: {
69
+ credit_card_name: :name,
70
+ credit_card_brand: :brand
71
+ }, class_name: 'CCard'
72
+ end
73
+
74
+ spawn_composition(:CCard) do
75
+ composed_from :user, class_name: 'AdminUser'
76
+ end
77
+ end
78
+
79
+ it { expect(user.credit_card).to be_an_instance_of(CCard) }
80
+ it { expect(user.credit_card.name).to eq 'Jon Snow' }
81
+ it { expect(user.credit_card.brand).to eq 'Visa' }
82
+ it { expect(user.credit_card.user).to eq user }
83
+ end
84
+ end
85
+
86
+ describe 'compose setter' do
87
+ let(:user) { User.new(credit_card_name: 'Jon Snow', credit_card_brand: 'Visa') }
88
+
89
+ before do
90
+ create_table(:users) do |t|
91
+ t.string :credit_card_name
92
+ t.string :credit_card_brand
93
+ end
94
+
95
+ spawn_model(:User) do
96
+ compose :credit_card,
97
+ mapping: {
98
+ credit_card_name: :name,
99
+ credit_card_brand: :brand
100
+ }
101
+ end
102
+
103
+ spawn_composition(:CreditCard) do
104
+ composed_from :user
105
+ end
106
+ end
107
+
108
+ context 'when setting attributes separately' do
109
+ before do
110
+ user.credit_card.name = 'Arya Stark'
111
+ user.credit_card.brand = 'MasterCard'
112
+ end
113
+
114
+ it { expect(user.credit_card.name).to eq 'Arya Stark' }
115
+ it { expect(user.credit_card_name).to eq 'Arya Stark' }
116
+ it { expect(user.credit_card.brand).to eq 'MasterCard' }
117
+ it { expect(user.credit_card_brand).to eq 'MasterCard' }
118
+
119
+ context 'and saving' do
120
+ before { user.save! && user.reload }
121
+ it { expect(user.credit_card.name).to eq 'Arya Stark' }
122
+ it { expect(user.credit_card_name).to eq 'Arya Stark' }
123
+ it { expect(user.credit_card.brand).to eq 'MasterCard' }
124
+ it { expect(user.credit_card_brand).to eq 'MasterCard' }
125
+ end
126
+ end
127
+
128
+ context 'when setting attributes through assign_attributes' do
129
+ before do
130
+ user.update_attributes(credit_card: { name: 'Arya Stark', brand: 'MasterCard' })
131
+ end
132
+
133
+ it { expect(user.credit_card.name).to eq 'Arya Stark' }
134
+ it { expect(user.credit_card_name).to eq 'Arya Stark' }
135
+ it { expect(user.credit_card.brand).to eq 'MasterCard' }
136
+ it { expect(user.credit_card_brand).to eq 'MasterCard' }
137
+ end
138
+
139
+ context 'when setting attributes (partially) through assign_attributes' do
140
+ before do
141
+ user.update_attributes(credit_card: { brand: 'MasterCard' })
142
+ end
143
+
144
+ it { expect(user.credit_card.name).to eq 'Jon Snow' }
145
+ it { expect(user.credit_card_name).to eq 'Jon Snow' }
146
+ it { expect(user.credit_card.brand).to eq 'MasterCard' }
147
+ it { expect(user.credit_card_brand).to eq 'MasterCard' }
148
+ end
149
+
150
+ context 'when setting attributes using a new object' do
151
+ before do
152
+ user.credit_card = CreditCard.new(name: 'Arya Stark', brand: 'MasterCard')
153
+ end
154
+
155
+ it { expect(user.credit_card.name).to eq 'Arya Stark' }
156
+ it { expect(user.credit_card_name).to eq 'Arya Stark' }
157
+ it { expect(user.credit_card.brand).to eq 'MasterCard' }
158
+ it { expect(user.credit_card_brand).to eq 'MasterCard' }
159
+ end
160
+
161
+ context 'when setting attribute through the base class' do
162
+ before do
163
+ user.credit_card_name = 'Arya Stark'
164
+ user.credit_card_brand = 'MasterCard'
165
+ end
166
+
167
+ it { expect(user.credit_card.name).to eq 'Arya Stark' }
168
+ it { expect(user.credit_card_name).to eq 'Arya Stark' }
169
+ it { expect(user.credit_card.brand).to eq 'MasterCard' }
170
+ it { expect(user.credit_card_brand).to eq 'MasterCard' }
171
+ end
172
+
173
+ context 'when setting to nil using =' do
174
+ before { user.credit_card = nil }
175
+
176
+ it { expect(user.credit_card).to be_nil }
177
+ it { expect(user.credit_card_name).to be_nil }
178
+ it { expect(user.credit_card_brand).to be_nil }
179
+ end
180
+
181
+ context 'when setting to nil using {}' do
182
+ before { user.update_attributes(credit_card: nil) }
183
+
184
+ it { expect(user.credit_card).to be_nil }
185
+ it { expect(user.credit_card_name).to be_nil }
186
+ it { expect(user.credit_card_brand).to be_nil }
187
+ end
188
+ end
189
+
190
+ describe 'validations' do
191
+ let(:user) { User.new(credit_card_name: 'Jon Snow', credit_card_brand: 'Visa') }
192
+
193
+ before do
194
+ create_table(:users) do |t|
195
+ t.string :credit_card_name
196
+ t.string :credit_card_brand
197
+ end
198
+
199
+ spawn_model(:User) do
200
+ compose :credit_card,
201
+ mapping: {
202
+ credit_card_name: :name,
203
+ credit_card_brand: :brand
204
+ }
205
+ end
206
+
207
+ spawn_composition(:CreditCard) do
208
+ composed_from :user
209
+
210
+ validates :name, presence: true
211
+ end
212
+ end
213
+
214
+ context 'when credit_card is valid' do
215
+ it { expect(user.credit_card).to be_valid }
216
+ end
217
+
218
+ context 'when credit_card is not valid' do
219
+ before { user.credit_card.name = '' }
220
+
221
+ it { expect(user.credit_card).not_to be_valid }
222
+ end
223
+ end
224
+
225
+ describe '#attributes' do
226
+ let(:user) { User.new(credit_card_name: 'Jon Snow', credit_card_brand: 'Visa') }
227
+
228
+ before do
229
+ create_table(:users) do |t|
230
+ t.string :credit_card_name
231
+ t.string :credit_card_brand
232
+ end
233
+
234
+ spawn_model(:User) do
235
+ compose :credit_card,
236
+ mapping: {
237
+ credit_card_name: :name,
238
+ credit_card_brand: :brand
239
+ }
240
+ end
241
+
242
+ spawn_composition(:CreditCard) do
243
+ composed_from :user
244
+
245
+ validates :name, presence: true
246
+ end
247
+ end
248
+
249
+ it { expect(user.credit_card.attributes).to eq(name: 'Jon Snow', brand: 'Visa') }
250
+ end
251
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+ require 'rspec/rails'
3
+
4
+ RSpec.configure do |config|
5
+ config.infer_spec_type_from_file_location!
6
+ config.filter_rails_from_backtrace!
7
+ end
@@ -0,0 +1,42 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+ ENV['DATABASE_URL'] = 'sqlite3://localhost/tmp/composition_test'
3
+
4
+ require 'bundler/setup'
5
+ require 'rails'
6
+ case Rails.version
7
+ when '4.2.7.1'
8
+ require 'support/apps/rails4_2'
9
+ when '5.0.2'
10
+ require 'support/apps/rails5_0'
11
+ end
12
+ require 'support/model_macros'
13
+ require 'composition'
14
+
15
+ RSpec.configure do |config|
16
+ config.expect_with :rspec do |expectations|
17
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
18
+ end
19
+
20
+ config.mock_with :rspec do |mocks|
21
+ mocks.verify_partial_doubles = true
22
+ end
23
+
24
+ config.shared_context_metadata_behavior = :apply_to_host_groups
25
+
26
+ config.include Composition::Testing::ModelMacros
27
+
28
+ config.before :each do
29
+ @spawned_models = []
30
+ @spawned_compositions = []
31
+ end
32
+
33
+ config.after :each do
34
+ @spawned_models.each do |model|
35
+ Object.instance_eval { remove_const model } if Object.const_defined?(model)
36
+ end
37
+
38
+ @spawned_compositions.each do |model|
39
+ Object.instance_eval { remove_const model } if Object.const_defined?(model)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ require 'rails/all'
2
+
3
+ module Rails42
4
+ class Application < Rails::Application
5
+ config.active_record.raise_in_transactional_callbacks = true
6
+ # Settings specified here will take precedence over those in config/application.rb.
7
+
8
+ # In the development environment your application's code is reloaded on
9
+ # every request. This slows down response time but is perfect for development
10
+ # since you don't have to restart the web server when you make code changes.
11
+ config.cache_classes = false
12
+
13
+ # Do not eager load code on boot.
14
+ config.eager_load = false
15
+
16
+ # Show full error reports and disable caching.
17
+ config.consider_all_requests_local = true
18
+ config.action_controller.perform_caching = false
19
+
20
+ # Don't care if the mailer can't send.
21
+ config.action_mailer.raise_delivery_errors = false
22
+
23
+ # Print deprecation notices to the Rails logger.
24
+ config.active_support.deprecation = :log
25
+
26
+ # Raise an error on page load if there are pending migrations.
27
+ config.active_record.migration_error = :page_load
28
+
29
+ # Debug mode disables concatenation and preprocessing of assets.
30
+ # This option may cause significant delays in view rendering with a large
31
+ # number of complex assets.
32
+ config.assets.debug = true
33
+
34
+ # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35
+ # yet still be able to expire them through the digest params.
36
+ config.assets.digest = true
37
+
38
+ # Adds additional error checking when serving assets at runtime.
39
+ # Checks for improperly declared sprockets dependencies.
40
+ # Raises helpful error messages.
41
+ config.assets.raise_runtime_errors = true
42
+
43
+ # Raises error for missing translations
44
+ # config.action_view.raise_on_missing_translations = true
45
+
46
+ config.secret_key_base = '49837489qkuweoiuoqwehisuakshdjksadhaisdy78o34y138974xyqp9rmye8yrpiokeuioqwzyoiuxftoyqiuxrhm3iou1hrzmjk'
47
+ end
48
+ end
49
+
50
+ Rails.application.initialize!
@@ -0,0 +1,48 @@
1
+ require 'rails/all'
2
+
3
+ module Rails50
4
+ class Application < Rails::Application
5
+ # The test environment is used exclusively to run your application's
6
+ # test suite. You never need to work with it otherwise. Remember that
7
+ # your test database is "scratch space" for the test suite and is wiped
8
+ # and recreated between test runs. Don't rely on the data there!
9
+ config.cache_classes = true
10
+
11
+ # Do not eager load code on boot. This avoids loading your whole application
12
+ # just for the purpose of running a single test. If you are using a tool that
13
+ # preloads Rails for running tests, you may have to set it to true.
14
+ config.eager_load = false
15
+
16
+ # Configure public file server for tests with Cache-Control for performance.
17
+ config.public_file_server.enabled = true
18
+ config.public_file_server.headers = {
19
+ 'Cache-Control' => 'public, max-age=3600'
20
+ }
21
+
22
+ # Show full error reports and disable caching.
23
+ config.consider_all_requests_local = true
24
+ config.action_controller.perform_caching = false
25
+
26
+ # Raise exceptions instead of rendering exception templates.
27
+ config.action_dispatch.show_exceptions = false
28
+
29
+ # Disable request forgery protection in test environment.
30
+ config.action_controller.allow_forgery_protection = false
31
+ config.action_mailer.perform_caching = false
32
+
33
+ # Tell Action Mailer not to deliver emails to the real world.
34
+ # The :test delivery method accumulates sent emails in the
35
+ # ActionMailer::Base.deliveries array.
36
+ config.action_mailer.delivery_method = :test
37
+
38
+ # Print deprecation notices to the stderr.
39
+ config.active_support.deprecation = :stderr
40
+
41
+ # Raises error for missing translations
42
+ # config.action_view.raise_on_missing_translations = true
43
+
44
+ config.secret_key_base = '49837489qkuweoiuoqwehisuakshdjksadhaisdy78o34y138974xyqp9rmye8yrpiokeuioqwzyoiuxftoyqiuxrhm3iou1hrzmjk'
45
+ end
46
+ end
47
+
48
+ Rails.application.initialize!
@@ -0,0 +1,29 @@
1
+ module Composition
2
+ module Testing
3
+ module ModelMacros
4
+ def spawn_model(klass, &block)
5
+ Object.instance_eval { remove_const klass } if Object.const_defined?(klass)
6
+ Object.const_set klass, Class.new(ActiveRecord::Base)
7
+ Object.const_get(klass).class_eval(&block) if block_given?
8
+ @spawned_models << klass.to_sym
9
+ end
10
+
11
+ def spawn_composition(klass, &block)
12
+ Object.instance_eval { remove_const klass } if Object.const_defined?(klass)
13
+ Object.const_set klass, Class.new(Composition::Base)
14
+ Object.const_get(klass).class_eval(&block) if block_given?
15
+ @spawned_compositions << klass.to_sym
16
+ end
17
+
18
+ def create_table(table_name)
19
+ ActiveRecord::Migration.suppress_messages do
20
+ ActiveRecord::Schema.define do
21
+ create_table table_name, force: true do |t|
22
+ yield(t)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: composition
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.beta1
5
+ platform: ruby
6
+ authors:
7
+ - Marcelo Casiraghi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '3.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '3.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: appraisal
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Composition for ActiveRecord models
126
+ email: marcelo@paragon-labs.com
127
+ executables: []
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - lib/composition.rb
132
+ - lib/composition/base.rb
133
+ - lib/composition/builders/compose.rb
134
+ - lib/composition/builders/composed_from.rb
135
+ - lib/composition/compositions/compose.rb
136
+ - lib/composition/compositions/composed_from.rb
137
+ - lib/composition/compositions/composition.rb
138
+ - lib/composition/macros/compose.rb
139
+ - lib/composition/macros/composed_from.rb
140
+ - lib/composition/reflection.rb
141
+ - lib/composition/version.rb
142
+ - spec/macros/compose_spec.rb
143
+ - spec/rails_helper.rb
144
+ - spec/spec_helper.rb
145
+ - spec/support/apps/rails4_2.rb
146
+ - spec/support/apps/rails5_0.rb
147
+ - spec/support/model_macros.rb
148
+ homepage: https://github.com/marceloeloelo/composition
149
+ licenses:
150
+ - MIT
151
+ metadata: {}
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">"
164
+ - !ruby/object:Gem::Version
165
+ version: 1.3.1
166
+ requirements: []
167
+ rubyforge_project:
168
+ rubygems_version: 2.6.10
169
+ signing_key:
170
+ specification_version: 4
171
+ summary: Composition for ActiveRecord models
172
+ test_files:
173
+ - spec/macros/compose_spec.rb
174
+ - spec/rails_helper.rb
175
+ - spec/spec_helper.rb
176
+ - spec/support/apps/rails4_2.rb
177
+ - spec/support/apps/rails5_0.rb
178
+ - spec/support/model_macros.rb