moribus 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.
Files changed (63) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +35 -0
  3. data/.rspec +4 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.simplecov +42 -0
  7. data/.travis.yml +8 -0
  8. data/Gemfile +19 -0
  9. data/LICENSE +20 -0
  10. data/README.md +104 -0
  11. data/Rakefile +15 -0
  12. data/lib/colorized_text.rb +33 -0
  13. data/lib/moribus.rb +133 -0
  14. data/lib/moribus/aggregated_behavior.rb +80 -0
  15. data/lib/moribus/aggregated_cache_behavior.rb +76 -0
  16. data/lib/moribus/alias_association.rb +106 -0
  17. data/lib/moribus/extensions.rb +37 -0
  18. data/lib/moribus/extensions/delegate_associated.rb +48 -0
  19. data/lib/moribus/extensions/has_aggregated_extension.rb +94 -0
  20. data/lib/moribus/extensions/has_current_extension.rb +17 -0
  21. data/lib/moribus/macros.rb +120 -0
  22. data/lib/moribus/tracked_behavior.rb +91 -0
  23. data/lib/moribus/version.rb +3 -0
  24. data/moribus.gemspec +33 -0
  25. data/spec/dummy/README.rdoc +261 -0
  26. data/spec/dummy/Rakefile +7 -0
  27. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  28. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  29. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  30. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  31. data/spec/dummy/app/mailers/.gitkeep +0 -0
  32. data/spec/dummy/app/models/.gitkeep +0 -0
  33. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  34. data/spec/dummy/config.ru +4 -0
  35. data/spec/dummy/config/application.rb +61 -0
  36. data/spec/dummy/config/boot.rb +10 -0
  37. data/spec/dummy/config/database.yml +25 -0
  38. data/spec/dummy/config/environment.rb +5 -0
  39. data/spec/dummy/config/environments/development.rb +37 -0
  40. data/spec/dummy/config/environments/production.rb +67 -0
  41. data/spec/dummy/config/environments/test.rb +37 -0
  42. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  43. data/spec/dummy/config/initializers/inflections.rb +15 -0
  44. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  45. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  46. data/spec/dummy/config/initializers/session_store.rb +8 -0
  47. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/spec/dummy/config/locales/en.yml +5 -0
  49. data/spec/dummy/config/routes.rb +58 -0
  50. data/spec/dummy/db/test.sqlite3 +0 -0
  51. data/spec/dummy/lib/assets/.gitkeep +0 -0
  52. data/spec/dummy/log/.gitkeep +0 -0
  53. data/spec/dummy/public/404.html +26 -0
  54. data/spec/dummy/public/422.html +26 -0
  55. data/spec/dummy/public/500.html +25 -0
  56. data/spec/dummy/public/favicon.ico +0 -0
  57. data/spec/dummy/script/rails +6 -0
  58. data/spec/moribus/alias_association_spec.rb +88 -0
  59. data/spec/moribus/macros_spec.rb +7 -0
  60. data/spec/moribus_spec.rb +332 -0
  61. data/spec/spec_helper.rb +15 -0
  62. data/spec/support/moribus_spec_model.rb +57 -0
  63. metadata +209 -0
File without changes
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ </div>
24
+ </body>
25
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe Moribus::AliasAssociation do
4
+ before do
5
+ class SpecPost < MoribusSpecModel(:spec_author_id => :integer, :body => :string)
6
+ belongs_to :spec_author , :alias => :creator
7
+ has_many :spec_comments , :alias => :remarks
8
+ has_one :spec_post_info, :alias => :information
9
+
10
+ alias_association :author , :spec_author
11
+ alias_association :comments , :spec_comments
12
+ alias_association :post_info, :spec_post_info
13
+ end
14
+
15
+ class SpecAuthor < MoribusSpecModel(:name => :string)
16
+ has_many :spec_posts
17
+ end
18
+
19
+ class SpecPostInfo < MoribusSpecModel(:spec_post_id => :integer, :ip => :string)
20
+ belongs_to :spec_post, :alias => :note
21
+ end
22
+
23
+ class SpecComment < MoribusSpecModel(:spec_post_id => :integer, :body => :string)
24
+ belongs_to :spec_post
25
+ end
26
+ end
27
+
28
+ after do
29
+ MoribusSpecModel.cleanup!
30
+ end
31
+
32
+ before do
33
+ author = SpecAuthor.create(:name => 'John')
34
+ @post = author.spec_posts.create(:body => 'Post Body')
35
+ @post.spec_comments.create(:body => 'Fabulous!')
36
+ @post.create_spec_post_info(:ip => '127.0.0.1')
37
+ end
38
+
39
+ describe "reflection aliasing" do
40
+ it "alias association name in reflections" do
41
+ SpecPost.reflect_on_association(:author).should_not be_nil
42
+ end
43
+
44
+ it "should not raise error when using aliased name in scopes" do
45
+ expect{
46
+ SpecPost.includes(:comments).first
47
+ }.to_not raise_error
48
+ end
49
+ end
50
+
51
+ describe "association accessor alias methods" do
52
+ subject{ @post }
53
+
54
+ it{ should respond_to :author }
55
+ it{ should respond_to :author= }
56
+ it{ should respond_to :comments }
57
+ it{ should respond_to :comments= }
58
+ it{ should respond_to :post_info }
59
+ it{ should respond_to :post_info= }
60
+ end
61
+
62
+ describe "singular association alias method" do
63
+ subject{ @post }
64
+
65
+ it{ should respond_to :build_author }
66
+ it{ should respond_to :create_author }
67
+ it{ should respond_to :create_author! }
68
+
69
+ it{ should respond_to :build_post_info }
70
+ it{ should respond_to :create_post_info }
71
+ it{ should respond_to :create_post_info! }
72
+ end
73
+
74
+ describe "collection association alias method" do
75
+ subject{ @post }
76
+
77
+ it{ should respond_to :comment_ids }
78
+ it{ should respond_to :comment_ids= }
79
+ end
80
+
81
+ describe ":alias => alias_name shortcuts" do
82
+ subject{ @post }
83
+
84
+ it { should respond_to :creator }
85
+ it { should respond_to :remarks }
86
+ it { should respond_to :information }
87
+ end
88
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ # Some of the Moribus::Macros methods are tested in
4
+ # spec/lib/moribus_spec.rb.
5
+ describe Moribus::Macros do
6
+
7
+ end
@@ -0,0 +1,332 @@
1
+ require 'spec_helper'
2
+
3
+ describe Moribus do
4
+ before do
5
+ class SpecStatus < MoribusSpecModel(:name => :string, :description => :string)
6
+ acts_as_enumerated
7
+
8
+ self.enumeration_model_updates_permitted = true
9
+ create!(:name => 'inactive', :description => 'Inactive')
10
+ create!(:name => 'active', :description => 'Active')
11
+ end
12
+
13
+ class SpecType < MoribusSpecModel(:name => :string, :description => :string)
14
+ acts_as_enumerated
15
+
16
+ self.enumeration_model_updates_permitted = true
17
+ create!(:name => 'important', :description => 'Important')
18
+ create!(:name => 'unimportant', :description => 'Unimportant')
19
+ end
20
+
21
+ class SpecSuffix < MoribusSpecModel(:name => :string, :description => :string)
22
+ acts_as_enumerated
23
+
24
+ self.enumeration_model_updates_permitted = true
25
+ create!(:name => 'none', :description => '')
26
+ create!(:name => 'jr', :description => 'Junior')
27
+ end
28
+
29
+ class SpecPersonName < MoribusSpecModel(:first_name => :string, :last_name => :string, :spec_suffix_id => :integer)
30
+ acts_as_aggregated
31
+ has_enumerated :spec_suffix, :default => ''
32
+
33
+ validates_presence_of :first_name, :last_name
34
+
35
+ # custom writer that additionally strips first name
36
+ def first_name=(value)
37
+ self[:first_name] = value.strip
38
+ end
39
+ end
40
+
41
+ class SpecCustomerFeature < MoribusSpecModel(:feature_name => :string)
42
+ acts_as_aggregated :cache_by => :feature_name
43
+ end
44
+
45
+ class SpecCustomerInfo < MoribusSpecModel( :spec_customer_id => :integer!,
46
+ :spec_person_name_id => :integer,
47
+ :spec_status_id => :integer,
48
+ :spec_type_id => :integer,
49
+ :is_current => :boolean,
50
+ :lock_version => :integer,
51
+ :created_at => :datetime,
52
+ :updated_at => :datetime,
53
+ :previous_id => :integer )
54
+ attr :custom_field
55
+
56
+ belongs_to :spec_customer, :inverse_of => :spec_customer_info, :touch => true
57
+ has_aggregated :spec_person_name
58
+ has_enumerated :spec_status
59
+ has_enumerated :spec_type
60
+
61
+ acts_as_tracked :preceding_key => :previous_id
62
+ end
63
+
64
+ class SpecCustomer < MoribusSpecModel(:spec_status_id => :integer)
65
+ has_one_current :spec_customer_info, :inverse_of => :spec_customer
66
+ has_enumerated :spec_status, :default => 'inactive'
67
+
68
+ delegate_associated :spec_person_name, :custom_field, :spec_type, :to => :spec_customer_info
69
+ end
70
+
71
+ class SpecCustomerEmail < MoribusSpecModel(:spec_customer_id => :integer, :email => :string, :is_current => :boolean, :status => :string)
72
+ connection.add_index table_name, [:email, :is_current], :unique => true
73
+
74
+ belongs_to :spec_customer
75
+
76
+ acts_as_tracked
77
+ end
78
+ end
79
+
80
+ after do
81
+ MoribusSpecModel.cleanup!
82
+ end
83
+
84
+ describe "common behavior" do
85
+ before do
86
+ @info = SpecCustomerInfo.create(
87
+ :spec_customer_id => 1,
88
+ :spec_person_name_id => 1,
89
+ :is_current => true,
90
+ :created_at => 5.days.ago,
91
+ :updated_at => 5.days.ago
92
+ )
93
+ end
94
+
95
+ it "should revert changes if exception is raised" do
96
+ old_id = @info.id
97
+ old_updated_at = @info.updated_at
98
+ old_created_at = @info.created_at
99
+ suppress(Exception) do
100
+ expect {
101
+ @info.update_attributes :spec_customer_id => nil, :spec_person_name_id => 2
102
+ }.not_to change(SpecCustomerInfo, :count)
103
+ end
104
+ @info.new_record?.should be_false
105
+ @info.id.should == old_id
106
+ @info.updated_at.should == old_updated_at
107
+ @info.created_at.should == old_created_at
108
+ end
109
+ end
110
+
111
+ describe 'Aggregated' do
112
+ context "definition" do
113
+ it "should raise an error on an unknown option" do
114
+ expect{
115
+ Class.new(ActiveRecord::Base).class_eval do
116
+ acts_as_aggregated :invalid_key => :error
117
+ end
118
+ }.to raise_error(ArgumentError)
119
+ end
120
+
121
+ it "should raise an error when including AggregatedCacheBehavior without AggregatedBehavior" do
122
+ expect{
123
+ Class.new(ActiveRecord::Base).class_eval do
124
+ include Moribus::AggregatedCacheBehavior
125
+ end
126
+ }.to raise_error(Moribus::AggregatedCacheBehavior::NotAggregatedError)
127
+ end
128
+ end
129
+
130
+ before do
131
+ @existing = SpecPersonName.create! :first_name => 'John', :last_name => 'Smith'
132
+ end
133
+
134
+ it "should not duplicate records" do
135
+ expect {
136
+ SpecPersonName.create :first_name => ' John ', :last_name => 'Smith'
137
+ }.not_to change(SpecPersonName, :count)
138
+ end
139
+
140
+ it "should lookup self and replace id with existing on create" do
141
+ name = SpecPersonName.new :first_name => 'John', :last_name => 'Smith'
142
+ name.save
143
+ name.id.should == @existing.id
144
+ end
145
+
146
+ it "should create a new record if lookup fails" do
147
+ expect {
148
+ SpecPersonName.create :first_name => 'Alice', :last_name => 'Smith'
149
+ }.to change(SpecPersonName, :count).by(1)
150
+ end
151
+
152
+ it "should lookup self and replace id with existing on update" do
153
+ name = SpecPersonName.create :first_name => 'Alice', :last_name => 'Smith'
154
+ name.update_attributes :first_name => 'John'
155
+ name.id.should == @existing.id
156
+ end
157
+
158
+ context "with caching" do
159
+ before do
160
+ @existing = SpecCustomerFeature.create(:feature_name => 'Pays')
161
+ SpecCustomerFeature.clear_cache
162
+ end
163
+
164
+ it "should lookup the existing value and add it to the cache" do
165
+ feature = SpecCustomerFeature.new :feature_name => @existing.feature_name
166
+ expect{ feature.save }.to change(SpecCustomerFeature.aggregated_records_cache, :length).by(1)
167
+ feature.id.should == @existing.id
168
+ end
169
+
170
+ it "should add the freshly-created record to the cache" do
171
+ expect{ SpecCustomerFeature.create(:feature_name => 'Fraud') }.to change(SpecCustomerFeature.aggregated_records_cache, :length).by(1)
172
+ end
173
+
174
+ it "should freeze the cached object" do
175
+ feature = SpecCustomerFeature.create(:feature_name => 'Cancelled')
176
+ SpecCustomerFeature.aggregated_records_cache[feature.feature_name].should be_frozen
177
+ end
178
+
179
+ it "should cache the clone of the record, not the record itself" do
180
+ feature = SpecCustomerFeature.create(:feature_name => 'Returned')
181
+ SpecCustomerFeature.aggregated_records_cache[feature.feature_name].object_id.should_not == feature.object_id
182
+ end
183
+ end
184
+ end
185
+
186
+ describe 'Tracked' do
187
+ before do
188
+ @customer = SpecCustomer.create
189
+ @info = @customer.create_spec_customer_info :spec_person_name_id => 1
190
+ end
191
+
192
+ it "should create a new current record if updated" do
193
+ expect {
194
+ @info.update_attributes(:spec_person_name_id => 2)
195
+ }.to change(SpecCustomerInfo, :count).by(1)
196
+ end
197
+
198
+ it "should replace itself with new id" do
199
+ old_id = @info.id
200
+ @info.update_attributes(:spec_person_name_id => 2)
201
+ @info.id.should_not == old_id
202
+ end
203
+
204
+ it "should set is_current record to false for superseded record" do
205
+ old_id = @info.id
206
+ @info.update_attributes(:spec_person_name_id => 2)
207
+ SpecCustomerInfo.find(old_id).is_current.should be_false
208
+ end
209
+
210
+ it "should set previous_id to the id of the previous record" do
211
+ old_id = @info.id
212
+ @info.update_attributes(:spec_person_name_id => 2)
213
+ @info.previous_id.should == old_id
214
+ end
215
+
216
+ it "assigning a new current record should change is_current to false for previous one" do
217
+ new_info = SpecCustomerInfo.new :spec_person_name_id => 2, :is_current => true
218
+ @customer.spec_customer_info = new_info
219
+ new_info.spec_customer_id.should == @customer.id
220
+ @info.is_current.should be_false
221
+ end
222
+
223
+ it "should not crash on superseding with 'is_current' conditional constraint" do
224
+ email = SpecCustomerEmail.create(:spec_customer => @customer, :email => 'foo@bar.com', :status => 'unverified', :is_current => true)
225
+ expect{ email.update_attributes(:status => 'verified') }.not_to raise_error
226
+ end
227
+
228
+ describe 'updated_at and created_at' do
229
+ let(:first_time) { Time.zone.parse('2012-07-16 00:00:00') }
230
+ let(:second_time) { Time.zone.parse('2012-07-17 08:10:15') }
231
+
232
+ before { Timecop.freeze(first_time) }
233
+ after { Timecop.return }
234
+
235
+ it "should be updated on change" do
236
+ info = @customer.create_spec_customer_info :spec_person_name_id => 1
237
+ info.updated_at.should == first_time
238
+ info.created_at.should == first_time
239
+
240
+ Timecop.freeze(second_time)
241
+ info.spec_person_name_id = 2
242
+ info.save!
243
+ info.updated_at.should == second_time
244
+ info.created_at.should == second_time
245
+ end
246
+ end
247
+
248
+ describe "Optimistic Locking" do
249
+ before do
250
+ @info1 = @customer.reload.spec_customer_info
251
+ @info2 = @customer.reload.spec_customer_info
252
+ end
253
+
254
+ it "should raise stale object error" do
255
+ @info1.update_attributes(:spec_person_name_id => 3)
256
+
257
+ expect{ @info2.update_attributes(:spec_person_name_id => 4) }.to raise_error(ActiveRecord::StaleObjectError)
258
+ end
259
+
260
+ it "should not fail if no locking_column present" do
261
+ email = SpecCustomerEmail.create(:spec_customer_id => 1, :email => 'foo@bar.com')
262
+ expect{ email.update_attributes(:email => 'foo2@bar.com') }.not_to raise_error
263
+ end
264
+ end
265
+
266
+ describe 'with Aggregated' do
267
+ before do
268
+ @info.spec_person_name = SpecPersonName.create(:first_name => 'John', :last_name => 'Smith')
269
+ @info.save
270
+ @info.reload
271
+ end
272
+
273
+ it "should supersede when nested record changes" do
274
+ old_id = @info.id
275
+ @customer.spec_customer_info.spec_person_name.first_name = 'Alice'
276
+ expect{ @customer.save }.to change(@info, :spec_person_name_id)
277
+ @info.id.should_not == old_id
278
+ @info.is_current.should == true
279
+ SpecCustomerInfo.find(old_id).is_current.should be_false
280
+ end
281
+ end
282
+ end
283
+
284
+ describe 'Delegations' do
285
+ before do
286
+ @customer = SpecCustomer.create(
287
+ :spec_customer_info_attributes => {
288
+ :spec_person_name_attributes => {:first_name => ' John ', :last_name => 'Smith'} } )
289
+ @info = @customer.spec_customer_info
290
+ end
291
+
292
+ it "should have delegated column information" do
293
+ @customer.column_for_attribute(:first_name).should_not be_nil
294
+ end
295
+
296
+ it "should not delegate special methods" do
297
+ @customer.should_not respond_to(:reset_first_name)
298
+ @customer.should_not respond_to(:first_name_was)
299
+ @customer.should_not respond_to(:first_name_before_type_cast)
300
+ @customer.should_not respond_to(:first_name_will_change!)
301
+ @customer.should_not respond_to(:first_name_changed?)
302
+ @customer.should_not respond_to(:lock_version)
303
+ end
304
+
305
+ it "should delegate methods to aggregated parts" do
306
+ @info.should respond_to(:first_name)
307
+ @info.should respond_to(:first_name=)
308
+ @info.should respond_to(:spec_suffix)
309
+ @info.last_name.should == 'Smith'
310
+ end
311
+
312
+ it "should delegate methods to representation" do
313
+ @customer.should respond_to(:first_name)
314
+ @customer.should respond_to(:first_name=)
315
+ @customer.should respond_to(:spec_suffix)
316
+ @customer.last_name.should == 'Smith'
317
+ @customer.should respond_to(:custom_field)
318
+ @customer.should respond_to(:custom_field=)
319
+ end
320
+
321
+ it 'should properly delegate enumerated attributes' do
322
+ @customer.should respond_to(:spec_type)
323
+ @customer.should respond_to(:spec_type=)
324
+ @customer.spec_type = :important
325
+ @customer.spec_type.should === :important
326
+ end
327
+
328
+ it "should raise NoMethodError if unknown method received" do
329
+ expect{ @customer.impossibru }.to raise_error(NoMethodError)
330
+ end
331
+ end
332
+ end