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 CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.4.0
@@ -19,9 +19,7 @@ module Tractor
19
19
  end
20
20
  end
21
21
 
22
- class Set
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 key, val.id
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 = Tractor.redis.smembers(key)
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
- Index.key_for(self, name, value)
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
- ids.map do |id|
174
- find_by_id(id)
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
- associations[name] = name
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] = Set.new("#{self.class}:#{self.id}:#{name}", klass)
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
 
@@ -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
- class Player < Tractor::Model::Base
7
- attribute :id
8
- attribute :name
9
- attribute :wins_loses
10
- end
11
-
12
- class Game < Tractor::Model::Base
13
- attribute :id
14
- attribute :board
15
- attribute :flying_object
16
- attribute :score, :type => :integer
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
- association :players, Player
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 set with the given name to the instance" do # "Monkey:a1a:SET_NAME"
72
- Game.associations.keys.should include(:players)
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 set"
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 == 3
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 = Sammich.create({ :id => '1', :weight => "medium", :product => "Cheese Sammich" })
339
- @balogna = Sammich.create({ :id => '2', :weight => "medium", :product => "Balogna Sammich" })
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
@@ -39,8 +39,6 @@ class MonkeyClient < Tractor::Model::Mapper
39
39
  attribute :birthday, :map => :birthdate
40
40
  attribute :evil, :type => :boolean, :map => :evil_monkey #[:evil_monkey, :boolean]
41
41
  index :evil
42
-
43
- association :bananas, BananaClient
44
42
  end
45
43
 
46
44
  class Monkey
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.3.1"
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-02-24}
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.3.1
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-02-24 00:00:00 -08:00
12
+ date: 2010-05-25 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15