methodmissing-scrooge 2.2.0 → 2.2.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/README.textile CHANGED
@@ -214,7 +214,7 @@ h2. Todo
214
214
 
215
215
  * Deeper coverage for Scrooge::AttributesProxy ( pending conversion to a subclass of Hash instead )
216
216
 
217
- * Extract Scrooge::Callsite
217
+ * Test cases for Scrooge::Callsite
218
218
 
219
219
  * Track both columns AND association invocations off Scrooge::Callsite
220
220
 
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 2
3
- :patch: 0
2
+ :patch: 1
4
3
  :major: 2
4
+ :minor: 2
data/lib/callsite.rb ADDED
@@ -0,0 +1,76 @@
1
+ module Scrooge
2
+ class Callsite
3
+
4
+ # Represents a Callsite and is a container for any columns and
5
+ # associations ( coming soon ) referenced at the callsite.
6
+ #
7
+
8
+ Mutex = Mutex.new
9
+
10
+ attr_accessor :klass,
11
+ :signature,
12
+ :columns,
13
+ :associations
14
+
15
+ def initialize( klass, signature )
16
+ @klass = klass
17
+ @signature = signature
18
+ @columns = setup_columns
19
+ @associations = setup_associations
20
+ end
21
+
22
+ # Flag a column as seen
23
+ #
24
+ def column!( column )
25
+ Mutex.synchronize do
26
+ @columns << column
27
+ end
28
+ end
29
+
30
+ # Flag an association as seen
31
+ #
32
+ def association!( association )
33
+ Mutex.synchronize do
34
+ @associations << association
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # Is the table a container for STI models ?
41
+ #
42
+ def inheritable?
43
+ @klass.column_names.include?( inheritance_column )
44
+ end
45
+
46
+ # Ensure that at least the primary key and optionally the inheritance
47
+ # column ( for STI ) is set.
48
+ #
49
+ def setup_columns
50
+ if inheritable?
51
+ Set.new([primary_key, inheritance_column])
52
+ else
53
+ Set.new([primary_key])
54
+ end
55
+ end
56
+
57
+ # Stubbed for future use
58
+ #
59
+ def setup_associations
60
+ Set.new
61
+ end
62
+
63
+ # Memoize a string representation of the inheritance column
64
+ #
65
+ def inheritance_column
66
+ @inheritance_column ||= @klass.inheritance_column.to_s
67
+ end
68
+
69
+ # Memoize a string representation of the primary
70
+ #
71
+ def primary_key
72
+ @primary_key ||= @klass.primary_key.to_s
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,138 @@
1
+ class Hash
2
+
3
+ # TODO: Circumvent this hack
4
+ alias_method :update_without_scrooge, :update
5
+ def update(other)
6
+ update_without_scrooge(other.to_hash)
7
+ end
8
+
9
+ end
10
+
11
+ module Scrooge
12
+ module Optimizations
13
+ module Columns
14
+ class AttributesProxy < Hash
15
+
16
+ # Hash like Proxy container for attributes
17
+ #
18
+
19
+ attr_accessor :callsite_signature, :scrooge_columns, :fully_fetched, :klass
20
+
21
+ def self.setup(record, scrooge_columns, klass, callsite_signature)
22
+ hash = new.replace(record)
23
+ hash.scrooge_columns = scrooge_columns.dup
24
+ hash.fully_fetched = false
25
+ hash.klass = klass
26
+ hash.callsite_signature = callsite_signature
27
+ hash
28
+ end
29
+
30
+ # Delegate Hash keys to all defined columns
31
+ #
32
+ def keys
33
+ @klass.column_names
34
+ end
35
+
36
+ # Let #has_key? consider defined columns
37
+ #
38
+ def has_key?(attr_name)
39
+ keys.include?(attr_name.to_s)
40
+ end
41
+
42
+ alias_method :include?, :has_key?
43
+ alias_method :key?, :has_key?
44
+ alias_method :member?, :has_key?
45
+
46
+ # Lazily augment and load missing attributes
47
+ #
48
+ def [](attr_name)
49
+ attr_s = attr_name.to_s
50
+ if interesting_for_scrooge?( attr_s )
51
+ augment_callsite!( attr_s )
52
+ fetch_remaining
53
+ @scrooge_columns << attr_s
54
+ end
55
+ super
56
+ end
57
+
58
+ def fetch(*args, &block)
59
+ self[args[0]]
60
+ super
61
+ end
62
+
63
+ def []=(attr_name, value)
64
+ @scrooge_columns << attr_name.to_s
65
+ super
66
+ end
67
+
68
+ alias_method :store, :[]=
69
+
70
+ def dup
71
+ super.dup_self
72
+ end
73
+
74
+ def to_hash
75
+ fetch_remaining
76
+ super
77
+ end
78
+
79
+ def to_a
80
+ fetch_remaining
81
+ super
82
+ end
83
+
84
+ def delete(attr_name)
85
+ self[attr_name]
86
+ super
87
+ end
88
+
89
+ def update(hash)
90
+ @fully_fetched = true
91
+ super
92
+ end
93
+
94
+ alias_method :merge!, :update
95
+
96
+ def fetch_remaining
97
+ unless @fully_fetched
98
+ columns_to_fetch = @klass.column_names - @scrooge_columns.to_a
99
+ unless columns_to_fetch.empty?
100
+ fetch_remaining!( columns_to_fetch )
101
+ end
102
+ @fully_fetched = true
103
+ end
104
+ end
105
+
106
+ protected
107
+
108
+ def fetch_remaining!( columns_to_fetch )
109
+ begin
110
+ new_object = fetch_record_with_remaining_columns( columns_to_fetch )
111
+ rescue ActiveRecord::RecordNotFound
112
+ raise ActiveRecord::MissingAttributeError, "scrooge cannot fetch missing attribute(s) #{columns_to_fetch.to_a.join(', ')} because record went away"
113
+ end
114
+ replace(new_object.instance_variable_get(:@attributes).merge(self))
115
+ end
116
+
117
+ def fetch_record_with_remaining_columns( columns_to_fetch )
118
+ @klass.send(:with_exclusive_scope) do
119
+ @klass.find(self[@klass.primary_key], :select=>@klass.scrooge_select_sql(columns_to_fetch))
120
+ end
121
+ end
122
+
123
+ def interesting_for_scrooge?( attr_s )
124
+ has_key?(attr_s) && !@scrooge_columns.include?(attr_s)
125
+ end
126
+
127
+ def augment_callsite!( attr_s )
128
+ @klass.scrooge_seen_column!(callsite_signature, attr_s)
129
+ end
130
+
131
+ def dup_self
132
+ @scrooge_columns = @scrooge_columns.dup
133
+ self
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,186 @@
1
+ module Scrooge
2
+ module Optimizations
3
+ module Columns
4
+ module Macro
5
+
6
+ class << self
7
+
8
+ # Inject into ActiveRecord
9
+ #
10
+ def install!
11
+ if scrooge_installable?
12
+ ActiveRecord::Base.send( :extend, Scrooge::Optimizations::Columns::SingletonMethods )
13
+ ActiveRecord::Base.send( :include, Scrooge::Optimizations::Columns::InstanceMethods )
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def scrooge_installable?
20
+ !ActiveRecord::Base.included_modules.include?( Scrooge::Optimizations::Columns::InstanceMethods )
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ module SingletonMethods
28
+
29
+ ScroogeBlankString = "".freeze
30
+ ScroogeComma = ",".freeze
31
+ ScroogeRegexSanitize = /(?:LIMIT|WHERE|FROM|GROUP\s*BY|HAVING|ORDER\s*BY|PROCEDURE|FOR\s*UPDATE|INTO\s*OUTFILE).*/i
32
+ ScroogeRegexJoin = /(?:left|inner|outer|cross)*\s*(?:straight_join|join)/i
33
+
34
+ @@scrooge_select_regexes = {}
35
+
36
+ # Augment a given callsite signature with a column / attribute.
37
+ #
38
+ def scrooge_seen_column!( callsite_signature, attr_name )
39
+ scrooge_callsite( callsite_signature ).column!( attr_name )
40
+ end
41
+
42
+ # Generates a SELECT snippet for this Model from a given Set of columns
43
+ #
44
+ def scrooge_select_sql( set )
45
+ set.map{|a| attribute_with_table( a ) }.join( ScroogeComma )
46
+ end
47
+
48
+ # Marshal.load
49
+ #
50
+ def _load(str)
51
+ Marshal.load(str)
52
+ end
53
+
54
+ private
55
+
56
+ # Only scope n-1 rows by default.
57
+ # Stephen: Temp. relaxed the LIMIT constraint - please advise.
58
+ def scope_with_scrooge?( sql )
59
+ sql =~ scrooge_select_regex &&
60
+ column_names.include?(self.primary_key.to_s) &&
61
+ sql !~ ScroogeRegexJoin
62
+ end
63
+
64
+ # Find through callsites.
65
+ #
66
+ def find_by_sql_with_scrooge( sql )
67
+ callsite_signature = (caller[ActiveRecord::Base::ScroogeCallsiteSample] << callsite_sql( sql )).hash
68
+ callsite_set = scrooge_callsite(callsite_signature).columns
69
+ sql = sql.gsub(scrooge_select_regex, "SELECT #{scrooge_select_sql(callsite_set)} FROM")
70
+ result = connection.select_all(sanitize_sql(sql), "#{name} Load Scrooged").collect! do |record|
71
+ instantiate( Scrooge::Optimizations::Columns::AttributesProxy.setup(record, callsite_set, self, callsite_signature) )
72
+ end
73
+ end
74
+
75
+ # Generate a regex that respects the table name as well to catch
76
+ # verbose SQL from JOINS etc.
77
+ #
78
+ def scrooge_select_regex
79
+ @@scrooge_select_regexes[self.table_name] ||= Regexp.compile( "SELECT (`?(?:#{table_name})?`?.?\\*) FROM" )
80
+ end
81
+
82
+ # Trim any conditions
83
+ #
84
+ def callsite_sql( sql )
85
+ sql.gsub(ScroogeRegexSanitize, ScroogeBlankString)
86
+ end
87
+
88
+ end
89
+
90
+ module InstanceMethods
91
+
92
+ def self.included( base )
93
+ base.alias_method_chain :delete, :scrooge
94
+ base.alias_method_chain :destroy, :scrooge
95
+ base.alias_method_chain :respond_to?, :scrooge
96
+ end
97
+
98
+ # Is this instance being handled by scrooge?
99
+ #
100
+ def scrooged?
101
+ @attributes.is_a?(Scrooge::Optimizations::Columns::AttributesProxy)
102
+ end
103
+
104
+ # Delete should fully load all the attributes before the @attributes hash is frozen
105
+ #
106
+ def delete_with_scrooge
107
+ scrooge_fetch_remaining
108
+ delete_without_scrooge
109
+ end
110
+
111
+ # Destroy should fully load all the attributes before the @attributes hash is frozen
112
+ #
113
+ def destroy_with_scrooge
114
+ scrooge_fetch_remaining
115
+ destroy_without_scrooge
116
+ end
117
+
118
+ # Augment callsite info for new model class when using STI
119
+ #
120
+ def becomes(klass)
121
+ returning klass.new do |became|
122
+ became.instance_variable_set("@attributes", @attributes)
123
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
124
+ became.instance_variable_set("@new_record", new_record?)
125
+ if scrooged?
126
+ self.class.scrooge_callsite(@attributes.callsite_signature).columns.each do |attrib|
127
+ became.class.scrooge_seen_column!(@attributes.callsite_signature, attrib)
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ # Marshal
134
+ # force a full load if needed, and remove any possibility for missing attr flagging
135
+ #
136
+ def _dump(depth)
137
+ scrooge_fetch_remaining
138
+ scrooge_dump_flag_this
139
+ str = Marshal.dump(self)
140
+ scrooge_dump_unflag_this
141
+ str
142
+ end
143
+
144
+ # Enables us to use Marshal.dump inside our _dump method without an infinite loop
145
+ #
146
+ def respond_to_with_scrooge?(symbol, include_private=false)
147
+ if symbol == :_dump && scrooge_dump_flagged?
148
+ false
149
+ else
150
+ respond_to_without_scrooge?(symbol, include_private)
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ # Flag Marshal dump in progress
157
+ #
158
+ def scrooge_dump_flag_this
159
+ Thread.current[:scrooge_dumping_objects] ||= []
160
+ Thread.current[:scrooge_dumping_objects] << object_id
161
+ end
162
+
163
+ # Flag Marhsal dump not in progress
164
+ #
165
+ def scrooge_dump_unflag_this
166
+ Thread.current[:scrooge_dumping_objects].delete(object_id)
167
+ end
168
+
169
+ # Flag scrooge as dumping ( excuse my French )
170
+ #
171
+ def scrooge_dump_flagged?
172
+ Thread.current[:scrooge_dumping_objects] &&
173
+ Thread.current[:scrooge_dumping_objects].include?(object_id)
174
+ end
175
+
176
+ # Fetch any missing attributes
177
+ #
178
+ def scrooge_fetch_remaining
179
+ @attributes.fetch_remaining if scrooged?
180
+ end
181
+
182
+ end
183
+
184
+ end
185
+ end
186
+ end
data/lib/scrooge.rb CHANGED
@@ -1,19 +1,14 @@
1
1
  $:.unshift(File.dirname(__FILE__))
2
2
 
3
3
  require 'set'
4
- require 'attributes_proxy'
4
+ require 'callsite'
5
+ require 'optimizations/columns/attributes_proxy'
6
+ require 'optimizations/columns/macro'
5
7
 
6
8
  module ActiveRecord
7
9
  class Base
8
10
 
9
- @@scrooge_mutex = Mutex.new
10
11
  @@scrooge_callsites = {}
11
- @@scrooge_select_regexes = {}
12
-
13
- ScroogeBlankString = "".freeze
14
- ScroogeComma = ",".freeze
15
- ScroogeRegexWhere = /WHERE.*/i
16
- ScroogeRegexJoin = /(?:left|inner|outer|cross)*\s*(?:straight_join|join)/i
17
12
  ScroogeCallsiteSample = 0..10
18
13
 
19
14
  class << self
@@ -29,198 +24,43 @@ module ActiveRecord
29
24
  find_by_sql_without_scrooge(sql)
30
25
  end
31
26
  end
32
-
33
- # Populate the storage for a given callsite signature
34
- #
35
- def scrooge_callsite_set!(callsite_signature, set)
36
- @@scrooge_callsites[self.table_name][callsite_signature] = set
37
- end
38
-
39
- # Reference storage for a given callsite signature
40
- #
41
- def scrooge_callsite_set(callsite_signature)
42
- scrooge_callsites
43
- @@scrooge_callsites[self.table_name][callsite_signature]
44
- end
45
27
 
46
28
  # Expose known callsites for this model
47
29
  #
48
30
  def scrooge_callsites
49
31
  @@scrooge_callsites[self.table_name] ||= {}
50
32
  end
51
-
33
+
34
+ # Fetch or setup a callsite instance for a given signature
35
+ #
36
+ def scrooge_callsite( callsite_signature )
37
+ @@scrooge_callsites[self.table_name] ||= {}
38
+ @@scrooge_callsites[self.table_name][callsite_signature] ||= callsite( callsite_signature )
39
+ end
40
+
52
41
  # Flush all known callsites.Mostly a test helper.
53
42
  #
54
43
  def scrooge_flush_callsites!
55
44
  @@scrooge_callsites[self.table_name] = {}
56
45
  end
57
-
58
- # Augment a given callsite signature with a column / attribute.
59
- #
60
- def augment_scrooge_callsite!( callsite_signature, attr_name )
61
- set = set_for_callsite( callsite_signature ) # make set if needed - eg unserialized models after restart
62
- @@scrooge_mutex.synchronize do
63
- set << attr_name
64
- end
65
- end
66
-
67
- # Generates a SELECT snippet for this Model from a given Set of columns
68
- #
69
- def scrooge_sql( set )
70
- set.map{|a| attribute_with_table( a ) }.join( ScroogeComma )
71
- end
72
46
 
73
47
  private
74
48
 
75
- # Only scope n-1 rows by default.
76
- # Stephen: Temp. relaxed the LIMIT constraint - please advise.
77
- def scope_with_scrooge?( sql )
78
- sql =~ scrooge_select_regex &&
79
- column_names.include?(self.primary_key.to_s) &&
80
- sql !~ ScroogeRegexJoin
81
- end
82
-
83
- # Find through callsites.
84
- #
85
- def find_by_sql_with_scrooge( sql )
86
- callsite_signature = (caller[ScroogeCallsiteSample] << sql.gsub(ScroogeRegexWhere, ScroogeBlankString)).hash
87
- callsite_set = set_for_callsite(callsite_signature)
88
- sql = sql.gsub(scrooge_select_regex, "SELECT #{scrooge_sql(callsite_set)} FROM")
89
- result = connection.select_all(sanitize_sql(sql), "#{name} Load Scrooged").collect! do |record|
90
- instantiate(Scrooge::AttributesProxy.new(record, callsite_set, self, callsite_signature))
49
+ # Initialize a callsite
50
+ #
51
+ def callsite( signature )
52
+ Scrooge::Callsite.new( self, signature )
91
53
  end
92
- end
93
54
 
94
- # Return an attribute Set for a given callsite signature.
95
- # Respects already tracked columns and ensures at least the primary key
96
- # if this is a fresh callsite.
97
- #
98
- def set_for_callsite( callsite_signature )
99
- @@scrooge_mutex.synchronize do
100
- callsite_set = scrooge_callsite_set(callsite_signature)
101
- unless callsite_set
102
- callsite_set = scrooge_default_callsite_set
103
- scrooge_callsite_set!(callsite_signature, callsite_set)
104
- end
105
- callsite_set
55
+ # Link the column to its table
56
+ #
57
+ def attribute_with_table( attr_name )
58
+ "#{quoted_table_name}.#{attr_name.to_s}"
106
59
  end
107
- end
108
-
109
- # Ensure that the inheritance column is defined for the callsite if
110
- # this is an STI klass tree.
111
- #
112
- def scrooge_default_callsite_set
113
- if column_names.include?( self.inheritance_column.to_s )
114
- Set.new([self.primary_key.to_s, self.inheritance_column.to_s])
115
- else
116
- Set.new([self.primary_key.to_s])
117
- end
118
- end
119
-
120
- # Generate a regex that respects the table name as well to catch
121
- # verbose SQL from JOINS etc.
122
- #
123
- def scrooge_select_regex
124
- @@scrooge_select_regexes[self.table_name] ||= Regexp.compile( "SELECT (`?(?:#{table_name})?`?.?\\*) FROM" )
125
- end
126
-
127
- # Link the column to its table
128
- #
129
- def attribute_with_table( attr_name )
130
- "#{quoted_table_name}.#{attr_name.to_s}"
131
- end
132
60
 
133
61
  end # class << self
134
62
 
135
- # Is this instance being handled by scrooge?
136
- #
137
- def scrooged?
138
- @attributes.is_a?(Scrooge::AttributesProxy)
139
- end
140
-
141
- # Delete should fully load all the attributes before the @attributes hash is frozen
142
- #
143
- alias_method :delete_without_scrooge, :delete
144
- def delete
145
- scrooge_fetch_remaining
146
- delete_without_scrooge
147
- end
148
-
149
- # Destroy should fully load all the attributes before the @attributes hash is frozen
150
- #
151
- alias_method :destroy_without_scrooge, :destroy
152
- def destroy
153
- scrooge_fetch_remaining
154
- destroy_without_scrooge
155
- end
156
-
157
- # Augment callsite info for new model class when using STI
158
- #
159
- def becomes(klass)
160
- returning klass.new do |became|
161
- became.instance_variable_set("@attributes", @attributes)
162
- became.instance_variable_set("@attributes_cache", @attributes_cache)
163
- became.instance_variable_set("@new_record", new_record?)
164
- if scrooged?
165
- self.class.scrooge_callsite_set(@attributes.callsite_signature).each do |attrib|
166
- became.class.augment_scrooge_callsite!(@attributes.callsite_signature, attrib)
167
- end
168
- end
169
- end
170
- end
171
-
172
- # Marshal
173
- # force a full load if needed, and remove any possibility for missing attr flagging
174
- #
175
- def _dump(depth)
176
- scrooge_fetch_remaining
177
- scrooge_dump_flag_this
178
- str = Marshal.dump(self)
179
- scrooge_dump_unflag_this
180
- str
181
- end
182
-
183
- # Marshal.load
184
- #
185
- def self._load(str)
186
- Marshal.load(str)
187
- end
188
-
189
- # Enables us to use Marshal.dump inside our _dump method without an infinite loop
190
- #
191
- alias_method :respond_to_without_scrooge, :respond_to?
192
- def respond_to?(symbol, include_private=false)
193
- if symbol == :_dump && scrooge_dump_flagged?
194
- false
195
- else
196
- respond_to_without_scrooge(symbol, include_private)
197
- end
198
- end
199
-
200
- private
201
-
202
- # Flag Marshal dump in progress
203
- #
204
- def scrooge_dump_flag_this
205
- Thread.current[:scrooge_dumping_objects] ||= []
206
- Thread.current[:scrooge_dumping_objects] << object_id
207
- end
208
-
209
- # Flag Marhsal dump not in progress
210
- #
211
- def scrooge_dump_unflag_this
212
- Thread.current[:scrooge_dumping_objects].delete(object_id)
213
- end
214
-
215
- # Flag scrooge as dumping ( excuse my French )
216
- #
217
- def scrooge_dump_flagged?
218
- Thread.current[:scrooge_dumping_objects] && Thread.current[:scrooge_dumping_objects].include?(object_id)
219
- end
220
-
221
- def scrooge_fetch_remaining
222
- @attributes.fetch_remaining if scrooged?
223
- end
224
-
225
63
  end
226
- end
64
+ end
65
+
66
+ Scrooge::Optimizations::Columns::Macro.install!
data/test/scrooge_test.rb CHANGED
@@ -37,27 +37,22 @@ class ScroogeTest < ActiveSupport::TestCase
37
37
 
38
38
  test "should be able to retrieve a callsite form a given signature" do
39
39
  assert MysqlUser.find(:first).scrooged?
40
- assert_instance_of Set, MysqlUser.scrooge_callsite_set( first_callsite )
41
- end
42
-
43
- test "should be able to populate the callsite for a given signature" do
44
- MysqlUser.scrooge_callsite_set!(123456, Set[1,2,3])
45
- assert_equal MysqlUser.scrooge_callsite_set(123456), Set[1,2,3]
40
+ assert_instance_of Scrooge::Callsite, MysqlUser.scrooge_callsite( first_callsite )
46
41
  end
47
42
 
48
43
  test "should be able to augment an existing callsite with attributes" do
49
44
  MysqlUser.find(:first)
50
- MysqlUser.augment_scrooge_callsite!( first_callsite, 'Password' )
51
- assert MysqlUser.scrooge_callsite_set( first_callsite ).include?( 'Password' )
45
+ MysqlUser.scrooge_seen_column!( first_callsite, 'Password' )
46
+ assert MysqlUser.scrooge_callsite( first_callsite ).columns.include?( 'Password' )
52
47
  end
53
48
 
54
49
  test "should be able to generate a SQL select snippet from a given set" do
55
- assert_equal MysqlUser.scrooge_sql( Set['Password','User','Host'] ), "`user`.User,`user`.Password,`user`.Host"
50
+ assert_equal MysqlUser.scrooge_select_sql( Set['Password','User','Host'] ), "`user`.User,`user`.Password,`user`.Host"
56
51
  end
57
52
 
58
53
  test "should be able to augment an existing callsite when attributes is referenced that we haven't seen yet" do
59
54
  user = MysqlUser.find(:first)
60
- MysqlUser.expects(:augment_scrooge_callsite!).times(2)
55
+ MysqlUser.expects(:scrooge_seen_column!).times(2)
61
56
  user.Password
62
57
  user.Host
63
58
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: methodmissing-scrooge
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Lourens Naud\xC3\xA9"
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-03-13 00:00:00 -07:00
13
+ date: 2009-03-15 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -27,7 +27,11 @@ files:
27
27
  - README
28
28
  - README.textile
29
29
  - VERSION.yml
30
- - lib/attributes_proxy.rb
30
+ - lib/callsite.rb
31
+ - lib/optimizations
32
+ - lib/optimizations/columns
33
+ - lib/optimizations/columns/attributes_proxy.rb
34
+ - lib/optimizations/columns/macro.rb
31
35
  - lib/scrooge.rb
32
36
  - test/helper.rb
33
37
  - test/models
@@ -1,121 +0,0 @@
1
- module Scrooge
2
- class AttributesProxy
3
- attr_reader :callsite_signature
4
-
5
- def initialize(record, scrooge_columns, klass, callsite_signature)
6
- @attributes = record
7
- @scrooge_columns = scrooge_columns.dup
8
- @fully_fetched = false
9
- @klass = klass
10
- @callsite_signature = callsite_signature
11
- end
12
-
13
- # Delegate Hash keys to all defined columns
14
- #
15
- def keys
16
- @klass.column_names
17
- end
18
-
19
- # Let #has_key? consider defined columns
20
- #
21
- def has_key?(attr_name)
22
- keys.include?(attr_name.to_s)
23
- end
24
-
25
- alias_method :include?, :has_key?
26
- alias_method :key?, :has_key?
27
-
28
- # Lazily augment and load missing attributes
29
- #
30
- def [](attr_name)
31
- attr_s = attr_name.to_s
32
- if interesting_for_scrooge?( attr_s )
33
- augment_callsite!( attr_s )
34
- fetch_remaining
35
- @scrooge_columns << attr_s
36
- end
37
- @attributes[attr_s]
38
- end
39
-
40
- def fetch(*args, &block)
41
- self[args[0]]
42
- @attributes.fetch(*args, &block)
43
- end
44
-
45
- def []=(attr_name, value)
46
- attr_s = attr_name.to_s
47
- @attributes[attr_s] = value
48
- @scrooge_columns << attr_s
49
- end
50
-
51
- def dup
52
- super.dup_self
53
- end
54
-
55
- def to_a
56
- fetch_remaining
57
- @attributes.to_a
58
- end
59
-
60
- def delete(attr_name)
61
- self[attr_name]
62
- @attributes.delete(attr_name)
63
- end
64
-
65
- def update(hash)
66
- hash.to_hash.each do |k, v|
67
- self[k] = v
68
- end
69
- end
70
-
71
- def to_hash
72
- fetch_remaining
73
- @attributes
74
- end
75
-
76
- def freeze
77
- @attributes.freeze
78
- end
79
-
80
- def frozen?
81
- @attributes.frozen?
82
- end
83
-
84
- def fetch_remaining
85
- unless @fully_fetched
86
- columns_to_fetch = @klass.column_names - @scrooge_columns.to_a
87
- unless columns_to_fetch.empty?
88
- begin
89
- new_object = fetch_record_with_remaining_columns( columns_to_fetch )
90
- rescue ActiveRecord::RecordNotFound
91
- raise ActiveRecord::MissingAttributeError, "scrooge cannot fetch missing attribute(s) because record went away"
92
- end
93
- @attributes = new_object.instance_variable_get(:@attributes).merge(@attributes)
94
- end
95
- @fully_fetched = true
96
- end
97
- end
98
-
99
- protected
100
-
101
- def fetch_record_with_remaining_columns( columns_to_fetch )
102
- @klass.send(:with_exclusive_scope) do
103
- @klass.find(@attributes[@klass.primary_key], :select=>@klass.scrooge_sql(columns_to_fetch))
104
- end
105
- end
106
-
107
- def interesting_for_scrooge?( attr_s )
108
- has_key?(attr_s) && !@scrooge_columns.include?(attr_s)
109
- end
110
-
111
- def augment_callsite!( attr_s )
112
- @klass.augment_scrooge_callsite!(callsite_signature, attr_s)
113
- end
114
-
115
- def dup_self
116
- @attributes = @attributes.dup
117
- @scrooge_columns = @scrooge_columns.dup
118
- self
119
- end
120
- end
121
- end