tractor 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/tractor/model/base.rb +54 -32
- data/spec/model/base_spec.rb +89 -61
- data/spec/spec_helper.rb +0 -2
- data/tractor.gemspec +2 -2
- metadata +2 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/tractor/model/base.rb
CHANGED
@@ -19,9 +19,7 @@ module Tractor
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
class
|
23
|
-
include Enumerable
|
24
|
-
|
22
|
+
class Association
|
25
23
|
attr_accessor :key, :klass
|
26
24
|
|
27
25
|
def initialize(key, klass)
|
@@ -30,17 +28,23 @@ module Tractor
|
|
30
28
|
end
|
31
29
|
|
32
30
|
def push(val)
|
33
|
-
Tractor.redis.sadd
|
31
|
+
Tractor.redis.sadd(key, val.id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(id)
|
35
|
+
Tractor.redis.srem(key, id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def ids
|
39
|
+
Tractor.redis.smembers(key)
|
34
40
|
end
|
35
41
|
|
36
42
|
def all
|
37
|
-
ids
|
38
|
-
ids.inject([]){ |a, id| a << klass.find_by_id(id); a }
|
43
|
+
ids.inject([]){ |o, id| o << klass.find_by_id(id); o }
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
42
47
|
class Index
|
43
|
-
include Enumerable
|
44
48
|
attr_reader :klass, :name, :value
|
45
49
|
|
46
50
|
def initialize(klass, name, value)
|
@@ -73,7 +77,6 @@ module Tractor
|
|
73
77
|
def initialize(attributes={})
|
74
78
|
@attribute_store = {}
|
75
79
|
@association_store = {}
|
76
|
-
|
77
80
|
attributes.each do |k,v|
|
78
81
|
send("#{k}=", v)
|
79
82
|
end
|
@@ -85,12 +88,14 @@ module Tractor
|
|
85
88
|
Tractor.redis["#{self.class}:#{self.id}"] = Marshal.dump(self)
|
86
89
|
Tractor.redis.sadd "#{self.class}:all", self.id
|
87
90
|
add_to_indices
|
91
|
+
add_to_associations
|
88
92
|
|
89
93
|
return self
|
90
94
|
end
|
91
95
|
|
92
96
|
def destroy
|
93
97
|
delete_from_indices(attribute_store)
|
98
|
+
remove_from_associations
|
94
99
|
Tractor.redis.srem("#{self.class}:all", self.id)
|
95
100
|
Tractor.redis.del "#{self.class}:#{self.id}"
|
96
101
|
end
|
@@ -102,6 +107,23 @@ module Tractor
|
|
102
107
|
save
|
103
108
|
end
|
104
109
|
|
110
|
+
def remove_from_associations
|
111
|
+
self.class.associations.each do |key, value|
|
112
|
+
foreign_key_value = self.send(value[:foreign_key])
|
113
|
+
return unless foreign_key_value
|
114
|
+
value[:foreign_klass].find_by_id(foreign_key_value).send(key).delete(self.id)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_to_associations
|
119
|
+
self.class.associations.each do |key, value|
|
120
|
+
foreign_key_value = self.send(value[:foreign_key])
|
121
|
+
|
122
|
+
return unless foreign_key_value
|
123
|
+
value[:foreign_klass].find_by_id(foreign_key_value).send(key).push(self)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
105
127
|
def add_to_indices
|
106
128
|
self.class.indices.each do |name|
|
107
129
|
index = Index.new(self.class, name, send(name))
|
@@ -134,14 +156,6 @@ module Tractor
|
|
134
156
|
m.save
|
135
157
|
m
|
136
158
|
end
|
137
|
-
|
138
|
-
def ids
|
139
|
-
Tractor.redis.smembers "#{self}:all"
|
140
|
-
end
|
141
|
-
|
142
|
-
def count
|
143
|
-
ids.size
|
144
|
-
end
|
145
159
|
|
146
160
|
def find_by_id(id)
|
147
161
|
redis_obj = Tractor.redis["#{self}:#{id}"]
|
@@ -153,26 +167,24 @@ module Tractor
|
|
153
167
|
# use method missing to do craziness, or define a find_by on each index (BETTER)
|
154
168
|
def find_by_attribute(name, value)
|
155
169
|
raise "No index on '#{name}'" unless indices.include?(name)
|
156
|
-
|
157
|
-
ids = ids_for_find(name, value)
|
158
|
-
ids.map do |id|
|
159
|
-
find_by_id(id)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def ids_for_find(name, value)
|
164
|
-
Tractor.redis.smembers(Index.key_for(self, name, value))
|
170
|
+
find({name => value})
|
165
171
|
end
|
166
172
|
|
167
173
|
def find(options = {})
|
168
174
|
return [] if options.empty?
|
175
|
+
unions = []
|
169
176
|
sets = options.map do |name, value|
|
170
|
-
|
177
|
+
if value.is_a?(Array) && value.any?
|
178
|
+
unions << union_name = "#{value}-#{Time.now.to_f}"
|
179
|
+
Tractor.redis.sunionstore(union_name, *value.map{|v| Index.key_for(self, name, v) })
|
180
|
+
union_name
|
181
|
+
else
|
182
|
+
Index.key_for(self, name, value)
|
183
|
+
end
|
171
184
|
end
|
172
185
|
ids = Tractor.redis.sinter(*sets)
|
173
|
-
|
174
|
-
|
175
|
-
end
|
186
|
+
Tractor.redis.del(unions.join(","))
|
187
|
+
ids.map {|id| find_by_id(id) }
|
176
188
|
end
|
177
189
|
|
178
190
|
def attribute(name, options={})
|
@@ -180,22 +192,32 @@ module Tractor
|
|
180
192
|
attributes[name] = options
|
181
193
|
setter(name, options[:type])
|
182
194
|
getter(name, options[:type])
|
195
|
+
index(name) if options[:index]
|
183
196
|
end
|
184
197
|
|
185
198
|
def index(name)
|
186
199
|
indices << name unless indices.include?(name)
|
187
200
|
end
|
188
201
|
|
202
|
+
# make an assumption about the foreign_key... probably bad :)
|
189
203
|
def association(name, klass)
|
190
|
-
|
204
|
+
foreign_key = "#{self.to_s.gsub(/^.*::/, '').downcase}_id"
|
205
|
+
klass.associations[name] = {:foreign_key => foreign_key, :klass => klass, :foreign_klass => self}
|
191
206
|
|
192
207
|
define_method(name) do
|
193
|
-
@association_store[name] =
|
208
|
+
@association_store[name] = Association.new("#{self.class}:#{self.id}:#{name}", klass)
|
194
209
|
end
|
195
210
|
end
|
196
211
|
|
212
|
+
def ids
|
213
|
+
Tractor.redis.smembers "#{self}:all"
|
214
|
+
end
|
215
|
+
|
216
|
+
def count
|
217
|
+
ids.size
|
218
|
+
end
|
219
|
+
|
197
220
|
def all
|
198
|
-
ids = Tractor.redis.smembers("#{self}:all")
|
199
221
|
ids.inject([]){ |a, id| a << find_by_id(id); a }
|
200
222
|
end
|
201
223
|
|
data/spec/model/base_spec.rb
CHANGED
@@ -3,38 +3,47 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
3
3
|
describe Tractor::Model::Base do
|
4
4
|
attr_reader :redis
|
5
5
|
before do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
module Tractor
|
7
|
+
module Model
|
8
|
+
class Player < Base
|
9
|
+
attribute :id
|
10
|
+
attribute :name
|
11
|
+
attribute :wins_loses
|
12
|
+
attribute :game_id
|
13
|
+
end
|
14
|
+
|
15
|
+
class Game < Tractor::Model::Base
|
16
|
+
attribute :id
|
17
|
+
attribute :board
|
18
|
+
attribute :flying_object
|
19
|
+
attribute :score, :type => :integer, :index => true
|
17
20
|
|
18
|
-
|
21
|
+
association :players, Tractor::Model::Player
|
22
|
+
end
|
23
|
+
end
|
19
24
|
end
|
20
25
|
end
|
21
26
|
|
27
|
+
after do
|
28
|
+
Tractor.redis.flushdb
|
29
|
+
end
|
30
|
+
|
22
31
|
describe ".attribute" do
|
23
32
|
it "inserts the values into the attributes class instance variable" do
|
24
|
-
Game.attributes.should include(:board)
|
33
|
+
Tractor::Model::Game.attributes.should include(:board)
|
25
34
|
end
|
26
35
|
|
27
36
|
it "allows you to specify what type the value should be when it comes out of the tractor" do
|
28
|
-
Game.attributes[:score][:type].should == :integer
|
37
|
+
Tractor::Model::Game.attributes[:score][:type].should == :integer
|
29
38
|
end
|
30
39
|
|
31
40
|
it "creates a set method for each attribute" do
|
32
|
-
game = Game.new(:board => "fancy")
|
41
|
+
game = Tractor::Model::Game.new(:board => "fancy")
|
33
42
|
game.send(:attribute_store)[:board].should == "fancy"
|
34
43
|
end
|
35
44
|
|
36
45
|
it "creates a get method for each attribute" do
|
37
|
-
game = Game.new(:board => "schmancy")
|
46
|
+
game = Tractor::Model::Game.new(:board => "schmancy")
|
38
47
|
game.board.should == "schmancy"
|
39
48
|
end
|
40
49
|
|
@@ -48,39 +57,64 @@ describe Tractor::Model::Base do
|
|
48
57
|
|
49
58
|
describe "when attribute is a integer" do
|
50
59
|
it "returns an integer" do
|
51
|
-
game = Game.new(:score => 1222)
|
60
|
+
game = Tractor::Model::Game.new(:score => 1222)
|
52
61
|
game.score.should == 1222
|
53
62
|
game.score.should be_a(Fixnum)
|
54
63
|
end
|
55
64
|
end
|
65
|
+
|
66
|
+
describe "when attribute is an index" do
|
67
|
+
before do
|
68
|
+
class Zombo < Tractor::Model::Base
|
69
|
+
attribute :anything, :index => true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns creates an index for the attribute" do
|
74
|
+
Zombo.indices.should == [:anything]
|
75
|
+
end
|
76
|
+
end
|
56
77
|
end
|
57
78
|
|
58
79
|
describe "#association" do
|
59
80
|
attr_reader :game, :player1, :player2
|
60
81
|
|
61
82
|
before do
|
62
|
-
@game = Game.new({ :id => 'g1' })
|
63
|
-
@player1 = Player.new({ :id => 'p1', :name => "delicious" })
|
64
|
-
@player2 = Player.new({ :id => 'p2', :name => "gross" })
|
83
|
+
@game = Tractor::Model::Game.new({ :id => 'g1' })
|
84
|
+
@player1 = Tractor::Model::Player.new({ :id => 'p1', :name => "delicious" })
|
85
|
+
@player2 = Tractor::Model::Player.new({ :id => 'p2', :name => "gross" })
|
65
86
|
|
66
87
|
game.save
|
67
88
|
player1.save
|
68
89
|
player2.save
|
69
90
|
end
|
70
91
|
|
71
|
-
it "adds a
|
72
|
-
|
92
|
+
it "adds a method with the given name to the instance" do # "Monkey:a1a:SET_NAME"
|
93
|
+
game.players.should be_a(Tractor::Association)
|
73
94
|
end
|
74
95
|
|
75
96
|
it "adds a push method for the set on an instance of the class" do
|
76
97
|
game.players.push player1
|
77
|
-
redis.smembers('Game:g1:players').should == ['p1']
|
98
|
+
redis.smembers('Tractor::Model::Game:g1:players').should == ['p1']
|
99
|
+
end
|
100
|
+
|
101
|
+
it "adds an ids method for the set that returns all ids in it" do
|
102
|
+
game.players.ids.should be_empty
|
103
|
+
game.players.push player1
|
104
|
+
game.players.ids.should == [player1.id]
|
105
|
+
end
|
106
|
+
|
107
|
+
it "automatically adds items to association when they are created" do
|
108
|
+
bocci_ball = Tractor::Model::Game.create({ :id => "bocci_ball" })
|
109
|
+
Tractor::Model::Player.create({ :id => "tobias", :name => "deciduous", :game_id => "bocci_ball" })
|
110
|
+
bocci_ball.players.ids.should == ["tobias"]
|
78
111
|
end
|
79
112
|
|
80
113
|
it "adds an all method for the association to return the items in it" do
|
81
114
|
game.players.all.should == []
|
82
115
|
game.players.push player1
|
83
116
|
game.players.push player2
|
117
|
+
|
84
118
|
player1_from_game = game.players.all[0]
|
85
119
|
player2_from_game = game.players.all[1]
|
86
120
|
|
@@ -90,13 +124,7 @@ describe Tractor::Model::Base do
|
|
90
124
|
player2_from_game.id.should == player2.id
|
91
125
|
end
|
92
126
|
|
93
|
-
it "requires the object being added to have been saved to the database before adding it to the
|
94
|
-
end
|
95
|
-
|
96
|
-
describe ".associations" do
|
97
|
-
it "returns all association that have been added to this class" do
|
98
|
-
Game.associations.keys.should == [:players]
|
99
|
-
end
|
127
|
+
it "requires the object being added to have been saved to the database before adding it to the association"
|
100
128
|
end
|
101
129
|
|
102
130
|
describe ".indices" do
|
@@ -113,7 +141,7 @@ describe Tractor::Model::Base do
|
|
113
141
|
attr_reader :sorted_attributes
|
114
142
|
|
115
143
|
before do
|
116
|
-
@sorted_attributes = Game.attributes.keys.sort{|x,y| x.to_s <=> y.to_s}
|
144
|
+
@sorted_attributes = Tractor::Model::Game.attributes.keys.sort{|x,y| x.to_s <=> y.to_s}
|
117
145
|
end
|
118
146
|
|
119
147
|
it "returns all attributes that have been added to this class" do
|
@@ -122,17 +150,17 @@ describe Tractor::Model::Base do
|
|
122
150
|
end
|
123
151
|
|
124
152
|
it "allows different attributes to be specified for different child classes" do
|
125
|
-
Game.attributes.size.should == 4
|
126
|
-
Player.attributes.size.should ==
|
153
|
+
Tractor::Model::Game.attributes.size.should == 4
|
154
|
+
Tractor::Model::Player.attributes.size.should == 4
|
127
155
|
|
128
|
-
Game.attributes.keys.should_not include(:name)
|
129
|
-
Player.attributes.keys.should_not include(:flying_object)
|
156
|
+
Tractor::Model::Game.attributes.keys.should_not include(:name)
|
157
|
+
Tractor::Model::Player.attributes.keys.should_not include(:flying_object)
|
130
158
|
end
|
131
159
|
end
|
132
160
|
|
133
161
|
describe "#save" do
|
134
162
|
it "raises if id is nil or empty" do
|
135
|
-
game = Game.new
|
163
|
+
game = Tractor::Model::Game.new
|
136
164
|
game.id = nil
|
137
165
|
lambda { game.save }.should raise_error("Probably wanna set an id")
|
138
166
|
game.id = ''
|
@@ -140,22 +168,22 @@ describe Tractor::Model::Base do
|
|
140
168
|
end
|
141
169
|
|
142
170
|
it "should write attributes to redis" do
|
143
|
-
game = Game.new({:id => '1', :board => "large", :flying_object => "disc"})
|
171
|
+
game = Tractor::Model::Game.new({:id => '1', :board => "large", :flying_object => "disc"})
|
144
172
|
game.save
|
145
173
|
|
146
|
-
redis["Game:1"].should_not be_nil
|
147
|
-
redis_game = Marshal.load(redis["Game:1"])
|
174
|
+
redis["Tractor::Model::Game:1"].should_not be_nil
|
175
|
+
redis_game = Marshal.load(redis["Tractor::Model::Game:1"])
|
148
176
|
redis_game.id.should == "1"
|
149
177
|
redis_game.board.should == "large"
|
150
178
|
redis_game.flying_object.should == "disc"
|
151
179
|
end
|
152
180
|
|
153
181
|
it "appends the new object to the Game set" do
|
154
|
-
Game.all.size.should == 0
|
155
|
-
game = Game.new({ :id => '1', :board => "small" })
|
182
|
+
Tractor::Model::Game.all.size.should == 0
|
183
|
+
game = Tractor::Model::Game.new({ :id => '1', :board => "small" })
|
156
184
|
game.save
|
157
185
|
|
158
|
-
Game.all.size.should == 1
|
186
|
+
Tractor::Model::Game.all.size.should == 1
|
159
187
|
end
|
160
188
|
end
|
161
189
|
|
@@ -188,12 +216,12 @@ describe Tractor::Model::Base do
|
|
188
216
|
before do
|
189
217
|
Sammich.create({ :id => 's1', :weight => "medium", :product => "Turkey Avocado" })
|
190
218
|
Sammich.create({ :id => 's2', :weight => "medium", :product => "Reuben Sammich" })
|
191
|
-
Player.create({ :id => 'p1', :name => "delicious" })
|
219
|
+
Tractor::Model::Player.create({ :id => 'p1', :name => "delicious" })
|
192
220
|
end
|
193
221
|
|
194
222
|
it "returns all the ids for a given class" do
|
195
223
|
Sammich.ids.should == ['s1', 's2']
|
196
|
-
Player.ids.should == ['p1']
|
224
|
+
Tractor::Model::Player.ids.should == ['p1']
|
197
225
|
end
|
198
226
|
end
|
199
227
|
|
@@ -201,24 +229,12 @@ describe Tractor::Model::Base do
|
|
201
229
|
before do
|
202
230
|
Sammich.create({ :id => 's1', :weight => "medium", :product => "Turkey Avocado" })
|
203
231
|
Sammich.create({ :id => 's2', :weight => "medium", :product => "Reuben Sammich" })
|
204
|
-
Player.create({ :id => 'p1', :name => "delicious" })
|
232
|
+
Tractor::Model::Player.create({ :id => 'p1', :name => "delicious" })
|
205
233
|
end
|
206
234
|
|
207
235
|
it "returns the count of all items of a given class" do
|
208
236
|
Sammich.count.should == 2
|
209
|
-
Player.count.should == 1
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
describe ".ids_for_find" do
|
214
|
-
before do
|
215
|
-
Sammich.create({ :id => 's1', :weight => "medium", :product => "Turkey Avocado" })
|
216
|
-
Sammich.create({ :id => 's2', :weight => "medium", :product => "Reuben Sammich" })
|
217
|
-
Player.create({ :id => 'p1', :name => "delicious" })
|
218
|
-
end
|
219
|
-
|
220
|
-
it "returns all the ids for a given attribute and value on a class" do
|
221
|
-
Sammich.ids_for_find(:weight, "medium").should == ['s1', 's2']
|
237
|
+
Tractor::Model::Player.count.should == 1
|
222
238
|
end
|
223
239
|
end
|
224
240
|
|
@@ -328,15 +344,23 @@ describe Tractor::Model::Base do
|
|
328
344
|
Sammich.find({ :weight => "medium" }).size.should == 1
|
329
345
|
end
|
330
346
|
|
331
|
-
it "removes the id from all of the associations that it may be in"
|
347
|
+
it "removes the id from all of the associations that it may be in" do
|
348
|
+
bocci_ball = Tractor::Model::Game.create({ :id => "bocci_ball" })
|
349
|
+
tobias = Tractor::Model::Player.create({ :id => "tobias", :name => "deciduous", :game_id => "bocci_ball" })
|
350
|
+
bocci_ball.players.ids.should == ["tobias"]
|
351
|
+
tobias.destroy
|
352
|
+
|
353
|
+
bocci_ball.players.ids.should == []
|
354
|
+
end
|
332
355
|
end
|
333
356
|
|
334
357
|
describe ".find" do
|
335
358
|
attr_reader :cheese, :balogna
|
336
359
|
|
337
360
|
before do
|
338
|
-
@cheese
|
339
|
-
@balogna
|
361
|
+
@cheese = Sammich.create({ :id => '1', :weight => "medium", :product => "Cheese Sammich" })
|
362
|
+
@balogna = Sammich.create({ :id => '2', :weight => "medium", :product => "Balogna Sammich" })
|
363
|
+
@meat = Sammich.create({ :id => '3', :weight => "heavy", :product => "Meat & More Sammich" })
|
340
364
|
end
|
341
365
|
|
342
366
|
context "when searching on 1 attribute" do
|
@@ -366,6 +390,10 @@ describe Tractor::Model::Base do
|
|
366
390
|
it "returns empty array if nothing matches the given options" do
|
367
391
|
Sammich.find( {:weight => "light" } ).should == []
|
368
392
|
end
|
393
|
+
|
394
|
+
it "returns all matching objects if multiple values are given for an attribute" do
|
395
|
+
Sammich.find( {:weight => ["medium", "heavy"]} ).map(&:id).sort.should == ['1','2','3']
|
396
|
+
end
|
369
397
|
end
|
370
398
|
|
371
399
|
describe ".find_by_attribute" do
|
data/spec/spec_helper.rb
CHANGED
data/tractor.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{tractor}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Shane Wolf"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-05-25}
|
13
13
|
s.description = %q{Very simple object mappings for ruby objects}
|
14
14
|
s.email = %q{shanewolf@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tractor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Wolf
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-05-25 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|