association_callbacks 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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