association_callbacks 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.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ .bundle
3
+ .rbenv-version
4
+ pkg/*
5
+ spec/test.sqlite3
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,89 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ association_callbacks (0.0.1)
5
+ activerecord (~> 3.2.0)
6
+ activesupport (~> 3.2.0)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ actionpack (3.2.2)
12
+ activemodel (= 3.2.2)
13
+ activesupport (= 3.2.2)
14
+ builder (~> 3.0.0)
15
+ erubis (~> 2.7.0)
16
+ journey (~> 1.0.1)
17
+ rack (~> 1.4.0)
18
+ rack-cache (~> 1.1)
19
+ rack-test (~> 0.6.1)
20
+ sprockets (~> 2.1.2)
21
+ activemodel (3.2.2)
22
+ activesupport (= 3.2.2)
23
+ builder (~> 3.0.0)
24
+ activerecord (3.2.2)
25
+ activemodel (= 3.2.2)
26
+ activesupport (= 3.2.2)
27
+ arel (~> 3.0.2)
28
+ tzinfo (~> 0.3.29)
29
+ activesupport (3.2.2)
30
+ i18n (~> 0.6)
31
+ multi_json (~> 1.0)
32
+ arel (3.0.2)
33
+ builder (3.0.0)
34
+ diff-lcs (1.1.3)
35
+ erubis (2.7.0)
36
+ hike (1.2.1)
37
+ i18n (0.6.0)
38
+ journey (1.0.3)
39
+ json (1.6.6)
40
+ multi_json (1.2.0)
41
+ rack (1.4.1)
42
+ rack-cache (1.2)
43
+ rack (>= 0.4)
44
+ rack-ssl (1.3.2)
45
+ rack
46
+ rack-test (0.6.1)
47
+ rack (>= 1.0)
48
+ railties (3.2.2)
49
+ actionpack (= 3.2.2)
50
+ activesupport (= 3.2.2)
51
+ rack-ssl (~> 1.3.2)
52
+ rake (>= 0.8.7)
53
+ rdoc (~> 3.4)
54
+ thor (~> 0.14.6)
55
+ rake (0.9.2.2)
56
+ rdoc (3.12)
57
+ json (~> 1.4)
58
+ rspec (2.9.0)
59
+ rspec-core (~> 2.9.0)
60
+ rspec-expectations (~> 2.9.0)
61
+ rspec-mocks (~> 2.9.0)
62
+ rspec-core (2.9.0)
63
+ rspec-expectations (2.9.0)
64
+ diff-lcs (~> 1.1.3)
65
+ rspec-mocks (2.9.0)
66
+ rspec-rails (2.9.0)
67
+ actionpack (>= 3.0)
68
+ activesupport (>= 3.0)
69
+ railties (>= 3.0)
70
+ rspec (~> 2.9.0)
71
+ sprockets (2.1.2)
72
+ hike (~> 1.2)
73
+ rack (~> 1.0)
74
+ tilt (~> 1.1, != 1.3.0)
75
+ sqlite3 (1.3.5)
76
+ sqlite3-ruby (1.3.3)
77
+ sqlite3 (>= 1.3.3)
78
+ thor (0.14.6)
79
+ tilt (1.3.3)
80
+ tzinfo (0.3.32)
81
+
82
+ PLATFORMS
83
+ ruby
84
+
85
+ DEPENDENCIES
86
+ association_callbacks!
87
+ rspec (~> 2.9.0)
88
+ rspec-rails (~> 2.9.0)
89
+ sqlite3-ruby
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Alexis Toulotte
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.mdown ADDED
@@ -0,0 +1,93 @@
1
+ # AssociationCallbacks
2
+
3
+ Provides a way to define [callbacks](http://api.rubyonrails.org/classes/ActiveModel/Callbacks.html)
4
+ of one [ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)
5
+ model into an associated one.
6
+
7
+ ## Example
8
+
9
+ First, two simple `Article` and `Comment` models:
10
+
11
+ class Article < ActiveRecord::Base
12
+ has_many :comments
13
+ end
14
+
15
+ class Comment < ActiveRecord::Base
16
+ belongs_to :article
17
+ end
18
+
19
+ Then, you often need to denormalize `Comment` stuff into `Post` or vice versa.
20
+ Here is the standard way to denormalize `last_comment_at` on posts:
21
+
22
+ class Comment < ActiveRecord::Base
23
+ belongs_to :article
24
+
25
+ after_create :update_post_last_comment_at
26
+ after_destroy :update_post_last_comment_at
27
+
28
+ def update_post_last_comment_at
29
+ post.update_attributes!(:last_comment_at => post.comments.order('created_at').last.try(:created_at))
30
+ end
31
+ end
32
+
33
+ But, there is a problem here: we define `Post` denormalization callbacks into
34
+ `Comment` model. IMHO, this is the wrong place. `Post` denormalization
35
+ **should** be in `Post` model in order to have less relationship between
36
+ models.
37
+
38
+ Here is how to do it with `association_callabacks`:
39
+
40
+ class Post < ActiveRecord::Base
41
+ has_many :comments
42
+
43
+ after_create :update_last_comment_at, :source => :comments
44
+ after_destroy :update_last_comment_at, :source => :comments
45
+
46
+ def update_last_comment_at
47
+ update_attributes!(:last_comment_at => comments.order('created_at').last.try(:created_at))
48
+ end
49
+ end
50
+
51
+ You just have to specify `:source` option to your callbacks, and that's all!
52
+ Note that `:source` option must be an existing association name.
53
+
54
+ Note that association callbacks methods can take associated record as
55
+ argument, above code can be:
56
+
57
+ class Post < ActiveRecord::Base
58
+ has_many :comments
59
+
60
+ after_create :update_last_comment_at
61
+
62
+ def update_last_comment_at(comment)
63
+ update_attributes!(:last_comment_at => comment.created_at)
64
+ end
65
+ end
66
+
67
+ Association callbacks can also be defined as block:
68
+
69
+ class Post < ActiveRecord::Base
70
+ has_many :comments
71
+
72
+ after_destroy :source => :comments do |post|
73
+ post.decrement!(:comments_count)
74
+ end
75
+ end
76
+
77
+ Another solution is to use [ActiveModel Observers](http://api.rubyonrails.org/classes/ActiveModel/Observer.html),
78
+ but for a better project comprehension, I really prefer placing denormalization
79
+ directly into model. Observers are more designed for emails notifications,
80
+ cache sweepers, etc.
81
+
82
+ ## Installation
83
+
84
+ Just add this into your `Gemfile`:
85
+
86
+ gem 'association_callabacks'
87
+
88
+ Then, just run `bundle install`.
89
+
90
+ ## Executing test suite
91
+
92
+ This project is fully tested with [Rspec 2](http://github.com/rspec/rspec).
93
+ Just run `bundle exec rake` (after a `bundle install`).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ desc 'Default: runs specs.'
7
+ task :default => :spec
8
+
9
+ desc 'Run all specs in spec directory.'
10
+ RSpec::Core::RakeTask.new(:spec)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'association_callbacks'
3
+ s.version = File.read(File.expand_path(File.dirname(__FILE__) + '/VERSION')).strip
4
+ s.platform = Gem::Platform::RUBY
5
+ s.author = 'Alexis Toulotte'
6
+ s.email = 'al@alweb.org'
7
+ s.homepage = 'https://github.com/alexistoulotte/association_callbacks'
8
+ s.summary = 'Callbacks for ActiveRecord associations'
9
+ s.description = 'Provides a way to define callbacks of one ActiveRecord model into an associated one'
10
+
11
+ s.rubyforge_project = 'association_callbacks'
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ['lib']
17
+
18
+ s.add_dependency 'activerecord', '~> 3.2.0'
19
+ s.add_dependency 'activesupport', '~> 3.2.0'
20
+
21
+ s.add_development_dependency 'rspec', '~> 2.9.0'
22
+ s.add_development_dependency 'rspec-rails', '~> 2.9.0'
23
+ s.add_development_dependency 'sqlite3-ruby'
24
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/lib/association_callbacks')
@@ -0,0 +1,89 @@
1
+ module AssociationCallbacks
2
+
3
+ module ActiveRecord
4
+
5
+ CALLBACKS = %w(
6
+ after_commit
7
+ after_create
8
+ after_destroy
9
+ after_save
10
+ after_update
11
+ after_validation
12
+ before_create
13
+ before_destroy
14
+ before_save
15
+ before_update
16
+ before_validation
17
+ ).freeze
18
+
19
+ extend ActiveSupport::Concern
20
+
21
+ included do |base|
22
+ CALLBACKS.each do |callback|
23
+ base.class_eval <<-EOS
24
+ class << self
25
+
26
+ def #{callback}_with_association(*args, &block)
27
+ if args.last.is_a?(Hash) && args.last.key?(:source)
28
+ options = args.extract_options!
29
+ define_callback_with_association(:#{callback}, args, options, &block)
30
+ else
31
+ #{callback}_without_association(*args, &block)
32
+ end
33
+ end
34
+ alias_method_chain :#{callback}, :association
35
+
36
+ end
37
+ EOS
38
+ end
39
+ end
40
+
41
+ module ClassMethods
42
+
43
+ private
44
+
45
+ def define_callback_with_association(callback, methods, options = {}, &block)
46
+ relation_name = options.delete(:source).try(:to_sym)
47
+ relation = reflect_on_association(relation_name) || raise(ArgumentError.new("No such association: #{name}.#{relation_name}"))
48
+ inverse_relation_name = relation.inverse_of.try(:name).presence || raise(ArgumentError.new("You must specify :inverse_of on #{name}.#{relation_name} to use association callbacks"))
49
+ relation.klass.send(callback, options) do |record|
50
+ invoke_callback_with_association(record, record.send(inverse_relation_name), methods, &block)
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ private
57
+
58
+ def invoke_callback_block_with_association(record, inverse_record, &block)
59
+ yield(inverse_record, record) if block_given?
60
+ end
61
+
62
+ def invoke_callback_methods_with_association(record, inverse_record, methods)
63
+ methods.each do |method|
64
+ if inverse_record.method(method).arity == 1
65
+ inverse_record.send(method, record)
66
+ else
67
+ inverse_record.send(method)
68
+ end
69
+ end
70
+ end
71
+
72
+ def invoke_callback_with_association(record, assocation, methods, &block)
73
+ return if assocation.nil?
74
+ if assocation.respond_to?(:find_each)
75
+ assocation.find_each do |inverse_record|
76
+ invoke_callback_methods_with_association(record, inverse_record, methods)
77
+ invoke_callback_block_with_association(record, inverse_record, &block)
78
+ end
79
+ elsif assocation.is_a?(::ActiveRecord::Base)
80
+ invoke_callback_methods_with_association(record, assocation, methods)
81
+ invoke_callback_block_with_association(record, assocation, &block)
82
+ else
83
+ raise "Assocation can't be used by callbacks: #{association.inspect}"
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+ end
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'active_record'
4
+ require 'active_support/concern'
5
+
6
+ module AssociationCallbacks
7
+
8
+ class << self
9
+
10
+ def version
11
+ @@version ||= File.read(File.expand_path(File.dirname(__FILE__) + '/../VERSION')).strip.freeze
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+
18
+ lib_path = File.expand_path(File.dirname(__FILE__) + '/association_callbacks')
19
+
20
+ require "#{lib_path}/active_record"
21
+
22
+ ActiveRecord::Base.send(:include, AssociationCallbacks::ActiveRecord)
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe AssociationCallbacks::ActiveRecord do
4
+
5
+ context 'with has_many relation' do
6
+
7
+ it 'invokes callback defines as method' do
8
+ post = Post.create!
9
+ expect {
10
+ Comment.create!(:post_id => post.id)
11
+ }.to change { post.reload.comments_count }.by(1)
12
+ end
13
+
14
+ it 'invokes callback defines as method (with one argument)' do
15
+ post = Post.create!
16
+ expect {
17
+ Comment.create!(:post => post, :created_at => 2.years.ago)
18
+ }.to change { post.reload.last_comment_at }.from(nil)
19
+ post.last_comment_at.to_i.should be_within(5).of(2.years.ago.to_i)
20
+ end
21
+
22
+ it 'invokes callback defines as block' do
23
+ post = Post.create!
24
+ comment = Comment.create!(:post => post)
25
+ expect {
26
+ comment.destroy
27
+ }.to change { post.reload.comments_count }.by(-1)
28
+ end
29
+
30
+ it 'invokes regular callbacks' do
31
+ post = Post.new(:body => 'this is body', :title => 'foo')
32
+ expect {
33
+ post.save!
34
+ }.to change { post.texts }.from(nil).to('foo,this,is,body')
35
+ end
36
+
37
+ end
38
+
39
+ context 'with belongs_to relation' do
40
+
41
+ it 'invokes callback defines as method' do
42
+ post = Post.create!
43
+ comment = post.comments.create!
44
+ expect {
45
+ post.update_attributes!(:title => 'foo')
46
+ }.to change { comment.reload.post_updated_at }
47
+ comment.post_updated_at.to_i.should be_within(5).of(Time.now.to_i)
48
+ end
49
+
50
+ it 'invokes callback defines as method (with one argument)' do
51
+ post = Post.create!(:title => 'foo')
52
+ comment = post.comments.create!
53
+ expect {
54
+ post.update_attributes!(:title => 'bar')
55
+ }.to change { comment.reload.post.title }.from('foo').to('bar')
56
+ end
57
+
58
+ it 'invokes callback defines as block' do
59
+ post = Post.create!
60
+ comment = Comment.create!(:post_id => post.id)
61
+ expect {
62
+ post.destroy
63
+ }.to change { comment.reload.orphan_from_id }.from(nil).to(post.id)
64
+ end
65
+
66
+ it 'invokes regular callbacks' do
67
+ post = Post.create!(:title => 'foo')
68
+ comment = Comment.new(:post => post)
69
+ expect {
70
+ comment.save!
71
+ }.to change { comment.post_title }.from(nil).to('foo')
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe AssociationCallbacks do
4
+
5
+ describe '.version' do
6
+
7
+ it 'is a string' do
8
+ AssociationCallbacks.version.should be_a(String)
9
+ end
10
+
11
+ it 'is with correct format' do
12
+ AssociationCallbacks.version.should match(/^\d+\.\d+(\.\d+)?$/)
13
+ end
14
+
15
+ it 'is freezed' do
16
+ expect {
17
+ AssociationCallbacks.version.gsub!('.', '#')
18
+ }.to raise_error(/can't modify frozen string/i)
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,26 @@
1
+ class Comment < ActiveRecord::Base
2
+
3
+ belongs_to :post, :inverse_of => :comments
4
+
5
+ after_save :update_post_updated_at, :source => :post
6
+ before_create :set_post_title
7
+ before_save :update_post_title, :source => :post
8
+ after_destroy :source => :post do |comment, post|
9
+ comment.update_attributes!(:orphan_from_id => post.id)
10
+ end
11
+
12
+ private
13
+
14
+ def set_post_title
15
+ self.post_title = post.title
16
+ end
17
+
18
+ def update_post_title(post)
19
+ update_attributes!(:post_title => post.title) if post.title_changed?
20
+ end
21
+
22
+ def update_post_updated_at
23
+ update_attributes!(:post_updated_at => post.updated_at)
24
+ end
25
+
26
+ end
@@ -0,0 +1,23 @@
1
+ class Post < ActiveRecord::Base
2
+
3
+ has_many :comments, :inverse_of => :post
4
+
5
+ after_create :increment_comments_count, :update_last_comment_at, :source => :comments
6
+ after_destroy :source => :comments do |post|
7
+ post.decrement!(:comments_count)
8
+ end
9
+ before_save do |post|
10
+ post.texts = "#{post.title} #{post.body}".strip.split(/\s+/).join(',')
11
+ end
12
+
13
+ private
14
+
15
+ def increment_comments_count
16
+ increment!(:comments_count)
17
+ end
18
+
19
+ def update_last_comment_at(comment)
20
+ update_attributes!(:last_comment_at => comment.created_at)
21
+ end
22
+
23
+ end
@@ -0,0 +1,16 @@
1
+ ENV["RAILS_ENV"] ||= 'test'
2
+
3
+ require File.dirname(__FILE__) + '/../lib/association_callbacks'
4
+
5
+ # Support
6
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
7
+
8
+ # Mocks
9
+ ActiveSupport::Dependencies.autoload_paths << "#{File.dirname(__FILE__)}/mocks"
10
+
11
+ RSpec.configure do |config|
12
+ config.before(:each) do
13
+ Comment.delete_all
14
+ Post.delete_all
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => "#{File.dirname(__FILE__)}/../../test.sqlite3", :timeout => 5000)
2
+
3
+ ActiveRecord::Base.connection.create_table(:comments, :force => true) do |t|
4
+ t.integer :orphan_from_id
5
+ t.datetime :post_updated_at
6
+ t.integer :post_id
7
+ t.string :post_title
8
+ t.timestamps
9
+ end
10
+ ActiveRecord::Base.connection.create_table(:posts, :force => true) do |t|
11
+ t.text :body
12
+ t.integer :comments_count, :default => 0
13
+ t.datetime :last_comment_at
14
+ t.text :texts
15
+ t.string :title
16
+ t.timestamps
17
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: association_callbacks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alexis Toulotte
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: &70294690344260 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70294690344260
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70294690343080 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 3.2.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70294690343080
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70294690341960 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.9.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70294690341960
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec-rails
49
+ requirement: &70294690340660 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 2.9.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70294690340660
58
+ - !ruby/object:Gem::Dependency
59
+ name: sqlite3-ruby
60
+ requirement: &70294690339420 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70294690339420
69
+ description: Provides a way to define callbacks of one ActiveRecord model into an
70
+ associated one
71
+ email: al@alweb.org
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - MIT-LICENSE
81
+ - README.mdown
82
+ - Rakefile
83
+ - VERSION
84
+ - association_callbacks.gemspec
85
+ - init.rb
86
+ - lib/association_callbacks.rb
87
+ - lib/association_callbacks/active_record.rb
88
+ - spec/association_callbacks/active_record_spec.rb
89
+ - spec/association_callbacks_spec.rb
90
+ - spec/mocks/comment.rb
91
+ - spec/mocks/post.rb
92
+ - spec/spec_helper.rb
93
+ - spec/support/bootsrap/database.rb
94
+ homepage: https://github.com/alexistoulotte/association_callbacks
95
+ licenses: []
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ segments:
107
+ - 0
108
+ hash: -2681495212806614534
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ segments:
116
+ - 0
117
+ hash: -2681495212806614534
118
+ requirements: []
119
+ rubyforge_project: association_callbacks
120
+ rubygems_version: 1.8.17
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Callbacks for ActiveRecord associations
124
+ test_files:
125
+ - spec/association_callbacks/active_record_spec.rb
126
+ - spec/association_callbacks_spec.rb
127
+ - spec/mocks/comment.rb
128
+ - spec/mocks/post.rb
129
+ - spec/spec_helper.rb
130
+ - spec/support/bootsrap/database.rb