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 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