gitmodel 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc CHANGED
@@ -1 +1,3 @@
1
1
  rvm use ruby-1.9.2@gitmodel
2
+ echo "starting memcached..."
3
+ memcached &
data/Gemfile.lock CHANGED
@@ -1,11 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitmodel (0.0.4)
4
+ gitmodel (0.0.5)
5
5
  activemodel (>= 3.0.1)
6
6
  activesupport (>= 3.0.1)
7
7
  grit (>= 2.3.0)
8
8
  lockfile (>= 1.4.3)
9
+ memcache-client (>= 1.8.5)
9
10
  yajl-ruby (>= 0.8.2)
10
11
 
11
12
  GEM
@@ -26,6 +27,7 @@ GEM
26
27
  mime-types (~> 1.15)
27
28
  i18n (0.5.0)
28
29
  lockfile (1.4.3)
30
+ memcache-client (1.8.5)
29
31
  mime-types (1.16)
30
32
  rspec (2.6.0)
31
33
  rspec-core (~> 2.6.0)
data/README.md CHANGED
@@ -151,6 +151,25 @@ structure that looks like this:
151
151
  * _attributes.json_
152
152
 
153
153
 
154
+ Performance
155
+ -----------
156
+
157
+ GitModel supports memcached for query results. This is off by default, but can be configured like this:
158
+
159
+ GitModel.memcache_servers(['server_1', 'server_2', ...])
160
+ GitModel.memcache_namespace('optional_namespace')
161
+
162
+ The namespace is optional, and usually not necessary because GitModel will prepend the last segment of GitModel.db_root anyway.
163
+
164
+ A Git SHA is also prepended to every key, so that outdated versions will not be retrieved from the cache. This is the SHA of the latest commit so unfortunately this is only useful when there are not frequent commits because every commit invalidates the cache. (This is obviously not ideal and I'm sure it can be improved upon.)
165
+
166
+ There is still a lot of work to be done to make it faster. First, some analysis is required, but some guesses about things that would help are:
167
+
168
+ * Use [Rugged](https://github.com/libgit2/rugged) instead of Grit
169
+ * Remove the transaction lock (see transaction.rb line 19)
170
+ * Ability to iterate over result set without eager loading of all instances
171
+
172
+
154
173
  Contributing
155
174
  ------------
156
175
 
@@ -179,10 +198,6 @@ To do
179
198
  * Generators
180
199
  * Rake tasks
181
200
  * Performance
182
- * Haven't optimized for performance yet.
183
- * Use [Rugged](https://github.com/libgit2/rugged) instead of Grit
184
- * Remove the transaction lock (see transaction.rb line 19)
185
- * Ability to iterate over result set without eager loading of all instances
186
201
  * Persistable.find/find_all/etc could be based on staged files so that queries reflect uncommitted changes
187
202
  * Better query support
188
203
  * Associations
data/gitmodel.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'gitmodel'
3
- s.version = '0.0.4'
3
+ s.version = '0.0.5'
4
4
  s.platform = Gem::Platform::RUBY
5
5
 
6
6
  s.authors = ["Paul Dowman"]
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency 'activesupport', '>= 3.0.1'
22
22
  s.add_dependency 'grit', '>= 2.3.0'
23
23
  s.add_dependency 'lockfile', '>= 1.4.3'
24
+ s.add_dependency 'memcache-client', '>= 1.8.5'
24
25
  s.add_dependency 'yajl-ruby', '>= 0.8.2'
25
26
 
26
27
  s.add_development_dependency 'ZenTest', '>= 4.4.0'
data/lib/gitmodel.rb CHANGED
@@ -1,12 +1,15 @@
1
+ # coding: UTF-8
2
+
1
3
  require 'rubygems'
2
4
  require 'bundler/setup'
3
5
 
4
6
  require 'active_model'
5
7
  require 'active_support/all' # TODO we don't really want all here, clean this up
6
8
  require 'grit'
7
- require 'yajl'
8
9
  require 'lockfile'
10
+ require 'memcache'
9
11
  require 'pp'
12
+ require 'yajl'
10
13
 
11
14
  $:.unshift(File.dirname(__FILE__))
12
15
  require 'gitmodel/errors'
@@ -32,6 +35,9 @@ module GitModel
32
35
  mattr_accessor :git_user_name
33
36
  mattr_accessor :git_user_email
34
37
 
38
+ mattr_accessor :memcache_servers
39
+ mattr_accessor :memcache_namespace
40
+
35
41
  def self.repo
36
42
  @@repo = Grit::Repo.new(GitModel.db_root)
37
43
  end
@@ -56,28 +62,64 @@ module GitModel
56
62
  create_db!
57
63
  end
58
64
 
59
- def self.last_commit(branch = nil)
60
- branch ||= default_branch
61
- # PERFORMANCE Cache this somewhere and update it on commit?
62
- # (Need separate instance per branch)
63
-
64
- return nil unless repo.commits(branch).any?
65
-
66
- # We should be able to use just repo.commits(branch).first here but
67
- # this is a workaround for this bug:
68
- # http://github.com/mojombo/grit/issues/issue/38
69
- GitModel.repo.commits("#{branch}^..#{branch}").first || GitModel.repo.commits(branch).first
65
+ def self.last_commit(branch)
66
+ cache(branch, 'last-commit') do
67
+ unless repo.commits(branch).any?
68
+ nil
69
+ else
70
+ # We should be able to use just repo.commits(branch).first here but
71
+ # this is a workaround for this bug:
72
+ # http://github.com/mojombo/grit/issues/issue/38
73
+ GitModel.repo.commits("#{branch}^..#{branch}").first || GitModel.repo.commits(branch).first
74
+ end
75
+ end
70
76
  end
71
77
 
72
- def self.current_tree(branch = nil)
78
+ def self.current_tree(branch)
73
79
  c = last_commit(branch)
74
80
  c ? c.tree : nil
75
81
  end
76
82
 
77
- def self.index!
78
- dirs = (GitModel.current_tree).trees
83
+ def self.index!(branch)
84
+ dirs = (GitModel.current_tree(branch)).trees
79
85
  dirs.each do |dir|
80
- dir.name.classify.constantize.index!
86
+ dir.name.classify.constantize.index!(branch)
81
87
  end
82
88
  end
89
+
90
+ # If we're using memcached (i.e. the memcache_servers setting is not nil) and
91
+ # the key exists in memcached, it's value will be returned and the block will
92
+ # not be run. If key does not exist in memcached, block will be executed,
93
+ # it's value stored in memcached under key, and value will be returned.
94
+ #
95
+ # There's no need to sweep the cache because the SHA of the latest Git commit
96
+ # is appended to the key, so any database change invalidates all cached
97
+ # objects.
98
+ def self.cache(branch, key, &block)
99
+ key = "#{key}-#{head_sha(branch)}"
100
+ value = nil
101
+ if memcache_servers
102
+ @@memcache ||= MemCache.new memcache_servers, :namespace => "#{File.basename(db_root)}-#{memcache_namespace}"
103
+ value = @@memcache.get(key)
104
+ if value.nil?
105
+ logger.info("✗ memcache MISS for key #{key}")
106
+ value = yield
107
+ @@memcache.set(key, value)
108
+ else
109
+ logger.info("✔ memcache HIT for key #{key}")
110
+ end
111
+ else
112
+ logger.debug("No memcache servers defined, not checking cache for key #{key}")
113
+ value = yield
114
+ end
115
+ value
116
+ end
117
+
118
+ private
119
+
120
+ # A more efficient way to get the SHA of the HEAD of the given branch
121
+ def self.head_sha(branch_name)
122
+ ref = File.join(repo.git.git_dir, "refs/heads/#{branch_name}")
123
+ File.exist?(ref) ? File.read(ref).chomp : nil
124
+ end
83
125
  end
@@ -4,11 +4,11 @@ module GitModel
4
4
  @model_class = model_class
5
5
  end
6
6
 
7
- def generate!
7
+ def generate!(branch)
8
8
  GitModel.logger.debug "Generating indexes for #{@model_class}"
9
9
  # TODO it sucks to load every instance here, optimize later
10
10
  @indexes = {}
11
- @model_class.find_all.each do |o|
11
+ @model_class.find_all(:branch => branch).each do |o|
12
12
  o.attributes.each do |attr, value|
13
13
  @indexes[attr] ||= {}
14
14
  @indexes[attr][value] ||= SortedSet.new
@@ -19,24 +19,27 @@ module GitModel
19
19
 
20
20
  def attr_index(attr)
21
21
  self.load unless @indexes
22
- return nil unless @indexes # this is just so that we can stub self.load in tests
23
-
24
- ret = @indexes[attr.to_s]
25
- raise GitModel::AttributeNotIndexed.new(attr.to_s) unless ret
26
- return ret
22
+ unless @indexes # this is just so that we can stub self.load in tests
23
+ nil
24
+ else
25
+ ret = @indexes[attr.to_s]
26
+ raise GitModel::AttributeNotIndexed.new(attr.to_s) unless ret
27
+ ret
28
+ end
27
29
  end
28
30
 
29
31
  def filename
30
32
  File.join(@model_class.db_subdir, '_indexes.json')
31
33
  end
32
34
 
33
- def generated?
34
- (GitModel.current_tree / filename) ? true : false
35
+ def generated?(branch = GitModel.default_branch)
36
+ (GitModel.current_tree(branch) / filename) ? true : false
35
37
  end
36
38
 
37
39
  def save(options = {})
38
40
  GitModel.logger.debug "Saving indexes for #{@model_class}..."
39
41
  transaction = options.delete(:transaction) || GitModel::Transaction.new(options)
42
+ branch = transaction.branch || options.delete(:branch) || GitModel.default_branch
40
43
  result = transaction.execute do |t|
41
44
  # convert to array because JSON hash keys must be strings
42
45
  data = []
@@ -52,26 +55,28 @@ module GitModel
52
55
  end
53
56
  end
54
57
 
55
- def load
56
- unless generated?
57
- GitModel.logger.debug "No index generated for #{@model_class}, not loading."
58
- return
59
- end
60
-
61
- GitModel.logger.debug "Loading indexes for #{@model_class}..."
62
- @indexes = {}
63
- blob = GitModel.current_tree / filename
64
-
65
- data = Yajl::Parser.parse(blob.data)
66
- data.each do |attr_and_values|
67
- attr = attr_and_values[0]
68
- values = {}
69
- attr_and_values[1].each do |value_and_ids|
70
- value = value_and_ids[0]
71
- ids = SortedSet.new(value_and_ids[1])
72
- values[value] = ids
58
+ def load(branch = GitModel.default_branch)
59
+ @indexes = GitModel.cache(branch, "#{@model_class.db_subdir}-index-load") do
60
+ unless generated?(branch)
61
+ GitModel.logger.debug "No index generated for #{@model_class}, on branch #{branch}, not loading."
62
+ else
63
+ GitModel.logger.debug "Loading indexes for #{@model_class}..."
64
+ indexes = {}
65
+ blob = GitModel.current_tree(branch) / filename
66
+
67
+ data = Yajl::Parser.parse(blob.data)
68
+ data.each do |attr_and_values|
69
+ attr = attr_and_values[0]
70
+ values = {}
71
+ attr_and_values[1].each do |value_and_ids|
72
+ value = value_and_ids[0]
73
+ ids = SortedSet.new(value_and_ids[1])
74
+ values[value] = ids
75
+ end
76
+ indexes[attr] = values
77
+ end
73
78
  end
74
- @indexes[attr] = values
79
+ indexes
75
80
  end
76
81
  end
77
82
 
@@ -132,13 +132,13 @@ module GitModel
132
132
 
133
133
  private
134
134
 
135
- def load(dir)
135
+ def load(dir, branch)
136
136
  _run_find_callbacks do
137
137
  # remove dangerous ".."
138
138
  # todo find a better way to ensure path is safe
139
139
  dir.gsub!(/\.\./, '')
140
140
 
141
- raise GitModel::RecordNotFound if GitModel.current_tree.nil?
141
+ raise GitModel::RecordNotFound if GitModel.current_tree(branch).nil?
142
142
 
143
143
  self.id = File.basename(dir)
144
144
  @new_record = false
@@ -146,13 +146,13 @@ module GitModel
146
146
  GitModel.logger.debug "Loading #{self.class.name} with id: #{id}"
147
147
 
148
148
  # load the attributes
149
- object = GitModel.current_tree / File.join(dir, 'attributes.json')
149
+ object = GitModel.current_tree(branch) / File.join(dir, 'attributes.json')
150
150
  raise GitModel::RecordNotFound if object.nil?
151
151
 
152
152
  self.attributes = Yajl::Parser.parse(object.data)
153
153
 
154
154
  # load all other non-hidden files in the dir as blobs
155
- blobs = (GitModel.current_tree / dir).blobs.reject{|b| b.name[0] == '.' || b.name == 'attributes.json'}
155
+ blobs = (GitModel.current_tree(branch) / dir).blobs.reject{|b| b.name[0] == '.' || b.name == 'attributes.json'}
156
156
  blobs.each do |b|
157
157
  self.blobs[b.name] = b.data
158
158
  end
@@ -181,95 +181,115 @@ module GitModel
181
181
  EOF
182
182
  end
183
183
 
184
- def find(id)
184
+ def find(id, branch = GitModel.default_branch)
185
185
  GitModel.logger.debug "Finding #{name} with id: #{id}"
186
- o = new
187
- dir = File.join(db_subdir, id)
188
- o.send :load, dir
189
- return o
186
+ result = GitModel.cache(branch, "#{db_subdir}-find-#{id}") do
187
+ o = new
188
+ dir = File.join(db_subdir, id)
189
+ o.send :load, dir, branch
190
+ o
191
+ end
192
+ return result
190
193
  end
191
194
 
192
- def exists?(id)
195
+ def exists?(id, branch = GitModel.default_branch)
193
196
  GitModel.logger.debug "Checking existence of #{name} with id: #{id}"
194
- GitModel.repo.commits.any? && !(GitModel.current_tree / File.join(db_subdir, id, 'attributes.json')).nil?
197
+ result = GitModel.cache(branch, "#{db_subdir}-exists-#{id}") do
198
+ GitModel.repo.commits.any? && !(GitModel.current_tree(branch) / File.join(db_subdir, id, 'attributes.json')).nil?
199
+ end
200
+ return result
195
201
  end
196
202
 
203
+ # TODO document conditions
204
+ # :branch
205
+ # :cache_key
206
+ # :order_by
207
+ # :order
208
+ # any model attribute
197
209
  def find_all(conditions = {})
210
+ branch = conditions.delete(:branch) || GitModel.default_branch
198
211
  # TODO Refactor this spaghetti
199
212
  GitModel.logger.debug "Finding all #{name.pluralize} with conditions: #{conditions.inspect}"
200
- return [] unless GitModel.current_tree
213
+ cache_key = "#{db_subdir}-find_all-#{format_conditions_hash_for_cache_key(conditions)}"
214
+ cached_results = GitModel.cache(branch, cache_key) do
215
+ current_tree = GitModel.current_tree(branch)
216
+ unless current_tree
217
+ []
218
+ else
219
+ order = conditions.delete(:order) || :asc
220
+ order_by = conditions.delete(:order_by) || :id
221
+ limit = conditions.delete(:limit)
222
+
223
+ matching_ids = []
224
+ if conditions.empty? # load all objects
225
+ trees = (current_tree / db_subdir).trees
226
+ trees.each do |t|
227
+ matching_ids << t.name if t.blobs.any?
228
+ end
229
+ else # only load objects that match conditions
230
+ matching_ids_for_condition = {}
231
+ conditions.each do |k,v|
232
+ matching_ids_for_condition[k] = []
233
+ if k == :id # id isn't indexed
234
+ if v.is_a?(Proc)
235
+ trees = (current_tree / db_subdir).trees
236
+ trees.each do |t|
237
+ matching_ids_for_condition[k] << t.name if t.blobs.any? && v.call(t.name)
238
+ end
239
+ else
240
+ # an unlikely use case but supporting it for completeness
241
+ matching_ids_for_condition[k] << v if (current_tree / db_subdir / v)
242
+ end
243
+ else
244
+ raise GitModel::IndexRequired unless index.generated?
245
+ attr_index = index.attr_index(k)
246
+ if v.is_a?(Proc)
247
+ attr_index.each do |value, ids|
248
+ matching_ids_for_condition[k] += ids.to_a if v.call(value)
249
+ end
250
+ else
251
+ matching_ids_for_condition[k] += attr_index[v].to_a
252
+ end
253
+ end
254
+ end
255
+ matching_ids += matching_ids_for_condition.values.inject{|memo, obj| memo & obj}
256
+ end
201
257
 
202
- order = conditions.delete(:order) || :asc
203
- order_by = conditions.delete(:order_by) || :id
204
- limit = conditions.delete(:limit)
258
+ results = nil
259
+ if order_by != :id
260
+ GitModel.logger.warn "Ordering by an attribute other than id requires loading all matching objects before applying limit, this will be slow" if limit
261
+ results = matching_ids.map{|k| find(k)}
205
262
 
206
- matching_ids = []
207
- if conditions.empty? # load all objects
208
- trees = (GitModel.current_tree / db_subdir).trees
209
- trees.each do |t|
210
- matching_ids << t.name if t.blobs.any?
211
- end
212
- else # only load objects that match conditions
213
- matching_ids_for_condition = {}
214
- conditions.each do |k,v|
215
- matching_ids_for_condition[k] = []
216
- if k == :id # id isn't indexed
217
- if v.is_a?(Proc)
218
- trees = (GitModel.current_tree / db_subdir).trees
219
- trees.each do |t|
220
- matching_ids_for_condition[k] << t.name if t.blobs.any? && v.call(t.name)
221
- end
263
+ if order == :asc
264
+ results = results.sort{|a,b| a.send(order_by) <=> b.send(order_by)}
265
+ elsif order == :desc
266
+ results = results.sort{|b,a| a.send(order_by) <=> b.send(order_by)}
222
267
  else
223
- # an unlikely use case but supporting it for completeness
224
- matching_ids_for_condition[k] << v if (GitModel.current_tree / db_subdir / v)
268
+ raise GitModel::InvalidParams("invalid order: '#{order}'")
269
+ end
270
+
271
+ if limit
272
+ results = results[0, limit]
225
273
  end
226
274
  else
227
- raise GitModel::IndexRequired unless index.generated?
228
- attr_index = index.attr_index(k)
229
- if v.is_a?(Proc)
230
- attr_index.each do |value, ids|
231
- matching_ids_for_condition[k] += ids.to_a if v.call(value)
232
- end
275
+ if order == :asc
276
+ matching_ids = matching_ids.sort{|a,b| a <=> b}
277
+ elsif order == :desc
278
+ matching_ids = matching_ids.sort{|b,a| a <=> b}
233
279
  else
234
- matching_ids_for_condition[k] += attr_index[v].to_a
280
+ raise GitModel::InvalidParams("invalid order: '#{order}'")
235
281
  end
236
- end
237
- end
238
- matching_ids += matching_ids_for_condition.values.inject{|memo, obj| memo & obj}
239
- end
240
-
241
- results = nil
242
- if order_by != :id
243
- GitModel.logger.warn "Ordering by an attribute other than id requires loading all matching objects before applying limit, this will be slow" if limit
244
- results = matching_ids.map{|k| find(k)}
282
+ if limit
245
283
 
246
- if order == :asc
247
- results = results.sort{|a,b| a.send(order_by) <=> b.send(order_by)}
248
- elsif order == :desc
249
- results = results.sort{|b,a| a.send(order_by) <=> b.send(order_by)}
250
- else
251
- raise GitModel::InvalidParams("invalid order: '#{order}'")
252
- end
253
-
254
- if limit
255
- results = results[0, limit]
256
- end
257
- else
258
- if order == :asc
259
- matching_ids = matching_ids.sort{|a,b| a <=> b}
260
- elsif order == :desc
261
- matching_ids = matching_ids.sort{|b,a| a <=> b}
262
- else
263
- raise GitModel::InvalidParams("invalid order: '#{order}'")
264
- end
265
- if limit
284
+ matching_ids = matching_ids[0, limit]
285
+ end
286
+ results = matching_ids.map{|k| find(k)}
287
+ end
266
288
 
267
- matching_ids = matching_ids[0, limit]
289
+ results
268
290
  end
269
- results = matching_ids.map{|k| find(k)}
270
- end
271
-
272
- return results
291
+ end # cached block
292
+ return cached_results
273
293
  end
274
294
 
275
295
  def all_values_for_attr(attr)
@@ -302,7 +322,8 @@ module GitModel
302
322
  path = File.join(db_subdir, id)
303
323
  transaction = options.delete(:transaction) || GitModel::Transaction.new(options)
304
324
  result = transaction.execute do |t|
305
- delete_tree(path, t.index, options)
325
+ branch = t.branch || options[:branch] || GitModel.default_branch
326
+ delete_tree(path, t.index, branch, options)
306
327
  end
307
328
  end
308
329
 
@@ -310,19 +331,20 @@ module GitModel
310
331
  GitModel.logger.debug "Deleting all #{name.pluralize}"
311
332
  transaction = options.delete(:transaction) || GitModel::Transaction.new(options)
312
333
  result = transaction.execute do |t|
313
- delete_tree(db_subdir, t.index, options)
334
+ branch = t.branch || options[:branch] || GitModel.default_branch
335
+ delete_tree(db_subdir, t.index, branch, options)
314
336
  end
315
337
  end
316
338
 
317
- def index!
318
- index.generate!
319
- index.save
339
+ def index!(branch)
340
+ index.generate!(branch)
341
+ index.save(:branch => branch)
320
342
  end
321
343
 
322
344
 
323
345
  private
324
346
 
325
- def delete_tree(path, index, options = {})
347
+ def delete_tree(path, index, branch, options = {})
326
348
  # This leaves a bunch of empty sub-trees, there must be a way to just
327
349
  # replace the tree to be deleted with an empty tree that doesn't even
328
350
  # reference the sub-trees.
@@ -347,6 +369,25 @@ module GitModel
347
369
  return hash
348
370
  end
349
371
 
372
+ def format_conditions_hash_for_cache_key(hash)
373
+ # allow setting an explicit cache key, mostly because Proc.hash is
374
+ # usually different even with the same code and same parameters
375
+ cache_key = hash.delete(:cache_key)
376
+
377
+ unless cache_key
378
+ cache_key = ""
379
+ hash.inject('') do |s,kv|
380
+ key = kv[0]
381
+ val = kv[1]
382
+ if val.is_a?(Proc)
383
+ val = "proc-#{val.hash}"
384
+ end
385
+ cache_key += "#{key}:#{val};"
386
+ end
387
+ end
388
+ cache_key
389
+ end
390
+
350
391
  end # module ClassMethods
351
392
 
352
393
  end # module Persistable
@@ -7,7 +7,7 @@ describe GitModel::Index do
7
7
  TestEntity.create!(:id => "baz", :attributes => {:x => 2, :y => 2})
8
8
 
9
9
  @i = GitModel::Index.new(TestEntity)
10
- @i.generate!
10
+ @i.generate!(GitModel.default_branch)
11
11
  end
12
12
 
13
13
  it "has a hash for each attribute of the model" do
@@ -23,7 +23,7 @@ describe GitModel::Index do
23
23
  it "can regenerate itself" do
24
24
  @i.attr_index(:x).clear
25
25
  @i.attr_index(:x).should be_empty
26
- @i.generate!
26
+ @i.generate!(GitModel.default_branch)
27
27
  @i.attr_index(:x).should == {1 => SortedSet.new(["foo", "bar"]), 2 => SortedSet.new(["baz"])}
28
28
  end
29
29
 
@@ -202,15 +202,16 @@ describe GitModel::Persistable do
202
202
  it 'also deletes blobs associated with the given object' do
203
203
  id = 'Lemuridae'
204
204
  TestEntity.create!(:id => id, :blobs => {:crowned => "Eulemur coronatus", :brown => "Eulemur fulvus"})
205
- (GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'crowned')).data.should_not be_nil
206
- (GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'brown')).data.should_not be_nil
205
+ b = GitModel.default_branch
206
+ (GitModel.current_tree(b) / File.join(TestEntity.db_subdir, id, 'crowned')).data.should_not be_nil
207
+ (GitModel.current_tree(b) / File.join(TestEntity.db_subdir, id, 'brown')).data.should_not be_nil
207
208
  TestEntity.delete(id)
208
209
 
209
- (GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'attributes.json')).should be_nil
210
- (GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'attributes.json')).should be_nil
210
+ (GitModel.current_tree(b) / File.join(TestEntity.db_subdir, id, 'attributes.json')).should be_nil
211
+ (GitModel.current_tree(b) / File.join(TestEntity.db_subdir, id, 'attributes.json')).should be_nil
211
212
 
212
- (GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'crowned')).should be_nil
213
- (GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'brown')).should be_nil
213
+ (GitModel.current_tree(b) / File.join(TestEntity.db_subdir, id, 'crowned')).should be_nil
214
+ (GitModel.current_tree(b) / File.join(TestEntity.db_subdir, id, 'brown')).should be_nil
214
215
  end
215
216
 
216
217
 
@@ -223,7 +224,7 @@ describe GitModel::Persistable do
223
224
  TestEntity.create!(:id => 'ape')
224
225
 
225
226
  TestEntity.delete_all
226
- TestEntity.index!
227
+ TestEntity.index!(GitModel.default_branch)
227
228
  TestEntity.find_all.should be_empty
228
229
  end
229
230
 
@@ -347,7 +348,7 @@ describe GitModel::Persistable do
347
348
  TestEntity.create!(:id => 'one', :attributes => {:a => 1, :b => 1})
348
349
  TestEntity.create!(:id => 'two', :attributes => {:a => 2, :b => 2})
349
350
  TestEntity.create!(:id => 'three', :attributes => {:a => 1, :b => 3})
350
- TestEntity.index!
351
+ TestEntity.index!(GitModel.default_branch)
351
352
 
352
353
  r = TestEntity.find_all(:a => 1)
353
354
  r.size.should == 2
@@ -361,7 +362,7 @@ describe GitModel::Persistable do
361
362
  TestEntity.create!(:id => 'one', :attributes => {:a => 1, :b => 1})
362
363
  TestEntity.create!(:id => 'two', :attributes => {:a => 2, :b => 2})
363
364
  TestEntity.create!(:id => 'three', :attributes => {:a => 1, :b => 3})
364
- TestEntity.index!
365
+ TestEntity.index!(GitModel.default_branch)
365
366
 
366
367
  r = TestEntity.find_all(:b => lambda{|b| b > 1}, :order => :asc)
367
368
  r.size.should == 2
@@ -377,7 +378,7 @@ describe GitModel::Persistable do
377
378
  TestEntity.create!(:id => 'one', :attributes => {:a => 1, :b => 2})
378
379
  TestEntity.create!(:id => 'two', :attributes => {:a => 1, :b => 2})
379
380
  TestEntity.create!(:id => 'three', :attributes => {:a => 1, :b => 1})
380
- TestEntity.index!
381
+ TestEntity.index!(GitModel.default_branch)
381
382
 
382
383
  r = TestEntity.find_all(:a => 1, :b => 2, :order => :asc)
383
384
  r.size.should == 2
@@ -392,7 +393,7 @@ describe GitModel::Persistable do
392
393
  TestEntity.create!(:id => 'two', :attributes => {:a => 2, :b => 2})
393
394
  TestEntity.create!(:id => 'three', :attributes => {:a => 1, :b => 1})
394
395
  TestEntity.create!(:id => 'four', :attributes => {:a => 3, :b => 3})
395
- TestEntity.index!
396
+ TestEntity.index!(GitModel.default_branch)
396
397
 
397
398
  r = TestEntity.find_all(:a => lambda{|a| a > 1}, :b => lambda{|b| b > 2}, :order => :asc)
398
399
  r.size.should == 1
@@ -434,7 +435,7 @@ describe GitModel::Persistable do
434
435
  TestEntity.create!(:id => 'one', :attributes => {:a => 1, :b => 1})
435
436
  TestEntity.create!(:id => 'two', :attributes => {:a => 2, :b => 2})
436
437
  TestEntity.create!(:id => 'three', :attributes => {:a => 1, :b => 3})
437
- TestEntity.index!
438
+ TestEntity.index!(GitModel.default_branch)
438
439
 
439
440
  r = TestEntity.find_all(:a => 1, :order => :asc)
440
441
  r.size.should == 2
@@ -446,7 +447,7 @@ describe GitModel::Persistable do
446
447
  TestEntity.create!(:id => 'one', :attributes => {:a => 1, :b => 1})
447
448
  TestEntity.create!(:id => 'two', :attributes => {:a => 2, :b => 2})
448
449
  TestEntity.create!(:id => 'three', :attributes => {:a => 1, :b => 3})
449
- TestEntity.index!
450
+ TestEntity.index!(GitModel.default_branch)
450
451
 
451
452
  r = TestEntity.find_all(:a => 1, :order => :desc)
452
453
  r.size.should == 2
@@ -458,7 +459,7 @@ describe GitModel::Persistable do
458
459
  TestEntity.create!(:id => 'one', :attributes => {:a => 1, :b => 1})
459
460
  TestEntity.create!(:id => 'two', :attributes => {:a => 1, :b => 2})
460
461
  TestEntity.create!(:id => 'three', :attributes => {:a => 1, :b => 3})
461
- TestEntity.index!
462
+ TestEntity.index!(GitModel.default_branch)
462
463
 
463
464
  r = TestEntity.find_all(:a => 1, :order => :asc, :limit => 2)
464
465
  r.size.should == 2
@@ -470,7 +471,7 @@ describe GitModel::Persistable do
470
471
  TestEntity.create!(:id => 'one', :attributes => {:a => 1, :b => 1})
471
472
  TestEntity.create!(:id => 'two', :attributes => {:a => 1, :b => 2})
472
473
  TestEntity.create!(:id => 'three', :attributes => {:a => 1, :b => 3})
473
- TestEntity.index!
474
+ TestEntity.index!(GitModel.default_branch)
474
475
 
475
476
  r = TestEntity.find_all(:a => 1, :order => :desc, :limit => 2)
476
477
  r.size.should == 2
@@ -497,7 +498,7 @@ describe GitModel::Persistable do
497
498
  it "generates and saves the index" do
498
499
  TestEntity.index.should_receive(:generate!)
499
500
  TestEntity.index.should_receive(:save)
500
- TestEntity.index!
501
+ TestEntity.index!(GitModel.default_branch)
501
502
  end
502
503
  end
503
504
 
@@ -505,7 +506,7 @@ describe GitModel::Persistable do
505
506
  it 'returns a list of all values that exist for a given attribute' do
506
507
  o = TestEntity.create!(:id => 'first', :attributes => {"a" => 1, "b" => 2})
507
508
  o = TestEntity.create!(:id => 'second', :attributes => {"a" => 3, "b" => 4})
508
- TestEntity.index!
509
+ TestEntity.index!(GitModel.default_branch)
509
510
  TestEntity.all_values_for_attr("a").should == [1, 3]
510
511
  end
511
512
  end
@@ -4,7 +4,7 @@ describe GitModel do
4
4
 
5
5
  describe ".last_commit" do
6
6
  it "returns nil if there are no commits" do
7
- GitModel.last_commit.should == nil
7
+ GitModel.last_commit(GitModel.default_branch).should == nil
8
8
  end
9
9
 
10
10
  it "returns the most recent commit on a branch" do
@@ -14,13 +14,13 @@ describe GitModel do
14
14
  index.add "foo", "foo"
15
15
  sha = index.commit nil, nil, nil, nil, 'master'
16
16
 
17
- GitModel.last_commit.to_s.should == sha
17
+ GitModel.last_commit(GitModel.default_branch).to_s.should == sha
18
18
  end
19
19
  end
20
20
 
21
21
  describe ".current_tree" do
22
22
  it "returns nil if there are no commits" do
23
- GitModel.current_tree.should == nil
23
+ GitModel.current_tree(GitModel.default_branch).should == nil
24
24
  end
25
25
 
26
26
  it "returns the tree for the most recent commit on a branch" do
@@ -40,7 +40,7 @@ describe GitModel do
40
40
  it "calls index! on each Persistable model class" do
41
41
  TestEntity.should_receive(:index!)
42
42
  TestEntity2.should_receive(:index!)
43
- GitModel.index!
43
+ GitModel.index!(GitModel.default_branch)
44
44
  end
45
45
  end
46
46
 
data/spec/spec_helper.rb CHANGED
@@ -18,3 +18,4 @@ class TestEntity2
18
18
  end
19
19
 
20
20
  #GitModel.logger.level = ::Logger::DEBUG
21
+ GitModel.memcache_servers = ['localhost']
metadata CHANGED
@@ -1,114 +1,125 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: gitmodel
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
4
5
  prerelease:
5
- version: 0.0.4
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Paul Dowman
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-07-04 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
12
+ date: 2011-08-03 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
16
15
  name: activemodel
17
- prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &70218492110080 !ruby/object:Gem::Requirement
19
17
  none: false
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
23
21
  version: 3.0.1
24
22
  type: :runtime
25
- version_requirements: *id001
26
- - !ruby/object:Gem::Dependency
27
- name: activesupport
28
23
  prerelease: false
29
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: *70218492110080
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70218492109600 !ruby/object:Gem::Requirement
30
28
  none: false
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
34
32
  version: 3.0.1
35
33
  type: :runtime
36
- version_requirements: *id002
37
- - !ruby/object:Gem::Dependency
38
- name: grit
39
34
  prerelease: false
40
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *70218492109600
36
+ - !ruby/object:Gem::Dependency
37
+ name: grit
38
+ requirement: &70218492135740 !ruby/object:Gem::Requirement
41
39
  none: false
42
- requirements:
43
- - - ">="
44
- - !ruby/object:Gem::Version
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
45
43
  version: 2.3.0
46
44
  type: :runtime
47
- version_requirements: *id003
48
- - !ruby/object:Gem::Dependency
49
- name: lockfile
50
45
  prerelease: false
51
- requirement: &id004 !ruby/object:Gem::Requirement
46
+ version_requirements: *70218492135740
47
+ - !ruby/object:Gem::Dependency
48
+ name: lockfile
49
+ requirement: &70218492135280 !ruby/object:Gem::Requirement
52
50
  none: false
53
- requirements:
54
- - - ">="
55
- - !ruby/object:Gem::Version
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
56
54
  version: 1.4.3
57
55
  type: :runtime
58
- version_requirements: *id004
59
- - !ruby/object:Gem::Dependency
60
- name: yajl-ruby
61
56
  prerelease: false
62
- requirement: &id005 !ruby/object:Gem::Requirement
57
+ version_requirements: *70218492135280
58
+ - !ruby/object:Gem::Dependency
59
+ name: memcache-client
60
+ requirement: &70218492134820 !ruby/object:Gem::Requirement
63
61
  none: false
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 1.8.5
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70218492134820
69
+ - !ruby/object:Gem::Dependency
70
+ name: yajl-ruby
71
+ requirement: &70218492134360 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
67
76
  version: 0.8.2
68
77
  type: :runtime
69
- version_requirements: *id005
70
- - !ruby/object:Gem::Dependency
71
- name: ZenTest
72
78
  prerelease: false
73
- requirement: &id006 !ruby/object:Gem::Requirement
79
+ version_requirements: *70218492134360
80
+ - !ruby/object:Gem::Dependency
81
+ name: ZenTest
82
+ requirement: &70218492133900 !ruby/object:Gem::Requirement
74
83
  none: false
75
- requirements:
76
- - - ">="
77
- - !ruby/object:Gem::Version
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
78
87
  version: 4.4.0
79
88
  type: :development
80
- version_requirements: *id006
81
- - !ruby/object:Gem::Dependency
82
- name: autotest
83
89
  prerelease: false
84
- requirement: &id007 !ruby/object:Gem::Requirement
90
+ version_requirements: *70218492133900
91
+ - !ruby/object:Gem::Dependency
92
+ name: autotest
93
+ requirement: &70218492133440 !ruby/object:Gem::Requirement
85
94
  none: false
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
89
98
  version: 4.4.1
90
99
  type: :development
91
- version_requirements: *id007
92
- - !ruby/object:Gem::Dependency
93
- name: rspec
94
100
  prerelease: false
95
- requirement: &id008 !ruby/object:Gem::Requirement
101
+ version_requirements: *70218492133440
102
+ - !ruby/object:Gem::Dependency
103
+ name: rspec
104
+ requirement: &70218492132980 !ruby/object:Gem::Requirement
96
105
  none: false
97
- requirements:
98
- - - ">="
99
- - !ruby/object:Gem::Version
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
100
109
  version: 2.0.1
101
110
  type: :development
102
- version_requirements: *id008
103
- description: GitModel persists Ruby objects using Git as a data storage engine. It's an ActiveModel implementation so it works stand-alone or in Rails 3 as a drop-in replacement for ActiveRecord or DataMapper. Because the database is a Git repository it can be synced across multiple machines, manipulated with standard Git client tools, can be branched and merged, and of course keeps the history of all changes.
111
+ prerelease: false
112
+ version_requirements: *70218492132980
113
+ description: GitModel persists Ruby objects using Git as a data storage engine. It's
114
+ an ActiveModel implementation so it works stand-alone or in Rails 3 as a drop-in
115
+ replacement for ActiveRecord or DataMapper. Because the database is a Git repository
116
+ it can be synced across multiple machines, manipulated with standard Git client
117
+ tools, can be branched and merged, and of course keeps the history of all changes.
104
118
  email: paul@pauldowman.com
105
119
  executables: []
106
-
107
120
  extensions: []
108
-
109
121
  extra_rdoc_files: []
110
-
111
- files:
122
+ files:
112
123
  - .document
113
124
  - .gitignore
114
125
  - .rspec
@@ -132,32 +143,30 @@ files:
132
143
  - spec/support/setup.rb
133
144
  homepage: http://github.com/pauldowman/gitmodel
134
145
  licenses: []
135
-
136
146
  post_install_message:
137
147
  rdoc_options: []
138
-
139
- require_paths:
148
+ require_paths:
140
149
  - lib
141
- required_ruby_version: !ruby/object:Gem::Requirement
150
+ required_ruby_version: !ruby/object:Gem::Requirement
142
151
  none: false
143
- requirements:
144
- - - ">="
145
- - !ruby/object:Gem::Version
146
- version: "0"
147
- required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
157
  none: false
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: "0"
158
+ requirements:
159
+ - - ! '>='
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
153
162
  requirements: []
154
-
155
163
  rubyforge_project:
156
- rubygems_version: 1.8.5
164
+ rubygems_version: 1.8.6
157
165
  signing_key:
158
166
  specification_version: 3
159
- summary: An ActiveModel-compliant persistence framework for Ruby that uses Git for versioning and remote syncing.
160
- test_files:
167
+ summary: An ActiveModel-compliant persistence framework for Ruby that uses Git for
168
+ versioning and remote syncing.
169
+ test_files:
161
170
  - spec/gitmodel/index_spec.rb
162
171
  - spec/gitmodel/persistable_spec.rb
163
172
  - spec/gitmodel/transaction_spec.rb