cached_belongs_to 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,6 @@
1
+ require 'autotest/growl'
2
+
3
+ Autotest.add_hook :initialize do |at|
4
+ at.add_exception 'test.log'
5
+ end
6
+
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ test.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cached_belongs_to.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 David Padilla
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # CachedBelongsTo
2
+
3
+ [![Build Status](https://secure.travis-ci.org/crowdint/cached_belongs_to.png)](http://travis-ci.org/crowdint/cached_belongs_to)
4
+
5
+ Back in the 80's disk space was precious. That's why we came out with table "normalization" for relational databases.
6
+
7
+ While this concept is still valid, disk space has become ridicously cheap, and,
8
+ there's a lot of cases where performance is more important than disk space.
9
+
10
+ ## Can't you just use eager loading?
11
+
12
+ You could! But, eager loading still loads the models into memory. Such a memory waste, specially if the parent models are big.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'cached_belongs_to'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install cached_belongs_to
27
+
28
+ ## Usage
29
+
30
+ Just use it as you would use `belongs_to`. Add the `:caches` option to specify the
31
+ fields in the association that you want to cache.
32
+
33
+ #
34
+ # table: authors
35
+ #
36
+ # name:string
37
+ #
38
+ class Author
39
+ has_many :books
40
+ end
41
+
42
+ class Book
43
+ cached_belongs_to :author, :caches => :name
44
+ end
45
+
46
+ You will need a database field in the books table to hold the cached value:
47
+
48
+ class AddCachedBelongsToBooks < ActiveRecord::Migration
49
+ def change
50
+ add_column :books, :author_name, :string
51
+ end
52
+ end
53
+
54
+ And, that's it. Everytime you hange the Author, it will come back and update
55
+ the cached fields on the Book model.
56
+
57
+ So, now on your views you can just call:
58
+
59
+ book.author_name
60
+
61
+ And won't pay the price of loading the whole Author model in memory, nor the query
62
+ time.
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create new Pull Request
71
+
72
+ # About the Author
73
+
74
+ [Crowd Interactive](http://www.crowdint.com) is an American web design and development company that happens to work in Colima, Mexico.
75
+ We specialize in building and growing online retail stores. We don’t work with everyone – just companies we believe in. Call us today to see if there’s a fit.
76
+ Find more info [here](http://www.crowdint.com)!
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+ require 'cucumber/rake/task'
5
+
6
+ RSpec::Core::RakeTask.new
7
+
8
+ Cucumber::Rake::Task.new
9
+
10
+ task :default => [ :spec, :cucumber ]
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/cached_belongs_to/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["David Padilla"]
6
+ gem.email = ["david@crowdint.com"]
7
+ gem.description = %q{Denormalize your belongs_to associations}
8
+ gem.summary = %q{Denormalize your belongs_to associations}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "cached_belongs_to"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = CachedBelongsTo::VERSION
17
+
18
+ gem.add_dependency 'activerecord', '~> 3.2'
19
+
20
+ gem.add_development_dependency 'autotest'
21
+ gem.add_development_dependency 'autotest-growl'
22
+ gem.add_development_dependency 'cucumber'
23
+ gem.add_development_dependency 'sqlite3'
24
+ gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'rspec'
26
+ end
@@ -0,0 +1,4 @@
1
+ <%
2
+ std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --tags ~@wip --require features/step_definitions --require features/support"
3
+ %>
4
+ default: <%= std_opts %> --drb --color features
File without changes
@@ -0,0 +1,39 @@
1
+ Given /^model "(.*?)" exists with attributes:$/ do |model_class_name, attributes|
2
+ klass = Class.new(ActiveRecord::Base)
3
+ suppress_warnings { Object.const_set model_class_name, klass }
4
+
5
+ ActiveRecord::Schema.define(:version => 0) do
6
+ create_table model_class_name.tableize, :force => true do |t|
7
+ attributes.hashes.each do |row|
8
+ t.send(row["type"], row["attribute"])
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ Given /^a (.*?) exists:$/ do |model_name, table|
15
+ @models ||= {}
16
+ @models[model_name] = model_name.constantize.new
17
+ table.rows_hash.each do |attribute, value|
18
+ @models[model_name].send("#{attribute}=", value)
19
+ end
20
+ @models[model_name].save!
21
+ end
22
+
23
+ Given /^that (.*?) belongs to that (.*?)$/ do |child, parent|
24
+ @models[parent].send(child.underscore.pluralize).send(:push, @models[child])
25
+
26
+ #@models[child].send("#{parent.underscore}=", @models[parent])
27
+ end
28
+
29
+ When /^I save the (.*?)$/ do |model_name|
30
+ @models[model_name].save!
31
+ end
32
+
33
+ When /^I change the (.*?)'s name to "(.*?)"$/ do |model_name, new_name|
34
+ @models[model_name].name = new_name
35
+ end
36
+
37
+ Given /^(.*?) has many (.*?)$/ do |parent, child|
38
+ parent.constantize.send(:has_many, child.underscore)
39
+ end
@@ -0,0 +1,7 @@
1
+ Given /^(.*?) cached_belongs_to (.*?) with cached: "(.*?)"$/ do |model1, model2, cached_attribute|
2
+ model1.constantize.send(:cached_belongs_to, model2.underscore.to_sym, :caches => cached_attribute)
3
+ end
4
+
5
+ Then /^(.*?)'s author name should be "(.*?)"$/ do |model_name, author_name|
6
+ @models[model_name].reload.author_name.should == author_name
7
+ end
@@ -0,0 +1,6 @@
1
+ require 'cached_belongs_to'
2
+ require './spec/support/warnings.rb'
3
+
4
+ ActiveRecord::Migration.verbose = false
5
+ ActiveRecord::Base.logger = Logger.new("test.log")
6
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
@@ -0,0 +1,20 @@
1
+ Feature: Update cached attributes after saving the child record
2
+
3
+ Scenario:
4
+ Given model "Author" exists with attributes:
5
+ | attribute | type |
6
+ | name | string |
7
+ And model "Book" exists with attributes:
8
+ | attribute | type |
9
+ | title | string |
10
+ | author_id | integer |
11
+ | author_name | string |
12
+ And Book cached_belongs_to Author with cached: "name"
13
+ And Author has many Books
14
+ And a Author exists:
15
+ | name | John Mellencamp |
16
+ And a Book exists:
17
+ | title | Treasure Island |
18
+ And that Book belongs to that Author
19
+ When I save the Book
20
+ Then Book's author name should be "John Mellencamp"
@@ -0,0 +1,21 @@
1
+ Feature: Update cached attributes after saving the parent record
2
+
3
+ Scenario:
4
+ Given model "Author" exists with attributes:
5
+ | attribute | type |
6
+ | name | string |
7
+ And model "Book" exists with attributes:
8
+ | attribute | type |
9
+ | title | string |
10
+ | author_id | integer |
11
+ | author_name | string |
12
+ And Book cached_belongs_to Author with cached: "name"
13
+ And Author has many Books
14
+ And a Author exists:
15
+ | name | John Mellencamp |
16
+ And a Book exists:
17
+ | title | Treasure Island |
18
+ And that Book belongs to that Author
19
+ When I change the Author's name to "Deeprak Chopa"
20
+ And I save the Author
21
+ Then Book's author name should be "Deeprak Chopa"
@@ -0,0 +1,45 @@
1
+ require "cached_belongs_to/version"
2
+ require 'active_record'
3
+
4
+ module CachedBelongsTo
5
+ module ClassMethods
6
+ def cached_belongs_to(*args)
7
+ caches = Array(args[1].delete(:caches))
8
+ klass = args[0]
9
+
10
+ belongs_to(*args)
11
+ children_hook_name = "cached_belongs_to_#{name.underscore}_after_save".to_sym
12
+ create_cached_belongs_to_child_hooks(caches, klass, children_hook_name)
13
+ create_cached_belongs_to_parent_hooks(caches, klass, children_hook_name)
14
+ end
15
+
16
+ def create_cached_belongs_to_child_hooks(*args, caches, klass, children_hook_name)
17
+ define_method children_hook_name do
18
+ caches.each do |attr|
19
+ send("#{klass}_#{attr}=", send(klass).send(attr)) if send(klass)
20
+ end
21
+ end
22
+
23
+ before_save children_hook_name
24
+ end
25
+
26
+ def create_cached_belongs_to_parent_hooks(caches, parent_class_name, children_hook_name)
27
+ method_name = "cached_belongs_to_#{parent_class_name}_after_save".to_sym
28
+ has_many_association = self.name.underscore.pluralize.to_sym
29
+ parent_class = parent_class_name.to_s.camelize.constantize
30
+
31
+ parent_class.send(:define_method, method_name) do
32
+ send(has_many_association).reload.each do |child|
33
+ caches.each do |attr|
34
+ child.send(children_hook_name)
35
+ child.save
36
+ end
37
+ end
38
+ end
39
+
40
+ parent_class.send(:after_save, method_name)
41
+ end
42
+ end
43
+ end
44
+
45
+ ActiveRecord::Base.extend(CachedBelongsTo::ClassMethods)
@@ -0,0 +1,3 @@
1
+ module CachedBelongsTo
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe CachedBelongsTo do
4
+ let(:book_class) do
5
+ klass = Class.new(ActiveRecord::Base)
6
+ suppress_warnings { Object.const_set 'Book', klass }
7
+ klass
8
+ end
9
+
10
+ let(:author_class) do
11
+ klass = Class.new(ActiveRecord::Base)
12
+ suppress_warnings { Object.const_set 'Author', klass }
13
+ klass
14
+ end
15
+
16
+ before do
17
+ author_class
18
+ end
19
+
20
+ it "Adds the cached_belongs_to class method to ActiveRecord models" do
21
+ book_class.should respond_to(:cached_belongs_to)
22
+ end
23
+
24
+ context "cached_belongs_to" do
25
+ before do
26
+ book_class.send(:cached_belongs_to, :author, { :caches => :name })
27
+ end
28
+
29
+ it "creates the belongs_to association as usual" do
30
+ book_class.new.should respond_to(:author)
31
+ end
32
+
33
+ it "defines the cached_belongs_to_book_after_save" do
34
+ book_class.new.should respond_to(:cached_belongs_to_book_after_save)
35
+ end
36
+
37
+ it "defines the cached_belongs_to_author_after_save" do
38
+ author_class.new.should respond_to(:cached_belongs_to_author_after_save)
39
+ end
40
+ end
41
+
42
+ context "cached_belongs_to_book_after_save" do
43
+ before do
44
+ book_class.send(:cached_belongs_to, :author, { :caches => :name })
45
+ end
46
+
47
+ it "assigns all the cached values from the parent association to the cached attributes" do
48
+ author = author_class.new :name => 'John Mellencamp'
49
+ book = book_class.new
50
+ book.author = author
51
+
52
+ book.stub(:author).and_return author
53
+
54
+ book.cached_belongs_to_book_after_save
55
+ book.author_name.should eq author.name
56
+ end
57
+ end
58
+
59
+ context "cached_belongs_to_author_after_save" do
60
+ before do
61
+ book_class.send(:cached_belongs_to, :author, { :caches => :name })
62
+ author_class.send(:has_many, :books)
63
+ end
64
+
65
+ it "assigns all the cached values from the parent association to the cached attributes" do
66
+ author = author_class.new :name => 'John Mellencamp'
67
+ book = book_class.new
68
+
69
+ author.stub_chain(:books, :reload).and_return([ book ])
70
+ book.should_receive(:cached_belongs_to_book_after_save)
71
+ book.should_receive :save
72
+
73
+ author.cached_belongs_to_author_after_save
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,32 @@
1
+ require 'cached_belongs_to'
2
+ require 'logger'
3
+
4
+ require 'support/warnings'
5
+
6
+ ActiveRecord::Migration.verbose = false
7
+ ActiveRecord::Base.logger = Logger.new("test.log")
8
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
9
+
10
+ ActiveRecord::Schema.define(:version => 0) do
11
+ create_table "books" do |t|
12
+ t.string "title"
13
+ t.integer "author_id"
14
+ t.string "author_name"
15
+ end
16
+
17
+ create_table "authors" do |t|
18
+ t.string "name"
19
+ end
20
+ end
21
+
22
+ RSpec.configure do |config|
23
+ config.treat_symbols_as_metadata_keys_with_true_values = true
24
+ config.run_all_when_everything_filtered = true
25
+ config.filter_run :focus
26
+
27
+ # Run specs in random order to surface order dependencies. If you find an
28
+ # order dependency and want to debug it, you can fix the order by providing
29
+ # the seed, which is printed after each run.
30
+ # --seed 1234
31
+ config.order = 'random'
32
+ end
@@ -0,0 +1,9 @@
1
+ module Kernel
2
+ def suppress_warnings
3
+ original_verbosity = $VERBOSE
4
+ $VERBOSE = nil
5
+ result = yield
6
+ $VERBOSE = original_verbosity
7
+ return result
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cached_belongs_to
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Padilla
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
30
+ - !ruby/object:Gem::Dependency
31
+ name: autotest
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: autotest-growl
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: cucumber
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: sqlite3
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rspec
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
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
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Denormalize your belongs_to associations
127
+ email:
128
+ - david@crowdint.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .autotest
134
+ - .gitignore
135
+ - .rspec
136
+ - .travis.yml
137
+ - Gemfile
138
+ - LICENSE
139
+ - README.md
140
+ - Rakefile
141
+ - cached_belongs_to.gemspec
142
+ - config/cucumber.yml
143
+ - features/step_definitions/.gitkeep
144
+ - features/step_definitions/active_record_model_steps.rb
145
+ - features/step_definitions/cached_belongs_to_steps.rb
146
+ - features/support/env.rb
147
+ - features/update_cached_attributes_after_save_child_record.feature
148
+ - features/update_cached_attributes_after_save_parent_record.feature
149
+ - lib/cached_belongs_to.rb
150
+ - lib/cached_belongs_to/version.rb
151
+ - spec/cached_belongs_to_spec.rb
152
+ - spec/spec_helper.rb
153
+ - spec/support/warnings.rb
154
+ homepage: ''
155
+ licenses: []
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ segments:
167
+ - 0
168
+ hash: -552693176683226932
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ segments:
176
+ - 0
177
+ hash: -552693176683226932
178
+ requirements: []
179
+ rubyforge_project:
180
+ rubygems_version: 1.8.23
181
+ signing_key:
182
+ specification_version: 3
183
+ summary: Denormalize your belongs_to associations
184
+ test_files:
185
+ - features/step_definitions/.gitkeep
186
+ - features/step_definitions/active_record_model_steps.rb
187
+ - features/step_definitions/cached_belongs_to_steps.rb
188
+ - features/support/env.rb
189
+ - features/update_cached_attributes_after_save_child_record.feature
190
+ - features/update_cached_attributes_after_save_parent_record.feature
191
+ - spec/cached_belongs_to_spec.rb
192
+ - spec/spec_helper.rb
193
+ - spec/support/warnings.rb