composition 1.0.0.beta1

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.
@@ -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