mongo_record 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -0
- data/lib/mongo_record/base.rb +97 -18
- data/mongo-activerecord-ruby.gemspec +1 -1
- data/tests/test_mongo.rb +147 -10
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -83,3 +83,13 @@ Jim Mulholland, jim at squeejee dot com
|
|
83
83
|
|
84
84
|
Clinton R. Nixon, crnixon at gmail dot com
|
85
85
|
* Ability to define and query indexes from models
|
86
|
+
|
87
|
+
Nate Wiger, http://github.com/nateware
|
88
|
+
* Optimization to first and last to close cursor and avoid expensive to_a
|
89
|
+
* Implemented Model.update_all leveraging Mongo collection.update
|
90
|
+
* Scoped dynamic finders to each instance, so rows with varying attributes work
|
91
|
+
* Added row.attributes helper to enable use of ActiveRecord::Callbacks if desired
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
data/lib/mongo_record/base.rb
CHANGED
@@ -18,6 +18,7 @@ require 'mongo/types/code'
|
|
18
18
|
require 'mongo/cursor'
|
19
19
|
require 'mongo_record/convert'
|
20
20
|
require 'mongo_record/sql'
|
21
|
+
#require 'active_support/core_ext' # symbolize_keys!
|
21
22
|
|
22
23
|
class String
|
23
24
|
# Convert this String to an ObjectID.
|
@@ -110,6 +111,7 @@ module MongoRecord
|
|
110
111
|
subclass.instance_variable_set("@field_names", []) # array of scalars names (symbols)
|
111
112
|
subclass.instance_variable_set("@subobjects", {}) # key = name (symbol), value = class
|
112
113
|
subclass.instance_variable_set("@arrays", {}) # key = name (symbol), value = class
|
114
|
+
subclass.field(:_id, :_ns)
|
113
115
|
end
|
114
116
|
|
115
117
|
# Call this method to set the Mongo collection name for this class.
|
@@ -117,7 +119,6 @@ module MongoRecord
|
|
117
119
|
# lower_case_with_underscores.
|
118
120
|
def collection_name(coll_name)
|
119
121
|
@coll_name = coll_name
|
120
|
-
field(:_id, :_ns)
|
121
122
|
end
|
122
123
|
|
123
124
|
# Creates one or more collection fields. Each field will be saved to
|
@@ -130,6 +131,7 @@ module MongoRecord
|
|
130
131
|
field = field.to_sym
|
131
132
|
unless @field_names.include?(field)
|
132
133
|
ivar_name = "@" + field.to_s
|
134
|
+
# this is better than lambda because it's only eval'ed once
|
133
135
|
define_method(field, lambda { instance_variable_get(ivar_name) })
|
134
136
|
define_method("#{field}=".to_sym, lambda { |val| instance_variable_set(ivar_name, val) })
|
135
137
|
define_method("#{field}?".to_sym, lambda {
|
@@ -162,7 +164,7 @@ module MongoRecord
|
|
162
164
|
end
|
163
165
|
|
164
166
|
fields = fields.map do |field|
|
165
|
-
field = field.
|
167
|
+
field = field.is_a?(Array) ? field : [field, :asc]
|
166
168
|
field[1] = (field[1] == :desc) ? Mongo::DESCENDING : Mongo::ASCENDING
|
167
169
|
field
|
168
170
|
end
|
@@ -395,7 +397,7 @@ module MongoRecord
|
|
395
397
|
end
|
396
398
|
|
397
399
|
def sum(column)
|
398
|
-
x =
|
400
|
+
x = all(:select => column)
|
399
401
|
x.map {|p1| p1[column.to_sym]}.compact.inject(0) { |s,v| s += v }
|
400
402
|
end
|
401
403
|
|
@@ -410,17 +412,24 @@ module MongoRecord
|
|
410
412
|
id.is_a?(Array) ? id.each { |oid| destroy(oid) } : find(id).destroy
|
411
413
|
end
|
412
414
|
|
413
|
-
#
|
414
|
-
|
415
|
-
|
416
|
-
|
415
|
+
# This updates all records matching the specified criteria. It leverages the
|
416
|
+
# db.update call from the Mongo core API to guarantee atomicity. You can
|
417
|
+
# specify either a hash for simplicity, or full Mongo API operators to the
|
418
|
+
# update part of the method call:
|
419
|
+
#
|
420
|
+
# Person.update_all({:name => 'Bob'}, {:name => 'Fred'})
|
421
|
+
# Person.update_all({'$set' => {:name => 'Bob'}, '$inc' => {:age => 1}}, {:name => 'Fred'})
|
422
|
+
def update_all(updates, conditions = nil, options = {})
|
423
|
+
all(:conditions => conditions).each do |row|
|
424
|
+
collection.update(criteria_from(conditions).merge(:_id => row.id.to_oid), update_fields_from(updates), options)
|
425
|
+
end
|
417
426
|
end
|
418
427
|
|
419
428
|
# Destroy all objects that match +conditions+. Warning: if
|
420
429
|
# +conditions+ is +nil+, all records in the collection will be
|
421
430
|
# destroyed.
|
422
431
|
def destroy_all(conditions = nil)
|
423
|
-
|
432
|
+
all(:conditions => conditions).each { |object| object.destroy }
|
424
433
|
end
|
425
434
|
|
426
435
|
# Deletes all records that match +condition+, which can be a
|
@@ -451,13 +460,13 @@ module MongoRecord
|
|
451
460
|
# Example of updating multiple records:
|
452
461
|
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
|
453
462
|
# Person.update(people.keys, people.values)
|
454
|
-
def update(id,
|
463
|
+
def update(id, attrib)
|
455
464
|
if id.is_a?(Array)
|
456
465
|
i = -1
|
457
|
-
id.collect { |id| i += 1; update(id,
|
466
|
+
id.collect { |id| i += 1; update(id, attrib[i]) }
|
458
467
|
else
|
459
468
|
object = find(id)
|
460
|
-
object.update_attributes(
|
469
|
+
object.update_attributes(attrib)
|
461
470
|
object
|
462
471
|
end
|
463
472
|
end
|
@@ -502,15 +511,21 @@ module MongoRecord
|
|
502
511
|
def find_initial(options)
|
503
512
|
options[:limit] = 1
|
504
513
|
options[:order] = 'created_at asc'
|
505
|
-
|
506
|
-
row.to_a[0]
|
514
|
+
find_one(options)
|
507
515
|
end
|
508
516
|
|
509
517
|
def find_last(options)
|
510
518
|
options[:limit] = 1
|
511
519
|
options[:order] = 'created_at desc'
|
512
|
-
|
513
|
-
|
520
|
+
find_one(options)
|
521
|
+
end
|
522
|
+
|
523
|
+
def find_one(options)
|
524
|
+
one = nil
|
525
|
+
cursor = find_every(options)
|
526
|
+
one = cursor.detect {|c| c}
|
527
|
+
cursor.close
|
528
|
+
one
|
514
529
|
end
|
515
530
|
|
516
531
|
def find_every(options)
|
@@ -523,7 +538,6 @@ module MongoRecord
|
|
523
538
|
find_options[:offset] = options[:offset].to_i if options[:offset]
|
524
539
|
find_options[:sort] = sort_by_from(options[:order]) if options[:order]
|
525
540
|
|
526
|
-
|
527
541
|
cursor = collection.find(criteria, find_options)
|
528
542
|
|
529
543
|
# Override cursor.next_object so it returns a new instance of this class
|
@@ -735,6 +749,26 @@ module MongoRecord
|
|
735
749
|
end
|
736
750
|
end
|
737
751
|
|
752
|
+
# Turns {:key => 'Value'} in update_all into the appropriate '$set' operator
|
753
|
+
def update_fields_from(arg)
|
754
|
+
raise "Update spec for #{self.name}.update_all must be a hash" unless arg.is_a?(Hash)
|
755
|
+
updates = {}
|
756
|
+
arg.each do |key,val|
|
757
|
+
case val
|
758
|
+
when Hash
|
759
|
+
# Assume something like $inc => {:num => 1}
|
760
|
+
updates[key] = val
|
761
|
+
when Array, Range
|
762
|
+
raise "Array/range not supported in value of update spec"
|
763
|
+
else
|
764
|
+
# Assume a simple value, so change to $set
|
765
|
+
updates['$set'] ||= {}
|
766
|
+
updates['$set'][key] = val
|
767
|
+
end
|
768
|
+
end
|
769
|
+
updates
|
770
|
+
end
|
771
|
+
|
738
772
|
# Overwrite the default class equality method to provide support for association proxies.
|
739
773
|
def ===(object)
|
740
774
|
object.is_a?(self)
|
@@ -767,9 +801,20 @@ module MongoRecord
|
|
767
801
|
iv = "@#{iv}"
|
768
802
|
instance_variable_set(iv, []) unless instance_variable_defined?(iv)
|
769
803
|
}
|
804
|
+
|
805
|
+
# Create accessors for any per-row dynamic fields we got from our schemaless store
|
806
|
+
self.instance_values.keys.each do |key|
|
807
|
+
next if respond_to?(key.to_sym) # exists
|
808
|
+
define_instance_accessors(key)
|
809
|
+
end
|
810
|
+
|
770
811
|
yield self if block_given?
|
771
812
|
end
|
772
813
|
|
814
|
+
def attributes
|
815
|
+
self.instance_values.inject({}){|h,iv| h[iv.first] = iv.last; h}
|
816
|
+
end
|
817
|
+
|
773
818
|
# Set the id of this object. Normally not called by user code.
|
774
819
|
def id=(val); @_id = (val == '' ? nil : val); end
|
775
820
|
|
@@ -882,13 +927,13 @@ module MongoRecord
|
|
882
927
|
end
|
883
928
|
|
884
929
|
def []=(attr_name, value)
|
885
|
-
|
930
|
+
define_instance_accessors(attr_name)
|
886
931
|
self.send(attr_name.to_s + '=', value)
|
887
932
|
end
|
888
933
|
|
889
934
|
def method_missing(sym, *args)
|
890
935
|
if self.instance_variables.include?("@#{sym}")
|
891
|
-
|
936
|
+
define_instance_accessors(sym)
|
892
937
|
return self.send(sym)
|
893
938
|
else
|
894
939
|
super
|
@@ -927,6 +972,9 @@ module MongoRecord
|
|
927
972
|
save!
|
928
973
|
end
|
929
974
|
|
975
|
+
def valid?; true; end
|
976
|
+
alias_method :respond_to_without_attributes?, :respond_to?
|
977
|
+
|
930
978
|
# Does nothing.
|
931
979
|
def attributes_from_column_definition; end
|
932
980
|
|
@@ -943,6 +991,15 @@ module MongoRecord
|
|
943
991
|
}
|
944
992
|
end
|
945
993
|
|
994
|
+
#--
|
995
|
+
# ================================================================
|
996
|
+
# "Dirty" attribute tracking, adapted from ActiveRecord. This is
|
997
|
+
# a big performance boost, plus it avoids issues if two people
|
998
|
+
# are updating a record concurrently.
|
999
|
+
# ================================================================
|
1000
|
+
#++
|
1001
|
+
|
1002
|
+
|
946
1003
|
private
|
947
1004
|
|
948
1005
|
def create_or_update
|
@@ -982,6 +1039,28 @@ module MongoRecord
|
|
982
1039
|
}
|
983
1040
|
end
|
984
1041
|
|
1042
|
+
# Per-object accessors, since row-to-row attributes can change
|
1043
|
+
# Use instance_eval so that they don't bleed over to other objects that lack the fields
|
1044
|
+
def define_instance_accessors(*fields)
|
1045
|
+
fields = Array(fields)
|
1046
|
+
fields.each do |field|
|
1047
|
+
ivar_name = "@" + field.to_s
|
1048
|
+
instance_eval <<-EndAccessors
|
1049
|
+
def #{field}
|
1050
|
+
instance_variable_get('#{ivar_name}')
|
1051
|
+
end
|
1052
|
+
def #{field}=(val)
|
1053
|
+
old = instance_variable_get('#{ivar_name}')
|
1054
|
+
instance_variable_set('#{ivar_name}', val)
|
1055
|
+
instance_variable_set('#{ivar_name}', val)
|
1056
|
+
end
|
1057
|
+
def #{field}?
|
1058
|
+
val = instance_variable_get('#{ivar_name}')
|
1059
|
+
val != nil && (!val.kind_of?(String) || val != '')
|
1060
|
+
end
|
1061
|
+
EndAccessors
|
1062
|
+
end
|
1063
|
+
end
|
985
1064
|
end
|
986
1065
|
|
987
1066
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'mongo_record'
|
3
|
-
s.version = '0.4.
|
3
|
+
s.version = '0.4.4'
|
4
4
|
s.platform = Gem::Platform::RUBY
|
5
5
|
s.summary = 'ActiveRecord-like models for the MongoDB'
|
6
6
|
s.description = 'MongoRecord is an ActiveRecord-like framework for MongoDB. For more information about Mongo, see http://www.mongodb.org.'
|
data/tests/test_mongo.rb
CHANGED
@@ -59,10 +59,10 @@ class MongoTest < Test::Unit::TestCase
|
|
59
59
|
super
|
60
60
|
MongoRecord::Base.connection = @@db
|
61
61
|
|
62
|
-
@@students.
|
63
|
-
@@courses.
|
64
|
-
@@tracks.
|
65
|
-
@@playlists.
|
62
|
+
@@students.remove
|
63
|
+
@@courses.remove
|
64
|
+
@@tracks.remove
|
65
|
+
@@playlists.remove
|
66
66
|
|
67
67
|
# Manually insert data without using MongoRecord::Base
|
68
68
|
@@tracks.insert({:_id => Mongo::ObjectID.new, :artist => 'Thomas Dolby', :album => 'Aliens Ate My Buick', :song => 'The Ability to Swing'})
|
@@ -85,10 +85,10 @@ class MongoTest < Test::Unit::TestCase
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def teardown
|
88
|
-
@@students.
|
89
|
-
@@courses.
|
90
|
-
@@tracks.
|
91
|
-
@@playlists.
|
88
|
+
@@students.remove
|
89
|
+
@@courses.remove
|
90
|
+
@@tracks.remove
|
91
|
+
@@playlists.remove
|
92
92
|
super
|
93
93
|
end
|
94
94
|
|
@@ -98,6 +98,11 @@ class MongoTest < Test::Unit::TestCase
|
|
98
98
|
assert t.instance_variable_defined?("@#{iv}")
|
99
99
|
}
|
100
100
|
end
|
101
|
+
|
102
|
+
def test_new_record_set_correctly
|
103
|
+
t = Track.new(:_id => 12345, :artist => 'Alice In Chains')
|
104
|
+
assert_equal true, t.new_record?
|
105
|
+
end
|
101
106
|
|
102
107
|
def test_method_generation
|
103
108
|
x = Track.new({:artist => 1, :album => 2})
|
@@ -124,6 +129,94 @@ class MongoTest < Test::Unit::TestCase
|
|
124
129
|
assert_nil(x.track)
|
125
130
|
end
|
126
131
|
|
132
|
+
def test_dynamic_methods_in_new
|
133
|
+
x = Track.new({:foo => 1, :bar => 2})
|
134
|
+
y = Track.new({:artist => 3, :song => 4})
|
135
|
+
|
136
|
+
assert x.respond_to?(:_id)
|
137
|
+
assert x.respond_to?(:artist)
|
138
|
+
assert x.respond_to?(:album)
|
139
|
+
assert x.respond_to?(:song)
|
140
|
+
assert x.respond_to?(:track)
|
141
|
+
assert x.respond_to?(:_id=)
|
142
|
+
assert x.respond_to?(:artist=)
|
143
|
+
assert x.respond_to?(:album=)
|
144
|
+
assert x.respond_to?(:song=)
|
145
|
+
assert x.respond_to?(:track=)
|
146
|
+
assert x.respond_to?(:_id?)
|
147
|
+
assert x.respond_to?(:artist?)
|
148
|
+
assert x.respond_to?(:album?)
|
149
|
+
assert x.respond_to?(:song?)
|
150
|
+
assert x.respond_to?(:track?)
|
151
|
+
|
152
|
+
# dynamic fields
|
153
|
+
assert x.respond_to?(:foo)
|
154
|
+
assert x.respond_to?(:bar)
|
155
|
+
assert x.respond_to?(:foo=)
|
156
|
+
assert x.respond_to?(:bar=)
|
157
|
+
assert x.respond_to?(:foo?)
|
158
|
+
assert x.respond_to?(:bar?)
|
159
|
+
|
160
|
+
# make sure accessors only per-object
|
161
|
+
assert !y.respond_to?(:foo)
|
162
|
+
assert !y.respond_to?(:bar)
|
163
|
+
assert !y.respond_to?(:foo=)
|
164
|
+
assert !y.respond_to?(:bar=)
|
165
|
+
assert !y.respond_to?(:foo?)
|
166
|
+
assert !y.respond_to?(:bar?)
|
167
|
+
|
168
|
+
assert_equal(1, x.foo)
|
169
|
+
assert_equal(2, x.bar)
|
170
|
+
assert_nil(x.song)
|
171
|
+
assert_nil(x.track)
|
172
|
+
assert_equal(3, y.artist)
|
173
|
+
assert_equal(4, y.song)
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_dynamic_methods_in_find
|
177
|
+
@@tracks.insert({:_id => 909, :artist => 'Faith No More', :album => 'Album Of The Year', :song => 'Stripsearch', :track => 2,
|
178
|
+
:vocals => 'Mike Patton', :drums => 'Mike Bordin', :producers => ['Roli Mosimann', 'Billy Gould']})
|
179
|
+
x = Track.find_by_id(909)
|
180
|
+
|
181
|
+
# defined
|
182
|
+
assert x.respond_to?(:_id)
|
183
|
+
assert x.respond_to?(:artist)
|
184
|
+
assert x.respond_to?(:album)
|
185
|
+
assert x.respond_to?(:song)
|
186
|
+
assert x.respond_to?(:track)
|
187
|
+
assert x.respond_to?(:_id=)
|
188
|
+
assert x.respond_to?(:artist=)
|
189
|
+
assert x.respond_to?(:album=)
|
190
|
+
assert x.respond_to?(:song=)
|
191
|
+
assert x.respond_to?(:track=)
|
192
|
+
assert x.respond_to?(:_id?)
|
193
|
+
assert x.respond_to?(:artist?)
|
194
|
+
assert x.respond_to?(:album?)
|
195
|
+
assert x.respond_to?(:song?)
|
196
|
+
assert x.respond_to?(:track?)
|
197
|
+
|
198
|
+
# dynamic fields
|
199
|
+
assert x.respond_to?(:vocals)
|
200
|
+
assert x.respond_to?(:drums)
|
201
|
+
assert x.respond_to?(:producers)
|
202
|
+
assert x.respond_to?(:vocals=)
|
203
|
+
assert x.respond_to?(:drums=)
|
204
|
+
assert x.respond_to?(:producers=)
|
205
|
+
assert x.respond_to?(:vocals?)
|
206
|
+
assert x.respond_to?(:drums?)
|
207
|
+
assert x.respond_to?(:producers?)
|
208
|
+
|
209
|
+
assert_equal 'Faith No More', x.artist
|
210
|
+
assert_equal 'Album Of The Year', x.album
|
211
|
+
assert_equal 'Stripsearch', x.song
|
212
|
+
assert_equal 2, x.track
|
213
|
+
assert_equal 'Mike Patton', x.vocals
|
214
|
+
assert_equal 'Mike Bordin', x.drums
|
215
|
+
assert_equal ['Roli Mosimann', 'Billy Gould'], x.producers
|
216
|
+
|
217
|
+
x.destroy
|
218
|
+
end
|
219
|
+
|
127
220
|
def test_initialize_block
|
128
221
|
track = Track.new { |t|
|
129
222
|
t.artist = "Me'Shell Ndegeocello"
|
@@ -293,6 +386,27 @@ class MongoTest < Test::Unit::TestCase
|
|
293
386
|
assert_no_match(/song: The Mayor Of Simpleton/, Track.find(:all).inject('') { |str, t| str + t.to_s })
|
294
387
|
end
|
295
388
|
|
389
|
+
def test_update_all
|
390
|
+
Track.update_all({:track => 919}, {:artist => 'XTC'})
|
391
|
+
Track.all.each{|r| assert_equal(919, r.track) if r.artist == 'XTC' }
|
392
|
+
|
393
|
+
# Should fail (can't $inc/$set) - remove this test once Mongo 1.2 is out
|
394
|
+
error = nil
|
395
|
+
begin
|
396
|
+
Track.update_all({:song => 'Just Drums'}, {}, :safe => true)
|
397
|
+
rescue Mongo::OperationFailure => error
|
398
|
+
end
|
399
|
+
assert_instance_of Mongo::OperationFailure, error
|
400
|
+
|
401
|
+
@@tracks.drop_index 'song_-1' # otherwise update_all $set fails
|
402
|
+
Track.update_all({:song => 'Just Drums'}, {}, :safe => true)
|
403
|
+
|
404
|
+
assert_no_match(/song: Budapest by Blimp/, Track.all.inject('') { |str, t| str + t.to_s })
|
405
|
+
|
406
|
+
assert_equal 6, Track.count
|
407
|
+
Track.index [:song, :desc], true # reindex
|
408
|
+
end
|
409
|
+
|
296
410
|
def test_delete_all
|
297
411
|
Track.delete_all({:artist => 'XTC'})
|
298
412
|
assert_no_match(/artist: XTC/, Track.find(:all).inject('') { |str, t| str + t.to_s })
|
@@ -635,7 +749,7 @@ class MongoTest < Test::Unit::TestCase
|
|
635
749
|
# Make sure collection exists
|
636
750
|
coll = alt_db.collection('students')
|
637
751
|
coll.insert('name' => 'foo')
|
638
|
-
coll.
|
752
|
+
coll.remove
|
639
753
|
|
640
754
|
assert_equal 0, coll.count()
|
641
755
|
s = Student.new(:name => 'Spongebob Squarepants', :address => @spongebob_addr)
|
@@ -730,7 +844,30 @@ class MongoTest < Test::Unit::TestCase
|
|
730
844
|
opts = {:artist => 'The Outfield', :album => 'Play Deep', :song => 'Your Love', :year => 1986}
|
731
845
|
playlist = Playlist.new
|
732
846
|
playlist.update_attributes(opts)
|
733
|
-
|
847
|
+
|
848
|
+
# We *want* the following to fail, because otherwise MongoRecord is buggy in the following
|
849
|
+
# situation:
|
850
|
+
#
|
851
|
+
# Rails/Sinatra/etc server instance #1 does: playlist.custom_field = 'foo'
|
852
|
+
# Rails/Sinatra/etc instance #2 attempts to do: Playlist.find_by_custom_field
|
853
|
+
#
|
854
|
+
# This will fail because, in previous versions of MongoRecord, the instance would callback
|
855
|
+
# into class.field(), the changing the class definition, then use this modified definition to
|
856
|
+
# determine whether dynamic finders work.
|
857
|
+
#
|
858
|
+
# The biggest issue is you'll never catch this in dev, since everything is a single instance
|
859
|
+
# in memory. It will manifest as mysterious "undefined method" production bugs. As such, we *must*
|
860
|
+
# restrict dynamic accessors to only modifying the instance for each row, or else they corrupt
|
861
|
+
# the class. This means find_by_whatever only works for fields defined via fields()
|
862
|
+
error = nil
|
863
|
+
begin
|
864
|
+
p = Playlist.find_by_artist("The Outfield")
|
865
|
+
rescue NoMethodError => error
|
866
|
+
end
|
867
|
+
assert_instance_of NoMethodError, error
|
868
|
+
|
869
|
+
# This should work though
|
870
|
+
p = Playlist.first(:conditions => {:artist => 'The Outfield'})
|
734
871
|
assert_equal(p.year, 1986)
|
735
872
|
end
|
736
873
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongo_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jim Menard
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2009-
|
13
|
+
date: 2009-12-29 00:00:00 -05:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|