ar-simple-idmap 0.1.0

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,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