ar-simple-idmap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ spec/debug.log
2
+ .DS_Store
3
+ spec/db/*.sqlite*
4
+ *~
data/COPYING ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Sokolov Yura aka funny_falcon
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 ADDED
@@ -0,0 +1,61 @@
1
+ IdentityMap
2
+ ===========
3
+
4
+ Adds simple hand controlled identity map for ActiveRecord.
5
+
6
+ Installing
7
+ ==========
8
+
9
+ gem install ar-simple-identity-map
10
+
11
+ or
12
+
13
+
14
+ Enabling
15
+ ========
16
+
17
+ To enable in ApplicationController (it is not enabled by default).
18
+
19
+ class ApplicationController < ActionController::Base
20
+ use_identity_map #(installs around filter)
21
+ # or use_identity_map :only=>[:index, :show]
22
+ # or use_identity_map :except=>[:update]
23
+ end
24
+
25
+ If you decide to disable filter in sub controllers:
26
+
27
+ class ClientController < ApplicationController
28
+ dont_use_identity_map :only=>[:save, :update, :messy_action]
29
+ end
30
+
31
+ Then you should enable identity map for each model class individually:
32
+
33
+ class TarifPlan < ActiveRecord::Base
34
+ use_id_map
35
+ has_many :clients
36
+ end
37
+
38
+ class Client < ActiveRecord::Base
39
+ use_id_map
40
+ belongs_to :tarif_plan
41
+ end
42
+
43
+ To enable in rake task or script:
44
+
45
+ ActiveRecord.with_id_map do
46
+ # all things here goes with identity map
47
+ end
48
+
49
+ If you found that identity logic does wrong thing in some particular place,
50
+ you could temporary disable it:
51
+
52
+ ActiveRecord.without_id_map do
53
+ # all things here goes without identity map
54
+ end
55
+
56
+
57
+ Copyright
58
+ =========
59
+
60
+ inspired by http://github.com/pjdavis/identity-map
61
+ Copyright (c) 2010 Sokolov Yura aka funny_falcon, released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the identity_map plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the identity_map plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'IdentityMap'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ begin
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gemspec|
28
+ gemspec.name = "ar-simple-idmap"
29
+ gemspec.summary = "Simple identity map for ActiveRecord"
30
+ gemspec.description = "Add simple finegrained handcontrolled identity map for ActiveRecord"
31
+ gemspec.email = "funny.falcon@gmail.com"
32
+ gemspec.homepage = "http://github.com/funny-falcon/identity-map"
33
+ gemspec.authors = ["Sokolov Yura aka funny_falcon"]
34
+ gemspec.add_dependency('activerecord')
35
+ gemspec.rubyforge_project = 'ar-simple-idmap'
36
+ end
37
+ Jeweler::GemcutterTasks.new
38
+ Jeweler::RubyforgeTasks.new
39
+ rescue LoadError
40
+ puts "Jeweler not available. Install it with: gem install jeweler"
41
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "lib", "identity_map")
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ puts IO.read(File.join(File.dirname(__FILE__), 'README'))
@@ -0,0 +1,5 @@
1
+ $:.push(File.join(File.dirname(__FILE__), %w[.. .. rspec]))
2
+
3
+ Autotest.add_discovery do
4
+ "rspec"
5
+ end
@@ -0,0 +1,63 @@
1
+ module ActionController
2
+ class Base
3
+ module IdentityMap
4
+ module InstanceMethods
5
+ def with_identity_map
6
+ ActiveRecord::Base.with_id_map {
7
+ yield
8
+ }
9
+ end
10
+
11
+ def without_identity_map
12
+ ActiveRecord::Base.without_id_map {
13
+ yield
14
+ }
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def use_identity_map(*args)
20
+ skip_around_filter :without_identity_map, *args
21
+ around_filter :with_identity_map, *args
22
+ end
23
+
24
+ def dont_use_identity_map(*args)
25
+ skip_around_filter :with_identity_map, *args
26
+ around_filter :without_identity_map, *args
27
+ end
28
+
29
+ def use_dispater_identity_map
30
+ if defined? ::ActionDispatch
31
+ ActionDispatch::Callbacks.before :create_identity_map
32
+ ActionDispatch::Callbacks.after :remove_identity_map
33
+ else
34
+ ActionController::Dispatcher.before_dispatch :create_identity_map
35
+ ActionController::Dispatcher.after_dispatch :remove_identity_map
36
+ end
37
+ end
38
+ end
39
+
40
+
41
+ end
42
+ extend IdentityMap::ClassMethods
43
+ include IdentityMap::InstanceMethods
44
+ end
45
+ end
46
+
47
+ module DispatcherMethods
48
+ def create_identity_map
49
+ ActiveRecord::Base.create_identity_map
50
+ end
51
+
52
+ def remove_identity_map
53
+ ActiveRecord::Base.drop_identity_map
54
+ end
55
+ end
56
+
57
+ if defined? ::ActionDispatch
58
+ ActionDispatch::Callbacks.send :include, DispatcherMethods
59
+ RAILS_DEFAULT_LOGGER.warn "CACHING OBJECTS"
60
+ else
61
+ ActionController::Dispatcher.send :include, DispatcherMethods
62
+ RAILS_DEFAULT_LOGGER.warn "CACHING OBJECTS"
63
+ end
@@ -0,0 +1,31 @@
1
+ module IdentityMap
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ class << base
6
+ alias_method_chain :association_instance_set, :identity_map
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ private
13
+
14
+ # Set the specified association instance.
15
+ def association_instance_set_with_identity_map(name, association)
16
+ identity_map = Thread.current['identity_map']
17
+ unless identity_map
18
+ association_instance_set_without_identity_map("@#{name}", association)
19
+ else
20
+ association_instance_set_without_identity_map("@#{name}", identity_map.put(association))
21
+ end
22
+ end
23
+
24
+
25
+ end
26
+
27
+ end
28
+
29
+ if Object.const_defined?("ActiveRecord")
30
+ ActiveRecord::Associations.send :include, IdentityMap
31
+ end
@@ -0,0 +1,100 @@
1
+ module ActiveRecord
2
+ class Base
3
+ module IdentityMap
4
+ module ClassMethods
5
+ private
6
+ def use_id_map
7
+ extend IdMapClassMethods
8
+ include IdMapInstanceMethods
9
+ class << self
10
+ alias_method_chain :find, :identity_map
11
+ alias_method_chain :instantiate, :identity_map
12
+ end
13
+ alias_method_chain :create, :identity_map
14
+ end
15
+ end
16
+
17
+ module IdMapClassMethods
18
+
19
+ def id_map
20
+ thread_id_map.try(:for_class, self)
21
+ end
22
+
23
+ def if_id_map
24
+ map = id_map
25
+ yield map if map
26
+ end
27
+
28
+ private
29
+
30
+ def find_with_identity_map( *args )
31
+ if_id_map do |map|
32
+ unless args.size > 1 && args[1].values.any?
33
+ args0 = args[0]
34
+ if args0.is_a?(Array)
35
+ result, to_find = [], []
36
+ args0.each do |id|
37
+ if (obj = map[id])
38
+ result << obj
39
+ else
40
+ to_find << id
41
+ end
42
+ end
43
+ result + find_without_identity_map( to_find )
44
+ else
45
+ map[args0] || find_without_identity_map(args0)
46
+ end
47
+ end
48
+ end || find_without_identity_map(*args)
49
+ end
50
+
51
+ def instantiate_with_identity_map( record )
52
+ if_id_map do |map|
53
+ id = record[primary_key]
54
+ if (object = map[id])
55
+ attrs = object.instance_variable_get( :@attributes )
56
+ unless (changed = object.instance_variable_get( :@changed_attributes )).blank?
57
+ for key, value in record
58
+ if changed.has_key? key
59
+ changed[key] = value
60
+ else
61
+ attrs[key] = value
62
+ end
63
+ end
64
+ else
65
+ attrs.merge!( record ) unless attrs == record
66
+ end
67
+ object
68
+ else
69
+ map[id] = instantiate_without_identity_map( record )
70
+ end
71
+ end || instantiate_without_identity_map( record )
72
+ end
73
+
74
+ def delete_with_identity_map( ids )
75
+ res = delete_without_identity_map( ids )
76
+ if_id_map{|map| [ *ids ].each{|id| map.delete(id) } }
77
+ res
78
+ end
79
+ end
80
+
81
+ module IdMapInstanceMethods
82
+ private
83
+ def create_with_identity_map
84
+ id = create_without_identidy_map
85
+ self.class.if_id_map{|map| map[id] = self }
86
+ id
87
+ end
88
+
89
+ def destroy_with_identity_map
90
+ res = destroy_without_identity_map
91
+ self.class.if_id_map{|map| map.delete(id) }
92
+ res
93
+ end
94
+ end
95
+ end
96
+
97
+ extend IdentityMap::ClassMethods
98
+ end
99
+ end
100
+
@@ -0,0 +1,89 @@
1
+ module ActiveRecord
2
+ class Base
3
+ module ThreadIdentityMap
4
+ class ClassIdMap
5
+ def initialize(klass)
6
+ @objects = {}
7
+ @object = klass.allocate
8
+ @object.instance_variable_set(:@attributes, {:id=>nil})
9
+ @object.instance_variable_set(:@attributes_cache, {})
10
+ end
11
+
12
+ def [](id)
13
+ @object.id = id
14
+ @objects[@object.id]
15
+ end
16
+
17
+ def []=(id, v)
18
+ @object.id = id
19
+ @objects[@object.id] = v
20
+ end
21
+
22
+ def delete(id)
23
+ @object.id = id
24
+ @objects.delete(id)
25
+ end
26
+ end
27
+
28
+ class IdMap
29
+
30
+ def initialize
31
+ @objects = {}
32
+ end
33
+
34
+ def for_class(klass)
35
+ @objects[ klass.base_class ] ||= ClassIdMap.new(klass)
36
+ end
37
+
38
+ end
39
+
40
+ module ClassMethods
41
+ def create_identity_map
42
+ set_thread_id_map IdMap.new
43
+ end
44
+
45
+ def drop_identity_map
46
+ set_thread_id_map nil
47
+ end
48
+
49
+ def with_id_map( fresh = true)
50
+ old = thread_id_map
51
+ create_identity_map if fresh || old.nil?
52
+ yield
53
+ ensure
54
+ set_thread_id_map old
55
+ end
56
+
57
+ def without_id_map
58
+ old = thread_id_map
59
+ drop_identity_map
60
+ yield
61
+ ensure
62
+ set_thread_id_map old
63
+ end
64
+
65
+ # def uncached_with_identity_map( &block )
66
+ # without_id_map do
67
+ # uncached_without_identity_map &block
68
+ # end
69
+ # end
70
+
71
+ protected
72
+
73
+ def thread_id_map
74
+ Thread.current[:ar_identity_map]
75
+ end
76
+
77
+ def thread_id_map=(v)
78
+ Thread.current[:ar_identity_map] = v
79
+ end
80
+
81
+ alias set_thread_id_map thread_id_map=
82
+
83
+ end
84
+ end
85
+
86
+ extend ThreadIdentityMap::ClassMethods
87
+ # self.class.send :alias_method_chain, :uncached, :identity_map
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ require "identity_map/cache"
2
+ require "identity_map/action_controller/dispatcher"
3
+ require "identity_map/active_record/base"
data/spec/database.yml ADDED
@@ -0,0 +1,21 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :database: vendor/plugins/identity_map/spec/db/identity_map_plugin.sqlite.db
4
+
5
+ sqlite3:
6
+ :adapter: sqlite3
7
+ :database: vendor/plugins/identity_map/spec/db/identity_map_plugin.sqlite3.db
8
+
9
+ postgresql:
10
+ :adapter: postgresql
11
+ :username: postgres
12
+ :password: postgres
13
+ :database: identity_map_plugin_test
14
+ :min_messages: ERROR
15
+
16
+ mysql:
17
+ :adapter: mysql
18
+ :host: localhost
19
+ :username: root
20
+ :password:
21
+ :database: identity_map_plugin_test
data/spec/db/schema.rb ADDED
@@ -0,0 +1,10 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ puts "Creating Schema"
3
+ create_table :customers, :force => true do |t|
4
+ t.string :name
5
+ end
6
+ create_table :phone_numbers, :force => true do |t|
7
+ t.string :number
8
+ t.integer :customer_id
9
+ end
10
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Customers" do
4
+
5
+ before(:each) do
6
+ Thread.current["identity_map"] = Cache.new
7
+ end
8
+
9
+ it "should load the same model twice" do
10
+ c1 = Customer.first
11
+ c2 = Customer.first
12
+ c1.__id__.should == c2.__id__
13
+ end
14
+
15
+ it "should work for has_many associations" do
16
+ p1 = PhoneNumber.first
17
+ p2 = Customer.first.phone_numbers.first
18
+ p1.__id__.should == p2.__id__
19
+ end
20
+
21
+ it "should work for belongs_to assocations" do
22
+ d1 = Customer.first
23
+ d2 = PhoneNumber.first.customer
24
+ d1.__id__.should == d2.target.__id__
25
+ end
26
+
27
+ it "should work for creating objects" do
28
+ c1 = Customer.create(:name => "billy")
29
+ c2 = Customer.find_by_name("billy")
30
+ c1.__id__.should == c2.__id__
31
+ end
32
+
33
+ after(:each) do
34
+ Thread.current["identity_map"] = nil
35
+ end
36
+
37
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,38 @@
1
+ begin
2
+ require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
3
+ rescue LoadError
4
+ puts "You need to install rspec in your base app"
5
+ exit
6
+ end
7
+
8
+ plugin_spec_dir = File.dirname(__FILE__)
9
+ ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log")
10
+
11
+ def load_schema
12
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
13
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
14
+
15
+ db_adapter = ENV['DB']
16
+ # no db passed, try one of these fine config-free DBs before bombing.
17
+ db_adapter ||=
18
+ begin
19
+ require 'rubygems'
20
+ require 'sqlite'
21
+ 'sqlite'
22
+ rescue MissingSourceFile
23
+ begin
24
+ require 'sqlite3'
25
+ 'sqlite3'
26
+ rescue MissingSourceFile
27
+ end
28
+ end
29
+
30
+ if db_adapter.nil?
31
+ raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
32
+ end
33
+
34
+ ActiveRecord::Base.establish_connection(config[db_adapter])
35
+ load(File.dirname(__FILE__) + "/db/schema.rb")
36
+ require File.dirname(__FILE__) + '/../init.rb'
37
+ end
38
+ require File.dirname(__FILE__) + '/test_models.rb'
@@ -0,0 +1,13 @@
1
+ load_schema
2
+
3
+ class Customer < ActiveRecord::Base
4
+ has_many :phone_numbers
5
+ end
6
+
7
+ customer = Customer.create(:name => "Boneman")
8
+
9
+ class PhoneNumber < ActiveRecord::Base
10
+ belongs_to :customer
11
+ end
12
+
13
+ phone_number = customer.phone_numbers.create(:number => "8675309")
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ar-simple-idmap
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Sokolov Yura aka funny_falcon
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-02 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Add simple finegrained handcontrolled identity map for ActiveRecord
36
+ email: funny.falcon@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ files:
44
+ - .gitignore
45
+ - COPYING
46
+ - README
47
+ - Rakefile
48
+ - init.rb
49
+ - install.rb
50
+ - lib/autotest/discover.rb
51
+ - lib/identity_map.rb
52
+ - lib/identity_map/action_controller/dispatcher.rb
53
+ - lib/identity_map/active_record/associations.rb
54
+ - lib/identity_map/active_record/base.rb
55
+ - lib/identity_map/cache.rb
56
+ - spec/database.yml
57
+ - spec/db/schema.rb
58
+ - spec/identity_map_spec.rb
59
+ - spec/spec.opts
60
+ - spec/spec_helper.rb
61
+ - spec/test_models.rb
62
+ has_rdoc: true
63
+ homepage: http://github.com/funny-falcon/identity-map
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --charset=UTF-8
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project: ar-simple-idmap
92
+ rubygems_version: 1.3.7
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Simple identity map for ActiveRecord
96
+ test_files:
97
+ - spec/db/schema.rb
98
+ - spec/test_models.rb
99
+ - spec/identity_map_spec.rb
100
+ - spec/spec_helper.rb