tractor 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|
|