arrest 0.0.16 → 0.0.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,11 @@ require "arrest/attributes/has_attributes"
8
8
  require "arrest/attributes/attribute"
9
9
  require "arrest/attributes/nested_attribute"
10
10
  require "arrest/attributes/polymorphic_attribute"
11
+ require "arrest/attributes/belongs_to_attribute"
12
+ require "arrest/attributes/has_many_attribute"
11
13
  require "arrest/attributes/converter"
14
+ require "arrest/class_utils.rb"
15
+ require "arrest/string_utils.rb"
12
16
  require "arrest/handler"
13
17
  require "arrest/source"
14
18
  require "arrest/helper/filter"
@@ -50,23 +50,31 @@ module Arrest
50
50
 
51
51
  def has_many(*args)
52
52
  method_name, options = args
53
- singular = (StringUtils.singular(method_name.to_s) + '_ids').to_sym
53
+ ids_field_name = (StringUtils.singular(method_name.to_s) + '_ids').to_sym
54
54
  method_name = method_name.to_sym
55
-
56
- clazz_name = method_name.to_s
55
+ clazz_name = StringUtils.singular(method_name.to_s)
56
+ foreign_key = clazz_name + "_id"
57
57
  if options
58
- clazz = options[:class_name]
59
- if clazz
60
- clazz_name = clazz.to_s
61
- end
58
+ clazz_name = options[:class_name].to_s unless options[:class_name] == nil
59
+ foreign_key = "#{StringUtils.underscore(clazz_name)}_id"
60
+ foreign_key = options[:foreign_key].to_s unless options[:foreign_key] == nil
62
61
  end
63
- attribute singular, Array
62
+
63
+ url_part = method_name.to_s
64
+
65
+ hm_attr = HasManyAttribute.new(ids_field_name,
66
+ method_name,
67
+ clazz_name,
68
+ url_part,
69
+ foreign_key)
70
+ add_attribute(hm_attr)
71
+
64
72
  send :define_method, method_name do
65
73
  if @has_many_collections == nil
66
74
  @has_many_collections = {}
67
75
  end
68
76
  if @has_many_collections[method_name] == nil
69
- @has_many_collections[method_name] = HasManyCollection.new(self, (StringUtils.classify (StringUtils.singular clazz_name)))
77
+ @has_many_collections[method_name] = HasManyCollection.new(self, hm_attr)
70
78
  end
71
79
 
72
80
  @has_many_collections[method_name]
@@ -144,7 +152,7 @@ module Arrest
144
152
  true
145
153
  end
146
154
  #
147
- # convenicence method printing curl command
155
+ # convenience method printing curl command
148
156
  def curl
149
157
  hs = ""
150
158
  Arrest::Source.header_decorator.headers.each_pair do |k,v|
@@ -17,28 +17,31 @@ module Arrest
17
17
  end
18
18
  end
19
19
 
20
- def create_and_add_attribute(field_name, polymorphic, read_only)
20
+ def create_and_add_attribute(field_name, polymorphic, read_only, foreign_key, class_name)
21
21
  if polymorphic
22
22
  add_attribute(PolymorphicAttribute.new(field_name.to_sym, read_only))
23
23
  else
24
- add_attribute(Attribute.new(field_name.to_sym, read_only, String))
24
+ add_attribute(BelongsToAttribute.new(field_name.to_sym, read_only, String, foreign_key, class_name))
25
25
  end
26
26
  end
27
27
 
28
28
  def belongs_to(*args)
29
29
  arg = args[0]
30
30
  name = arg.to_s.downcase
31
- class_name = StringUtils.classify name
31
+ class_name = StringUtils.classify(name)
32
+ foreign_key = "#{StringUtils.underscore(ClassUtils.simple_name(self))}_id"
32
33
  params = args[1] unless args.length < 2
34
+
33
35
  if params
34
36
  read_only = params[:read_only] == true
35
37
  polymorphic = params[:polymorphic] unless params[:polymorphic] == nil
36
38
  class_name = params[:class_name].to_s unless params[:class_name] == nil
39
+ foreign_key = params[:foreign_key].to_s unless params[:foreign_key] == nil
37
40
  end
38
41
 
39
42
  field_name = create_field_name(name, params, polymorphic)
40
43
 
41
- create_and_add_attribute(field_name, polymorphic, read_only)
44
+ create_and_add_attribute(field_name, polymorphic, read_only, foreign_key, class_name)
42
45
 
43
46
  send :define_method, name do
44
47
  val = self.send(field_name)
@@ -0,0 +1,13 @@
1
+ module Arrest
2
+ class BelongsToAttribute < Attribute
3
+ attr_accessor :foreign_key
4
+ def initialize(name, read_only, field_class, foreign_key, target_class_name)
5
+ super(name, read_only, field_class)
6
+ @foreign_key = foreign_key
7
+ @target_class_name = target_class_name
8
+ end
9
+ def target_class
10
+ Arrest::Source.mod.const_get(@target_class_name)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Arrest
2
+ class HasManyAttribute < Attribute
3
+ attr_reader :method_name, :clazz_name, :url_part, :foreign_key
4
+ def initialize(ids_field_name,
5
+ method_name,
6
+ clazz_name,
7
+ url_part,
8
+ foreign_key)
9
+ super(ids_field_name, false, Array)
10
+ @method_name = method_name.to_sym
11
+ @clazz_name = clazz_name.to_sym
12
+ @url_part = url_part.to_sym
13
+ @foreign_key = foreign_key.to_sym
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Arrest
2
+ class ClassUtils
3
+ class << self
4
+ # Returns the simple class name without any preceding modules or namespaces
5
+ # (removes everything up to the last '::' inclusively from class.name)
6
+ def simple_name(clazz)
7
+ clazz.name.gsub(/.*::/,"")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,8 +1,9 @@
1
1
  module Arrest
2
2
  class HasManyCollection #< BasicObject
3
- def initialize parent, clazz_name
3
+ def initialize parent, has_many_attribute
4
4
  @parent = parent
5
- @clazz_name = clazz_name
5
+ @clazz_name = (StringUtils.classify(has_many_attribute.clazz_name.to_s))
6
+ @url_part = has_many_attribute.url_part
6
7
  @children = nil
7
8
  @foreign_key_name = (StringUtils.underscore(@parent.class.name).gsub(/^.*\//, '') + '_id').to_sym
8
9
  define_filters
@@ -23,10 +24,9 @@ module Arrest
23
24
 
24
25
  private
25
26
 
26
-
27
27
  def children
28
28
  if @children == nil
29
- url = @parent.resource_location + '/' + resolved_class.resource_name.to_s
29
+ url = @parent.resource_location + '/' + @url_part.to_s
30
30
  @children = resolved_class.by_url(url)
31
31
  end
32
32
  @children
@@ -1,4 +1,7 @@
1
1
  module Arrest
2
+
3
+ Edge = Struct.new(:foreign_key, :name, :id, :tail)
4
+
2
5
  class MemSource
3
6
 
4
7
  attr_accessor :data
@@ -8,7 +11,9 @@ module Arrest
8
11
  # each having a unique id
9
12
 
10
13
  @@collections = {} # maps urls to collections of ids of objects
11
-
14
+
15
+ # For every has_many relation
16
+ @@edge_matrix = {} # matrix of edges based on node ids for has_many and belongs_to relations
12
17
 
13
18
  @@data = {}
14
19
 
@@ -24,12 +29,25 @@ module Arrest
24
29
  @@data
25
30
  end
26
31
 
32
+ def edge_matrix
33
+ @@edge_matrix
34
+ end
35
+
36
+ def edge_count
37
+ @@edge_matrix.values.inject(0){|sum, edges| sum + edges.length }
38
+ end
39
+
40
+ def node_count
41
+ @@edge_matrix.length
42
+ end
43
+
27
44
  def initialize
28
45
  @@all_objects = {} # holds all objects of all types,
29
46
 
30
47
  @@collections = {} # maps urls to collections of ids of objects
31
48
  @@random = Random.new(42)
32
49
 
50
+ @@edge_matrix = {}
33
51
  end
34
52
 
35
53
 
@@ -61,17 +79,38 @@ module Arrest
61
79
  end
62
80
  end
63
81
 
64
- def get_many sub, filters = {}
82
+ def parse_for_has_many_relations(resource_path)
83
+ matcher = /^.+\/([^\/]+)\/([^\/]+)$/.match(resource_path)
84
+ return [] unless matcher
85
+ object_id = matcher[1]
86
+ relation = matcher[2]
87
+
88
+ if (object_id && relation && @@edge_matrix[object_id])
89
+ result = []
90
+ @@edge_matrix[object_id].each do |edge|
91
+ if (edge.name.to_s == relation)
92
+ result << edge.id
93
+ end
94
+ end
95
+ return result
96
+ end
97
+ []
98
+ end
99
+
100
+ def get_many(sub, filters = {})
65
101
  Arrest::debug sub + (hash_to_query filters)
66
102
  # filters are ignored by mem impl so far
103
+
104
+ id_list = parse_for_has_many_relations(sub)
105
+ if id_list.empty?
106
+ id_list = @@collections[sub] || []
107
+ end
67
108
 
68
- id_list = @@collections[sub] || []
69
109
  objects = id_list.map do |id|
70
110
  @@all_objects[id]
71
111
  end
72
112
 
73
113
  wrap collection_json(objects), id_list.length
74
-
75
114
  end
76
115
 
77
116
  def get_one sub, filters = {}
@@ -95,6 +134,7 @@ module Arrest
95
134
  v.reject!{ |id| id == base_id }
96
135
  end
97
136
  @@all_objects[base_id].delete
137
+ remove_edges(@@edge_matrix, base_id)
98
138
  end
99
139
  end
100
140
 
@@ -118,26 +158,104 @@ module Arrest
118
158
  end
119
159
 
120
160
 
121
- def delete rest_resource
161
+ def delete(rest_resource)
122
162
  raise "To change an object it must have an id" unless rest_resource.respond_to?(:id) && rest_resource.id != nil
123
163
  @@all_objects.delete(rest_resource.id)
124
164
  @@collections.each_pair do |k,v|
125
165
  v.reject!{ |id| id == rest_resource.id }
126
166
  end
167
+ remove_edges(@@edge_matrix, rest_resource.id)
127
168
  rest_resource
128
169
  end
129
170
 
130
- def put rest_resource
171
+ def remove_outgoing_edges(edge_matrix, id)
172
+ if (edge_matrix[id])
173
+ out_edges = edge_matrix[id].find_all{|edge| edge.tail}
174
+ in_edges_to_delete = out_edges.map do |out_edge|
175
+ foreign_edges = edge_matrix[out_edge.id] # the edge set of the foreign node that this node points to
176
+ has_many_back_edges = foreign_edges.find_all do |for_edge|
177
+ for_edge.id == id && for_edge.foreign_key == out_edge.foreign_key
178
+ end
179
+ [has_many_back_edges.first, out_edge.id] # first element may be nil
180
+ end
181
+
182
+ in_edges_to_delete.each do |tupel|
183
+ if tupel[0]
184
+ edge_matrix[tupel[1]].delete_if{|e| e.id == tupel[0].id && e.foreign_key == tupel[0].foreign_key}
185
+ end
186
+ end
187
+ edge_matrix[id] = Set.new()
188
+ end
189
+ end
190
+
191
+ def remove_edges(edge_matrix, node_id)
192
+ if (edge_matrix[node_id])
193
+ edge_matrix[node_id].each do |edge|
194
+ to_nodes = edge_matrix[edge.id]
195
+ to_nodes.delete_if{|e| e.id == node_id}
196
+ end
197
+ edge_matrix.delete(node_id)
198
+ end
199
+ end
200
+
201
+ def identify_and_store_edges(edge_matrix, rest_resource)
202
+ from_id = rest_resource.id
203
+
204
+ rest_resource.class.all_fields.each do |attr|
205
+ if attr.is_a?(Arrest::HasManyAttribute)
206
+ to_ids = rest_resource.send(attr.name) # -> foo_ids
207
+ url_part = attr.url_part
208
+ foreign_key = attr.foreign_key
209
+ edge_matrix[from_id] ||= Set.new()
210
+ if to_ids
211
+ to_ids.each do |to_id|
212
+ edge_matrix[from_id].add(Edge.new(foreign_key, url_part, to_id, true))
213
+ edge_matrix[to_id] ||= Set.new()
214
+ edge_matrix[to_id].add(Edge.new(foreign_key, url_part, from_id, false))
215
+ end
216
+ end
217
+ elsif attr.is_a?(Arrest::BelongsToAttribute)
218
+ to_id = rest_resource.send(attr.name)
219
+ if to_id
220
+ foreign_key = attr.foreign_key
221
+ has_many_clazz = attr.target_class()
222
+ puts "#{foreign_key}"
223
+ hm_candidates = has_many_clazz.all_fields.find_all do |field|
224
+ puts "->#{field.foreign_key.to_s}" if field.is_a?(Arrest::HasManyAttribute)
225
+ field.is_a?(Arrest::HasManyAttribute) && field.foreign_key.to_s == foreign_key
226
+ end
227
+ return if hm_candidates.empty?
228
+ has_many_node = hm_candidates.first
229
+ url_part = has_many_node.url_part
230
+
231
+ edge_matrix[from_id] ||= Set.new()
232
+ edge_matrix[from_id].add(Edge.new(foreign_key, url_part, to_id, true))
233
+ edge_matrix[to_id] ||= Set.new()
234
+ edge_matrix[to_id].add(Edge.new(foreign_key, url_part, from_id, false))
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+
241
+ def put(rest_resource)
131
242
  raise "To change an object it must have an id" unless rest_resource.respond_to?(:id) && rest_resource.id != nil
132
243
  old = @@all_objects[rest_resource.id]
133
-
244
+
245
+ remove_outgoing_edges(@@edge_matrix, old.id)
246
+
134
247
  rest_resource.class.all_fields.each do |f|
135
248
  old.send("#{f.name}=", rest_resource.send(f.name))
136
249
  end
250
+
251
+ identify_and_store_edges(@@edge_matrix, rest_resource)
252
+
137
253
  true
138
254
  end
139
255
 
140
- def post rest_resource
256
+
257
+ def post(rest_resource)
258
+
141
259
  Arrest::debug "post -> #{rest_resource.class.name} #{rest_resource.to_hash} #{rest_resource.class.all_fields.map(&:name)}"
142
260
  raise "new object must have setter for id" unless rest_resource.respond_to?(:id=)
143
261
  raise "new object must not have id" if rest_resource.respond_to?(:id) && rest_resource.id != nil
@@ -152,10 +270,13 @@ module Arrest
152
270
  @@collections[rest_resource.resource_path] = []
153
271
  end
154
272
  @@collections[rest_resource.resource_path] << rest_resource.id
273
+
274
+ identify_and_store_edges(@@edge_matrix, rest_resource)
275
+
155
276
  true
156
277
  end
157
278
 
158
- def cheat_collection url, ids
279
+ def cheat_collection(url, ids)
159
280
  @@collections[url] = ids
160
281
  end
161
282
 
@@ -9,7 +9,7 @@ module Arrest
9
9
 
10
10
  def by_url url
11
11
  begin
12
- body = body_root(source().get_many url)
12
+ body = body_root(source().get_many(url))
13
13
  rescue Arrest::Errors::DocumentNotFoundError
14
14
  Arrest::logger.info "DocumentNotFoundError for #{url} gracefully returning []"
15
15
  return []
@@ -1,64 +1,66 @@
1
- class StringUtils
2
- class << self
3
-
4
- PLURALS = [['(quiz)$', '\1zes'],['(ox)$', '\1en'],['([m|l])ouse$', '\1ice'],['(matr|vert|ind)ix|ex$', '\1ices'],
5
- ['(x|ch|ss|sh)$', '\1es'],['([^aeiouy]|qu)ies$', '\1y'],['([^aeiouy]|q)y$$', '\1ies'],['(hive)$', '\1s'],
6
- ['(?:[^f]fe|([lr])f)$', '\1\2ves'],['(sis)$', 'ses'],['([ti])um$', '\1a'],['(buffal|tomat)o$', '\1oes'],['(bu)s$', '\1es'],
7
- ['(alias|status)$', '\1es'],['(octop|vir)us$', '\1i'],['(ax|test)is$', '\1es'],['s$', 's'],['$', 's']]
8
- SINGULARS =[['(quiz)zes$', '\1'],['(matr)ices$', '\1ix'],['(vert|ind)ices$', '\1ex'],['^(ox)en$', '\1'],['(alias|status)es$', '\1'],
9
- ['(octop|vir)i$', '\1us'],['(cris|ax|test)es$', '\1is'],['(shoe)s$', '\1'],['[o]es$', '\1'],['[bus]es$', '\1'],['([m|l])ice$', '\1ouse'],
10
- ['(x|ch|ss|sh)es$', '\1'],['(m)ovies$', '\1ovie'],['[s]eries$', '\1eries'],['([^aeiouy]|qu)ies$', '\1y'],['[lr]ves$', '\1f'],
11
- ['(tive)s$', '\1'],['(hive)s$', '\1'],['([^f])ves$', '\1fe'],['(^analy)ses$', '\1sis'],
12
- ['([a]naly|[b]a|[d]iagno|[p]arenthe|[p]rogno|[s]ynop|[t]he)ses$', '\1\2sis'],['([ti])a$', '\1um'],['(news)$', '\1ews'], ['(.*)s$', '\1'], ['^(.*)$', '\1']]
13
-
14
- def singular(str)
15
- SINGULARS.each { |match_exp, replacement_exp| return str.gsub(Regexp.compile(match_exp), replacement_exp) unless str.match(Regexp.compile(match_exp)).nil?}
16
- end
17
-
18
- def plural(str)
19
- PLURALS.each { |match_exp, replacement_exp| return str.gsub(Regexp.compile(match_exp), replacement_exp) unless str.match(Regexp.compile(match_exp)).nil? }
20
- end
21
-
22
- def plural?
23
- PLURALS.each {|match_exp, replacement_exp| return true if str.match(Regexp.compile(match_exp))}
24
- false
25
- end
26
-
27
- def blank? str
28
- str == nil || str == ""
29
- end
30
-
31
- def is_upper? str
32
- str == str.upcase
33
- end
34
-
35
- def underscore str
36
- word = str.to_s.dup
37
- word.gsub!(/::/, '/')
38
- word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
39
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
40
- word.tr!("-", "_")
41
- word.downcase!
42
- word
43
- end
44
-
45
- def classify(str, upper_first = true)
46
- result = ""
47
- upperNext = false
48
- #(singular str) .each_char do |c|
49
- (str) .each_char do |c|
50
- if c == "_"
51
- upperNext = true
52
- else
53
- if upperNext || (result == "" && upper_first)
54
- result << c.upcase
1
+ module Arrest
2
+ class StringUtils
3
+ class << self
4
+
5
+ PLURALS = [['(quiz)$', '\1zes'],['(ox)$', '\1en'],['([m|l])ouse$', '\1ice'],['(matr|vert|ind)ix|ex$', '\1ices'],
6
+ ['(x|ch|ss|sh)$', '\1es'],['([^aeiouy]|qu)ies$', '\1y'],['([^aeiouy]|q)y$$', '\1ies'],['(hive)$', '\1s'],
7
+ ['(?:[^f]fe|([lr])f)$', '\1\2ves'],['(sis)$', 'ses'],['([ti])um$', '\1a'],['(buffal|tomat)o$', '\1oes'],['(bu)s$', '\1es'],
8
+ ['(alias|status)$', '\1es'],['(octop|vir)us$', '\1i'],['(ax|test)is$', '\1es'],['s$', 's'],['$', 's']]
9
+ SINGULARS =[['(quiz)zes$', '\1'],['(matr)ices$', '\1ix'],['(vert|ind)ices$', '\1ex'],['^(ox)en$', '\1'],['(alias|status)es$', '\1'],
10
+ ['(octop|vir)i$', '\1us'],['(cris|ax|test)es$', '\1is'],['(shoe)s$', '\1'],['[o]es$', '\1'],['[bus]es$', '\1'],['([m|l])ice$', '\1ouse'],
11
+ ['(x|ch|ss|sh)es$', '\1'],['(m)ovies$', '\1ovie'],['[s]eries$', '\1eries'],['([^aeiouy]|qu)ies$', '\1y'],['[lr]ves$', '\1f'],
12
+ ['(tive)s$', '\1'],['(hive)s$', '\1'],['([^f])ves$', '\1fe'],['(^analy)ses$', '\1sis'],
13
+ ['([a]naly|[b]a|[d]iagno|[p]arenthe|[p]rogno|[s]ynop|[t]he)ses$', '\1\2sis'],['([ti])a$', '\1um'],['(news)$', '\1ews'], ['(.*)s$', '\1'], ['^(.*)$', '\1']]
14
+
15
+ def singular(str)
16
+ SINGULARS.each { |match_exp, replacement_exp| return str.gsub(Regexp.compile(match_exp), replacement_exp) unless str.match(Regexp.compile(match_exp)).nil?}
17
+ end
18
+
19
+ def plural(str)
20
+ PLURALS.each { |match_exp, replacement_exp| return str.gsub(Regexp.compile(match_exp), replacement_exp) unless str.match(Regexp.compile(match_exp)).nil? }
21
+ end
22
+
23
+ def plural?
24
+ PLURALS.each {|match_exp, replacement_exp| return true if str.match(Regexp.compile(match_exp))}
25
+ false
26
+ end
27
+
28
+ def blank? str
29
+ str == nil || str == ""
30
+ end
31
+
32
+ def is_upper? str
33
+ str == str.upcase
34
+ end
35
+
36
+ def underscore str
37
+ word = str.to_s.dup
38
+ word.gsub!(/::/, '/')
39
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
40
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
41
+ word.tr!("-", "_")
42
+ word.downcase!
43
+ word
44
+ end
45
+
46
+ def classify(str, upper_first = true)
47
+ result = ""
48
+ upperNext = false
49
+ #(singular str) .each_char do |c|
50
+ (str) .each_char do |c|
51
+ if c == "_"
52
+ upperNext = true
55
53
  else
56
- result << c
54
+ if upperNext || (result == "" && upper_first)
55
+ result << c.upcase
56
+ else
57
+ result << c
58
+ end
59
+ upperNext = false
57
60
  end
58
- upperNext = false
59
61
  end
62
+ result
60
63
  end
61
- result
62
64
  end
63
65
  end
64
- end
66
+ end
@@ -1,3 +1,3 @@
1
1
  module Arrest
2
- VERSION = "0.0.16"
2
+ VERSION = "0.0.17"
3
3
  end
@@ -120,3 +120,13 @@ end
120
120
 
121
121
  class DeleteMeAll < Arrest::RootResource
122
122
  end
123
+
124
+ class Foo < Arrest::RootResource
125
+ has_many :bars#, :class_name => :Bar, :foreign_key => defaults to bar_id
126
+ has_many :other_bars, :class_name => :Bar, :foreign_key => :common_key
127
+ end
128
+ class Bar < Arrest::RootResource
129
+ has_many :foos
130
+ belongs_to :foo# foreign key defaults to class name, {:foreign_key => bar_id}
131
+ belongs_to :other_foo, {:class_name => Foo, :foreign_key => :common_key}#, :foreign_key => :other_foo_key
132
+ end
@@ -451,5 +451,103 @@ class FirstTest < Test::Unit::TestCase
451
451
  all = DeleteMeAll.all
452
452
  assert_equal [], all
453
453
  end
454
+
455
+ def test_update_belongs_to
456
+ f1 = Foo.new()
457
+ f1.save
458
+ assert_equal 0, Arrest::Source.source.edge_count
459
+ b1 = Bar.new({:foo_id => f1.id})
460
+ b1.save
461
+ assert_equal 2, Arrest::Source.source.edge_count
462
+ assert_equal 2, Arrest::Source.source.node_count
463
+
464
+ f2 = Foo.new()
465
+ f2.save
466
+ assert_equal 2, Arrest::Source.source.edge_count
467
+ b1.foo_id = f2.id
468
+ b1.save
469
+ #Arrest::Source.source.edge_matrix.each_pair{|k,v| y k; y v}
470
+ assert_equal 2, Arrest::Source.source.edge_count
471
+ assert_equal 3, Arrest::Source.source.node_count
472
+ end
473
+
474
+ def test_has_many_matrix_in_mem_source
475
+ f1 = Foo.new()
476
+ f1.save
477
+ f2 = Foo.new()
478
+ f2.save
479
+ f3 = Foo.new()
480
+ f3.save
481
+
482
+ b1 = Bar.new({:foo_ids => [f1.id, f2.id], :foo_id => f3.id})
483
+ b1.save
484
+
485
+ assert_equal 2, b1.foos.length
486
+
487
+ b2 = Bar.new({:foo_ids => [f2.id, f3.id], :foo_id =>f1.id})
488
+ b2.save
489
+
490
+ f1.delete
491
+
492
+ b1_rel = Bar.find(b1.id)
493
+ assert_equal 1, b1_rel.foos.length
494
+ assert_equal f2.id, b1_rel.foos.first.id
495
+
496
+
497
+ f2.bar_ids=[b1.id]
498
+ f2.other_bar_ids=[b2.id]
499
+ f2.save
500
+ f2_rel = Foo.find(f2.id)
501
+ assert_equal 1, f2_rel.bars.length
502
+ assert_equal 1, f2_rel.other_bars.length
503
+
504
+ b2.delete
505
+
506
+ f2_rel = Foo.find(f2.id)
507
+ assert_equal 1, f2_rel.bars.length
508
+ assert_equal 0, f2_rel.other_bars.length
509
+ assert_equal b1.id, f2_rel.bars.first.id
510
+
511
+ end
512
+
513
+ def test_has_many_with_belongs_to
514
+ f1 = Foo.new()
515
+ f1.save
516
+ f2 = Foo.new()
517
+ f2.save
518
+ f3 = Foo.new()
519
+ f3.save
520
+
521
+ b1 = Bar.new({:other_foo_id => f1.id, :foo_id => f3.id})
522
+ b1.save
523
+ b2 = Bar.new({:other_foo_id => f2.id, :foo_id => f1.id})
524
+ b2.save
525
+
526
+ f1_rel = Foo.find(f1.id)
527
+ f2_rel = Foo.find(f2.id)
528
+ f3_rel = Foo.find(f3.id)
529
+
530
+ assert_equal 1, f1_rel.bars.length
531
+ assert_equal b1.id, f1_rel.other_bars.first.id
532
+ assert_equal b1.id, f3_rel.bars.first.id
533
+
534
+ assert_equal b2.id, f1_rel.bars.first.id
535
+ assert_equal b2.id, f2_rel.other_bars.first.id
536
+
537
+ #test update
538
+ b1.foo_id = f2.id
539
+ b1.save
540
+
541
+
542
+
543
+ f3_rel = Foo.find(f3.id)
544
+ assert f3_rel.bars.empty?
545
+ f2_rel = Foo.find(f2.id)
546
+ assert_equal b1.id, f2_rel.bars.first.id
547
+
548
+ b1.delete
549
+ f1_rel = Foo.find(f1.id)
550
+ assert f1_rel.other_bars.empty?
551
+ end
454
552
  end
455
553
 
metadata CHANGED
@@ -1,133 +1,176 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: arrest
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.16
3
+ version: !ruby/object:Gem::Version
4
+ hash: 61
5
5
  prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 17
10
+ version: 0.0.17
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Axel Tetzlaff
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2012-01-18 00:00:00.000000000Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2012-01-24 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
15
21
  name: json
16
- requirement: &18586060 !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
17
24
  none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
22
32
  type: :runtime
23
- prerelease: false
24
- version_requirements: *18586060
25
- - !ruby/object:Gem::Dependency
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
26
35
  name: faraday
27
- requirement: &18585500 !ruby/object:Gem::Requirement
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
28
38
  none: false
29
- requirements:
30
- - - =
31
- - !ruby/object:Gem::Version
39
+ requirements:
40
+ - - "="
41
+ - !ruby/object:Gem::Version
42
+ hash: 9
43
+ segments:
44
+ - 0
45
+ - 7
46
+ - 5
32
47
  version: 0.7.5
33
48
  type: :runtime
34
- prerelease: false
35
- version_requirements: *18585500
36
- - !ruby/object:Gem::Dependency
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
37
51
  name: activemodel
38
- requirement: &18585000 !ruby/object:Gem::Requirement
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
39
54
  none: false
40
- requirements:
55
+ requirements:
41
56
  - - ~>
42
- - !ruby/object:Gem::Version
43
- version: '3'
57
+ - !ruby/object:Gem::Version
58
+ hash: 5
59
+ segments:
60
+ - 3
61
+ version: "3"
44
62
  type: :runtime
45
- prerelease: false
46
- version_requirements: *18585000
47
- - !ruby/object:Gem::Dependency
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
48
65
  name: bundler
49
- requirement: &18584540 !ruby/object:Gem::Requirement
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
50
68
  none: false
51
- requirements:
52
- - - ! '>='
53
- - !ruby/object:Gem::Version
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 23
73
+ segments:
74
+ - 1
75
+ - 0
76
+ - 0
54
77
  version: 1.0.0
55
78
  type: :development
56
- prerelease: false
57
- version_requirements: *18584540
58
- - !ruby/object:Gem::Dependency
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
59
81
  name: rake
60
- requirement: &18584160 !ruby/object:Gem::Requirement
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
61
84
  none: false
62
- requirements:
63
- - - ! '>='
64
- - !ruby/object:Gem::Version
65
- version: '0'
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
66
92
  type: :development
67
- prerelease: false
68
- version_requirements: *18584160
69
- - !ruby/object:Gem::Dependency
93
+ version_requirements: *id005
94
+ - !ruby/object:Gem::Dependency
70
95
  name: rdoc
71
- requirement: &18583700 !ruby/object:Gem::Requirement
96
+ prerelease: false
97
+ requirement: &id006 !ruby/object:Gem::Requirement
72
98
  none: false
73
- requirements:
74
- - - ! '>='
75
- - !ruby/object:Gem::Version
76
- version: '0'
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 3
103
+ segments:
104
+ - 0
105
+ version: "0"
77
106
  type: :development
78
- prerelease: false
79
- version_requirements: *18583700
80
- - !ruby/object:Gem::Dependency
107
+ version_requirements: *id006
108
+ - !ruby/object:Gem::Dependency
81
109
  name: rspec
82
- requirement: &18583040 !ruby/object:Gem::Requirement
110
+ prerelease: false
111
+ requirement: &id007 !ruby/object:Gem::Requirement
83
112
  none: false
84
- requirements:
113
+ requirements:
85
114
  - - ~>
86
- - !ruby/object:Gem::Version
87
- version: '2'
115
+ - !ruby/object:Gem::Version
116
+ hash: 7
117
+ segments:
118
+ - 2
119
+ version: "2"
88
120
  type: :development
89
- prerelease: false
90
- version_requirements: *18583040
91
- - !ruby/object:Gem::Dependency
121
+ version_requirements: *id007
122
+ - !ruby/object:Gem::Dependency
92
123
  name: rr
93
- requirement: &18582580 !ruby/object:Gem::Requirement
124
+ prerelease: false
125
+ requirement: &id008 !ruby/object:Gem::Requirement
94
126
  none: false
95
- requirements:
96
- - - ! '>='
97
- - !ruby/object:Gem::Version
98
- version: '0'
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ hash: 3
131
+ segments:
132
+ - 0
133
+ version: "0"
99
134
  type: :development
100
- prerelease: false
101
- version_requirements: *18582580
102
- - !ruby/object:Gem::Dependency
135
+ version_requirements: *id008
136
+ - !ruby/object:Gem::Dependency
103
137
  name: simplecov
104
- requirement: &18581960 !ruby/object:Gem::Requirement
138
+ prerelease: false
139
+ requirement: &id009 !ruby/object:Gem::Requirement
105
140
  none: false
106
- requirements:
107
- - - ! '>='
108
- - !ruby/object:Gem::Version
109
- version: '0'
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ hash: 3
145
+ segments:
146
+ - 0
147
+ version: "0"
110
148
  type: :development
111
- prerelease: false
112
- version_requirements: *18581960
113
- - !ruby/object:Gem::Dependency
149
+ version_requirements: *id009
150
+ - !ruby/object:Gem::Dependency
114
151
  name: rack
115
- requirement: &18581440 !ruby/object:Gem::Requirement
152
+ prerelease: false
153
+ requirement: &id010 !ruby/object:Gem::Requirement
116
154
  none: false
117
- requirements:
118
- - - ! '>='
119
- - !ruby/object:Gem::Version
120
- version: '0'
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ hash: 3
159
+ segments:
160
+ - 0
161
+ version: "0"
121
162
  type: :development
122
- prerelease: false
123
- version_requirements: *18581440
163
+ version_requirements: *id010
124
164
  description: Consume a rest API in a AR like fashion
125
- email:
165
+ email:
126
166
  - axel.tetzlaff@fortytools.com
127
167
  executables: []
168
+
128
169
  extensions: []
170
+
129
171
  extra_rdoc_files: []
130
- files:
172
+
173
+ files:
131
174
  - .gitignore
132
175
  - .rspec
133
176
  - Gemfile
@@ -138,10 +181,13 @@ files:
138
181
  - lib/arrest/abstract_resource.rb
139
182
  - lib/arrest/attributes/attribute.rb
140
183
  - lib/arrest/attributes/belongs_to.rb
184
+ - lib/arrest/attributes/belongs_to_attribute.rb
141
185
  - lib/arrest/attributes/converter.rb
142
186
  - lib/arrest/attributes/has_attributes.rb
187
+ - lib/arrest/attributes/has_many_attribute.rb
143
188
  - lib/arrest/attributes/nested_attribute.rb
144
189
  - lib/arrest/attributes/polymorphic_attribute.rb
190
+ - lib/arrest/class_utils.rb
145
191
  - lib/arrest/exceptions.rb
146
192
  - lib/arrest/handler.rb
147
193
  - lib/arrest/helper/child_collection.rb
@@ -164,28 +210,45 @@ files:
164
210
  - test/nested_resource.rb
165
211
  - test/unit.rb
166
212
  - test/validations.rb
167
- homepage: ''
213
+ homepage: ""
168
214
  licenses: []
215
+
169
216
  post_install_message:
170
217
  rdoc_options: []
171
- require_paths:
218
+
219
+ require_paths:
172
220
  - lib
173
- required_ruby_version: !ruby/object:Gem::Requirement
221
+ required_ruby_version: !ruby/object:Gem::Requirement
174
222
  none: false
175
- requirements:
176
- - - ! '>='
177
- - !ruby/object:Gem::Version
178
- version: '0'
179
- required_rubygems_version: !ruby/object:Gem::Requirement
223
+ requirements:
224
+ - - ">="
225
+ - !ruby/object:Gem::Version
226
+ hash: 3
227
+ segments:
228
+ - 0
229
+ version: "0"
230
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
231
  none: false
181
- requirements:
182
- - - ! '>='
183
- - !ruby/object:Gem::Version
184
- version: '0'
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ hash: 3
236
+ segments:
237
+ - 0
238
+ version: "0"
185
239
  requirements: []
240
+
186
241
  rubyforge_project: arrest
187
242
  rubygems_version: 1.8.10
188
243
  signing_key:
189
244
  specification_version: 3
190
245
  summary: Another ruby rest client
191
- test_files: []
246
+ test_files:
247
+ - spec/arrest_spec.rb
248
+ - spec/spec_helper.rb
249
+ - spec/support/models/user.rb
250
+ - test/has_attributed.rb
251
+ - test/models.rb
252
+ - test/nested_resource.rb
253
+ - test/unit.rb
254
+ - test/validations.rb