cached_belongs_to 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/.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