active_container 0.0.1

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.
Binary file
@@ -0,0 +1,171 @@
1
+ This version of the GNU Lesser General Public License incorporates
2
+ the terms and conditions of version 3 of the GNU General Public
3
+ License, supplemented by the additional permissions listed below.
4
+
5
+
6
+ 0. Additional Definitions.
7
+ --------------------------
8
+
9
+ As used herein, "this License" refers to version 3 of the GNU Lesser
10
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
11
+ General Public License.
12
+
13
+ "The Library" refers to a covered work governed by this License,
14
+ other than an Application or a Combined Work as defined below.
15
+
16
+ An "Application" is any work that makes use of an interface provided
17
+ by the Library, but which is not otherwise based on the Library.
18
+ Defining a subclass of a class defined by the Library is deemed a mode
19
+ of using an interface provided by the Library.
20
+
21
+ A "Combined Work" is a work produced by combining or linking an
22
+ Application with the Library. The particular version of the Library
23
+ with which the Combined Work was made is also called the "Linked
24
+ Version".
25
+
26
+ The "Minimal Corresponding Source" for a Combined Work means the
27
+ Corresponding Source for the Combined Work, excluding any source code
28
+ for portions of the Combined Work that, considered in isolation, are
29
+ based on the Application, and not on the Linked Version.
30
+
31
+ The "Corresponding Application Code" for a Combined Work means the
32
+ object code and/or source code for the Application, including any data
33
+ and utility programs needed for reproducing the Combined Work from the
34
+ Application, but excluding the System Libraries of the Combined Work.
35
+
36
+
37
+ 1. Exception to Section 3 of the GNU GPL.
38
+ --------------------------------------------------------------------------------
39
+
40
+ You may convey a covered work under sections 3 and 4 of this License
41
+ without being bound by section 3 of the GNU GPL.
42
+
43
+
44
+ 2. Conveying Modified Versions.
45
+ --------------------------------------------------------------------------------
46
+
47
+ If you modify a copy of the Library, and, in your modifications, a
48
+ facility refers to a function or data to be supplied by an Application
49
+ that uses the facility (other than as an argument passed when the
50
+ facility is invoked), then you may convey a copy of the modified
51
+ version:
52
+
53
+ * a) under this License, provided that you make a good faith effort to
54
+ ensure that, in the event an Application does not supply the
55
+ function or data, the facility still operates, and performs
56
+ whatever part of its purpose remains meaningful, or
57
+
58
+ * b) under the GNU GPL, with none of the additional permissions of
59
+ this License applicable to that copy.
60
+
61
+
62
+ 3. Object Code Incorporating Material from Library Header Files.
63
+ --------------------------------------------------------------------------------
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ * a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ * b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+
80
+ 4. Combined Works.
81
+ --------------------------------------------------------------------------------
82
+
83
+ You may convey a Combined Work under terms of your choice that,
84
+ taken together, effectively do not restrict modification of the
85
+ portions of the Library contained in the Combined Work and reverse
86
+ engineering for debugging such modifications, if you also do each of
87
+ the following:
88
+
89
+ * a) Give prominent notice with each copy of the Combined Work that
90
+ the Library is used in it and that the Library and its use are
91
+ covered by this License.
92
+
93
+ * b) Accompany the Combined Work with a copy of the GNU GPL and this license
94
+ document.
95
+
96
+ * c) For a Combined Work that displays copyright notices during
97
+ execution, include the copyright notice for the Library among
98
+ these notices, as well as a reference directing the user to the
99
+ copies of the GNU GPL and this license document.
100
+
101
+ * d) Do one of the following:
102
+
103
+ * 0) Convey the Minimal Corresponding Source under the terms of this
104
+ License, and the Corresponding Application Code in a form
105
+ suitable for, and under terms that permit, the user to
106
+ recombine or relink the Application with a modified version of
107
+ the Linked Version to produce a modified Combined Work, in the
108
+ manner specified by section 6 of the GNU GPL for conveying
109
+ Corresponding Source.
110
+
111
+ * 1) Use a suitable shared library mechanism for linking with the
112
+ Library. A suitable mechanism is one that (a) uses at run time
113
+ a copy of the Library already present on the user's computer
114
+ system, and (b) will operate properly with a modified version
115
+ of the Library that is interface-compatible with the Linked
116
+ Version.
117
+
118
+ * e) Provide Installation Information, but only if you would otherwise
119
+ be required to provide such information under section 6 of the
120
+ GNU GPL, and only to the extent that such information is
121
+ necessary to install and execute a modified version of the
122
+ Combined Work produced by recombining or relinking the
123
+ Application with a modified version of the Linked Version. (If
124
+ you use option 4d0, the Installation Information must accompany
125
+ the Minimal Corresponding Source and Corresponding Application
126
+ Code. If you use option 4d1, you must provide the Installation
127
+ Information in the manner specified by section 6 of the GNU GPL
128
+ for conveying Corresponding Source.)
129
+
130
+
131
+ 5. Combined Libraries.
132
+ --------------------------------------------------------------------------------
133
+
134
+ You may place library facilities that are a work based on the
135
+ Library side by side in a single library together with other library
136
+ facilities that are not Applications and are not covered by this
137
+ License, and convey such a combined library under terms of your
138
+ choice, if you do both of the following:
139
+
140
+ * a) Accompany the combined library with a copy of the same work based
141
+ on the Library, uncombined with any other library facilities,
142
+ conveyed under the terms of this License.
143
+
144
+ * b) Give prominent notice with the combined library that part of it
145
+ is a work based on the Library, and explaining where to find the
146
+ accompanying uncombined form of the same work.
147
+
148
+
149
+ 6. Revised Versions of the GNU Lesser General Public License.
150
+ --------------------------------------------------------------------------------
151
+
152
+ The Free Software Foundation may publish revised and/or new versions
153
+ of the GNU Lesser General Public License from time to time. Such new
154
+ versions will be similar in spirit to the present version, but may
155
+ differ in detail to address new problems or concerns.
156
+
157
+ Each version is given a distinguishing version number. If the
158
+ Library as you received it specifies that a certain numbered version
159
+ of the GNU Lesser General Public License "or any later version"
160
+ applies to it, you have the option of following the terms and
161
+ conditions either of that published version or of any later version
162
+ published by the Free Software Foundation. If the Library as you
163
+ received it does not specify a version number of the GNU Lesser
164
+ General Public License, you may choose any version of the GNU Lesser
165
+ General Public License ever published by the Free Software Foundation.
166
+
167
+ If the Library as you received it specifies that a proxy can decide
168
+ whether future versions of the GNU Lesser General Public License shall
169
+ apply, that proxy's public statement of acceptance of any version is
170
+ permanent authorization for you to choose that version for the
171
+ Library.
@@ -0,0 +1,298 @@
1
+ [![Build Status](https://travis-ci.org/ToadJamb/active_container.svg?branch=chore%2Ftravis-ci)](https://travis-ci.org/ToadJamb/active_container)
2
+
3
+ ActiveContainer
4
+ ===============
5
+
6
+ Trim the fatty models. Use ActiveContainer.
7
+
8
+ ActiveContainer doesn't just keep your models thin,
9
+ it keeps your tests away from the database.
10
+ Completely.
11
+ FactoryGirl's `build` and `build_stubbed` require db access.
12
+ And even RSpec ActiveModel Mocks can't avoid attempting to open a connection
13
+ when you call ANY function on the model.
14
+
15
+ You can get away from this easily and safely with ActiveContainer.
16
+
17
+ More to come...
18
+
19
+
20
+ Installation
21
+ ------------
22
+
23
+ $ gem install active_container
24
+
25
+
26
+ Gemfile
27
+ -------
28
+
29
+ $ gem 'active_container'
30
+
31
+
32
+ Require
33
+ -------
34
+
35
+ $ require 'active_container'
36
+
37
+
38
+ Usage
39
+ -----
40
+
41
+ Please note that this example is not necessarily for a Rails project.
42
+ In particular, namespacing may not need to be explicit.
43
+
44
+ Naming is important! Wrappers for `MyModel` *MUST* be named `MyModelWrapper`.
45
+
46
+
47
+ ### Example
48
+
49
+ Let's wear out the blog post example:
50
+
51
+ ```
52
+ # models/person.rb
53
+ class Person < ActiveRecord::Base
54
+ has_many :posts
55
+
56
+ validates :first_name, :presence => true
57
+ validates :last_name, :presence => true
58
+ validates :full_name, :presence => true
59
+
60
+ validate :custom_validation
61
+
62
+ def custom_validation
63
+ # do custom validation
64
+ end
65
+ end
66
+
67
+ # models/post.rb
68
+ class Post < ActiveRecord::Base
69
+ has_one :author, :through => :person
70
+
71
+ validates :title, :presence => true
72
+ validates :body, :presence => true
73
+ end
74
+
75
+ # models/wrappers/post_wrapper.rb
76
+ class PostWrapper < ActiveContainer::Wrapper
77
+ end
78
+
79
+ # models/wrappers/person_wrapper.rb
80
+ class PersonWrapper < ActiveContainer::Wrapper
81
+ include Wrappers::Person::FullName
82
+ include Wrappers::Person::PostCount
83
+
84
+ delegate :first_name, :last_name
85
+
86
+ wrap_delegate :posts
87
+ end
88
+
89
+ # models/wrappers/person/full_name.rb
90
+ module Wrappers
91
+ module Person
92
+ module FullName
93
+ def full_name
94
+ "#{first_name} #{last_name}"
95
+ end
96
+
97
+ # This logic is overly simplified for example purposes only.
98
+ def full_name=(value)
99
+ names = value.split(' ')
100
+
101
+ @record.full_name = value # Must use `@record` here
102
+ self.first_name = names.first # We can use either `@record` or `self`.
103
+ self.last_name = names.last # We can use either `@record` or `self`.
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ # models/wrappers/person/post_count.rb
110
+ module Wrappers
111
+ module Person
112
+ module PostCount
113
+ def post_count
114
+ post.count
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ ```
121
+
122
+ It is important that the helpers are included prior to calling
123
+ `delegate` as there are checks to ensure that assignment
124
+ methods do not already exist.
125
+
126
+ `ActiveContainer::Wrapper` automatically passes `id` to the underlying model,
127
+ so it does not need to be included in the list sent to `delegate`.
128
+
129
+
130
+ ### Commentary
131
+
132
+ #### Goals
133
+
134
+ ##### Reduce code in models (including `include` statements)
135
+
136
+ The only code that is left in the model is limited to ActiveRecord/ActiveModel
137
+ relationships, validations, etc.
138
+
139
+ These can get out of hand on their own in large projects.
140
+ This way, nothing but the essentials are in your models.
141
+
142
+
143
+ ##### Increase test speed
144
+
145
+ One of the primary goals was to get completely away from the database
146
+ during testing.
147
+
148
+ This means not even so much as opening a connection
149
+ and this may not even be possible if you work with anything that has
150
+ knowledge of the ActiveRecord object you're working with.
151
+
152
+ This includes FactoryGirl's `build` and `build_stubbed`.
153
+ Even `mock_model` will attempt to open a database connection
154
+ if you call a method you haven't mocked out
155
+ (it wasn't mocked because we wanted to run the code!).
156
+
157
+
158
+ ##### More explicit model interfaces
159
+
160
+ ActiveRecord models tell you little about what attributes you actually have.
161
+ You specify the methods that pass through to the underlying models,
162
+ so that interface is clearly seen when examining a Wrapper.
163
+
164
+ Along with this, it is recommended that functionality is grouped in *VERY SMALL*
165
+ chunks via mixins for the wrappers.
166
+
167
+ This allows easier testing of the mixins and tells you more about the interface
168
+ for the wrapper.
169
+
170
+
171
+ ##### Encapsulation of model lifecycle events.
172
+
173
+ ActiveRecord callbacks are the devil's work.
174
+ ActiveContainer lets you take control again.
175
+
176
+ It can be used only for that, if you like:
177
+
178
+ $ Person.new(:first_name => 'John').wrap.save # where save has custom logic
179
+
180
+ A simple example:
181
+
182
+ ```
183
+ # models/wrappers/person_wrapper.rb
184
+ class PersonWrapper < ActiveContainer::Wrapper
185
+ def save
186
+ if !@record.save
187
+ # do something
188
+ end
189
+ end
190
+ end
191
+ ```
192
+
193
+
194
+ If you want to apply logic to all models, simply create your own BaseWrapper:
195
+
196
+ ```
197
+ # models/wrappers/base_wrapper.rb
198
+ class BaseWrapper < ActiveContainer::Wrapper
199
+ def save
200
+ if !@record.save
201
+ # do something
202
+ end
203
+ end
204
+ end
205
+
206
+ # models/wrappers/person_wrapper.rb
207
+ class PersonWrapper < BaseWrapper
208
+ def save
209
+ if !@record.save
210
+ # do something
211
+ end
212
+ end
213
+ end
214
+ ```
215
+
216
+
217
+ Testing
218
+ -------
219
+
220
+ Like Drake, let's start at the bottom.
221
+
222
+ ```
223
+ #spec/models/wrappers/person/full_name_spec.rb
224
+ # It is expected that `spec_helper` will load you files
225
+ # without even THINKING about a database connection.
226
+ require 'spec_helper'
227
+
228
+ RSpec.describe Wrappers::Person::FullName do
229
+ subject { PersonWrapper.new person }
230
+
231
+ let(:person) do
232
+ OpenStruct.new \
233
+ :first_name => first_name,
234
+ :last_name => last_name,
235
+ end
236
+
237
+ describe '#full_name' do
238
+ shared_examples_for 'a full name' do |first, last, expected|
239
+ context "given a first name of #{first.inspect}" do
240
+ let(:first_name) { first_name }
241
+
242
+ context "given a last name of #{last.inspect}" do
243
+ let(:last_name) { last_name }
244
+
245
+ it "returns #{expected.inspect}" do
246
+ expect(subject.full_name).to eq expected
247
+ end
248
+ end
249
+ end
250
+ end
251
+
252
+ it_behaves_like 'a full name', 'Tom', 'Jones', 'Tom Jones'
253
+ it_behaves_like 'a full name', 'Tiny ', ' Tim ', 'Tiny Tim '
254
+ end
255
+ end
256
+
257
+ #spec/models/wrappers/person_wrapper_spec.rb
258
+ require 'spec/app_helper' # or rails_helper or whatever includes the full app.
259
+ RSpec.describe PersonWrapper do
260
+ subject { PersonWrapper.new person }
261
+
262
+ let(:person) do
263
+ # this or FactoryGirl.build_stubbed
264
+ Person.new \
265
+ :first_name => first_name,
266
+ :last_name => last_name,
267
+ end
268
+
269
+ describe '#full_name' do
270
+ context 'given first and last name of Dizzy and Gillespie' do
271
+ let(:first_name) { 'Dizzy' }
272
+ let(:last_name) { 'Gillespie' }
273
+
274
+ it 'returns Dizzy Gillespie' do
275
+ expect(subject.full_name).to eq 'Dizzy Gillespie'
276
+ end
277
+ end
278
+ end
279
+ end
280
+ ```
281
+
282
+
283
+ ### Notes
284
+
285
+ The `PersonWrapper` is a fairly poor example.
286
+ The important thing here is that `#full_name`
287
+ gets called *at some point* in this group of tests.
288
+
289
+ It is not important that it be tested explicitly.
290
+ By reducing the number of tests at this level to be just enough
291
+ to ensure integration with the individual components (including the model),
292
+ we can speed up our test suite dramatically.
293
+
294
+ The biggest concern at this level is not whether the logic is correct,
295
+ but whether we have changed attribute names without updating
296
+ the mocked objects we use at the lower level.
297
+
298
+ This is the tradeoff.