directed-edge 0.2.0 → 0.2.1

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/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  coverage
2
2
  rdoc
3
3
  pkg
4
+ .yardoc
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (C) 2009, Directed Edge, Inc. <info@directededge.com>
1
+ Copyright (C) 2009-2010, Directed Edge, Inc. <info@directededge.com>
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without
4
4
  modification, are permitted provided that the following conditions
data/README.rdoc CHANGED
@@ -2,16 +2,44 @@
2
2
 
3
3
  Bindings for the Directed Edge Web-services API
4
4
 
5
- == Note on Patches/Pull Requests
5
+ == Usage
6
+
7
+ You should have gotten a user name and password from Directed Edge when you
8
+ signed up for an account. If you don't have one now, you can get one at:
9
+
10
+ - {Signup for Directed Edge Account}[http://www.directededge.com/signup.html]
11
+
12
+ You'll use those when instantiating a Directed Edge database object, which will
13
+ be the hub for other operations:
14
+
15
+ DE_USER = 'testaccount'
16
+ DE_PASS = '1234567890abcd'
17
+
18
+ database = DirectedEdge::Database.new(DE_USER, DE_PASS)
19
+
20
+ From there you can create items:
21
+
22
+ item1 = DirectedEdge::Item.new(database, 'item_1')
23
+ item2 = DirectedEdge::Item.new(database, 'item_2')
24
+
25
+ Push them over to the Directed Edge web service:
26
+
27
+ item1.save
28
+ item2.save
29
+
30
+ And do stuff with them, like set properties, tags, link them to other items:
31
+
32
+ item1['picture'] = 'http://foo.bar.com/1.jpg'
33
+ item1.add_tag('product')
34
+ item1.link_to(item2)
35
+ item1.save
36
+
37
+ There's more info on the Directed Edge developer site:
38
+
39
+ - {Getting started for Ruby developers}[http://developer.directededge.com/article/Getting_started_for_Ruby_developers]
40
+ - {Ruby E-Commerce Tutorial}[http://developer.directededge.com/article/Ruby_Bindings_for_E-Commerce_Tutorial]
6
41
 
7
- * Fork the project.
8
- * Make your feature addition or bug fix.
9
- * Add tests for it. This is important so I don't break it in a
10
- future version unintentionally.
11
- * Commit, do not mess with rakefile, version, or history.
12
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
- * Send me a pull request. Bonus points for topic branches.
14
42
 
15
43
  == Copyright
16
44
 
17
- Copyright (c) 2009 Directed Edge, Inc. See LICENSE for details.
45
+ Copyright (c) 2009-2010 Directed Edge, Inc. See LICENSE for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1
data/lib/directed_edge.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009 Directed Edge, Inc.
1
+ # Copyright (C) 2009-2010 Directed Edge, Inc.
2
2
  #
3
3
  # Redistribution and use in source and binary forms, with or without
4
4
  # modification, are permitted provided that the following conditions
@@ -34,13 +34,10 @@ require 'cgi'
34
34
 
35
35
  module DirectedEdge
36
36
 
37
- # A hash subclass that tracks the insert order, which is useful when returning
38
- # a set of results that include full properties.
37
+ # @private
39
38
 
40
39
  class InsertOrderHash < Hash
41
40
 
42
- # Overridden assignment to track insert order
43
-
44
41
  def []=(key, value)
45
42
  store(key, value)
46
43
  @insert_order = [] if @insert_order.nil?
@@ -48,13 +45,13 @@ module DirectedEdge
48
45
  @insert_order.push(key)
49
46
  end
50
47
 
51
- # Provides an iterator that uses the hash's insert order
52
-
53
48
  def insert_order_each
54
49
  @insert_order.each { |key| yield key, fetch(key) } unless @insert_order.nil?
55
50
  end
56
51
  end
57
52
 
53
+ # @private
54
+
58
55
  class CollectionHash < Hash
59
56
  def initialize(type)
60
57
  @type = type
@@ -71,13 +68,16 @@ module DirectedEdge
71
68
  end
72
69
  end
73
70
 
74
- # Base class used for Database and Item that has some basic resource
75
- # grabbing functionality.
71
+ # @private
76
72
 
77
73
  class Resource
78
74
 
79
75
  private
80
76
 
77
+ def initialize(rest_resource)
78
+ @resource = rest_resource
79
+ end
80
+
81
81
  # Reads an item from the database and puts it into an XML document.
82
82
 
83
83
  def read_document(method='', params={})
@@ -85,7 +85,7 @@ module DirectedEdge
85
85
  REXML::Document.new(@resource[method].get(:accept => 'text/xml').to_s)
86
86
  end
87
87
 
88
- # Returns an array of the elements from the document matching the given
88
+ # @return [Array] The elements from the document matching the given
89
89
  # element name.
90
90
 
91
91
  def list_from_document(document, element)
@@ -112,7 +112,7 @@ module DirectedEdge
112
112
  # so that they can be passed off to the web services API -- e.g.
113
113
  # :foo_bar to 'fooBar'
114
114
 
115
- def normalize_params(hash)
115
+ def normalize_params!(hash)
116
116
  hash.each do |key, value|
117
117
  if !key.is_a?(String)
118
118
  hash.delete(key)
@@ -156,28 +156,48 @@ module DirectedEdge
156
156
  attr_reader :resource
157
157
 
158
158
  # Creates a connection to a Directed Edge database. The name and password
159
- # should have been provided when the account was created. The protocol
160
- # parameter is optional and may be <tt>http</tt> or <tt>https</tt>.
161
- # <tt>http</tt> is used by default as it is somewhat lower latency.
159
+ # should have been provided when the account was created.
160
+ #
161
+ # @param [String] name User name given when the Directed Edge account was
162
+ # created.
163
+ # @param [String] password Password given when the Directed Edge account was
164
+ # created.
165
+ # @param [String] protocol The protocol to connect to the Directed Edge
166
+ # webservices with.
167
+ #
168
+ # @return [DirectedEdge::Item]
162
169
 
163
- def initialize(name, password='', protocol='http')
170
+ def initialize(name, password='', protocol='http', options = {})
164
171
  @name = name
165
- host = ENV['DIRECTEDEDGE_HOST'] || 'webservices.directededge.com'
166
- @resource =
167
- RestClient::Resource.new("#{protocol}://#{name}:#{password}@#{host}/api/v1/#{name}")
172
+ host = options[:host] || ENV['DIRECTEDEDGE_HOST'] || 'webservices.directededge.com'
173
+ url = "#{protocol}://#{name}:#{password}@#{host}/api/v1/#{name}"
174
+
175
+ options[:timeout] ||= 10
176
+
177
+ super(RestClient::Resource.new(url, options))
168
178
  end
169
179
 
170
180
  # Imports a Directed Edge XML file to the database.
171
181
  #
172
- # See http://developer.directededge.com for more information on the XML format or the
173
- # Exporter for help on creating a file for importing.
182
+ # @see Exporter
183
+ #
184
+ # @see {Developer site}[http://developer.directededge.com/] for more information
185
+ # on the XML format.
174
186
 
175
187
  def import(file_name)
176
188
  @resource.put(File.read(file_name), :content_type => 'text/xml')
177
189
  end
178
190
 
179
- # Returns a set of recommendations for the set of items that is passed in in
191
+ # @return [Array] A set of recommendations for the set of items that is passed in in
180
192
  # aggregate, commonly used to do recommendations for a basket of items.
193
+ #
194
+ # @param [Array] items List of items to base the recommendations on, e.g. all of the
195
+ # items in the basket.
196
+ #
197
+ # The tags and params parameters are equivalent to those with the normal Item#related
198
+ # call.
199
+ #
200
+ # @see Item#related
181
201
 
182
202
  def group_related(items=Set.new, tags=Set.new, params={})
183
203
  if !items.is_a?(Array) || items.size < 1
@@ -186,7 +206,7 @@ module DirectedEdge
186
206
  params['items'] = items.to_a.join(',')
187
207
  params['tags'] = tags.to_a.join(',')
188
208
  params['union'] = true
189
- params = normalize_params(params)
209
+ normalize_params!(params)
190
210
  if params['includeProperties'] == 'true'
191
211
  property_hash_from_document(read_document('related', params), 'related')
192
212
  else
@@ -236,6 +256,8 @@ module DirectedEdge
236
256
  # destination is an existing database object, updates will be queued until
237
257
  # finish is called, at which point they will be uploaded to the webservices
238
258
  # in batch.
259
+ #
260
+ # @return [Exporter]
239
261
 
240
262
  def initialize(destination)
241
263
  if destination.is_a?(String)
@@ -321,8 +343,9 @@ module DirectedEdge
321
343
  # manipulated locally and then saved back to the database by calling save.
322
344
 
323
345
  def initialize(database, id)
324
- @database = database
346
+ super(database.resource[URI.escape(id, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))])
325
347
 
348
+ @database = database
326
349
  @id = id
327
350
  @links = CollectionHash.new(Hash)
328
351
  @tags = Set.new
@@ -335,13 +358,11 @@ module DirectedEdge
335
358
  @preselected_to_remove = Set.new
336
359
  @blacklisted_to_remove = Set.new
337
360
  @properties_to_remove = Set.new
338
-
339
- @resource = @database.resource[URI.escape(@id)]
340
361
  @cached = false
341
362
  end
342
363
 
343
- # Returns true if the other item is the same. The item given can either be
344
- # a string or an item object.
364
+ # @return [Boolean] True if the other item has the same ID. The item given
365
+ # can either be a string or an item object.
345
366
 
346
367
  def ==(other)
347
368
  if other.is_a?(Item)
@@ -351,17 +372,13 @@ module DirectedEdge
351
372
  end
352
373
  end
353
374
 
354
- # Returns the item's ID.
375
+ # @return [String] The item's ID
355
376
 
356
377
  def name
357
378
  @id
358
379
  end
359
380
 
360
- # Creates an item if it does not already exist in the database or overwrites
361
- # an existing item if one does.
362
- #
363
- # This has been deprecated as it's not set up to properly support link types.
364
- # use new / save instead.
381
+ # @deprecated Use new / save instead.
365
382
 
366
383
  def create(links={}, tags=Set.new, properties={})
367
384
  warn 'DirectedEdge::Item::create has been deprecated. Use new / save instead.'
@@ -378,9 +395,11 @@ module DirectedEdge
378
395
 
379
396
  # Writes all changes to links, tags and properties back to the database and
380
397
  # returns this item.
398
+ #
399
+ # @return [Item]
381
400
 
382
- def save
383
- if @cached
401
+ def save(options={})
402
+ if options[:overwrite] || @cached
384
403
  put(complete_document)
385
404
  else
386
405
 
@@ -410,6 +429,8 @@ module DirectedEdge
410
429
 
411
430
  # Reloads (or loads) the item from the database. Any unsaved changes will
412
431
  # will be discarded.
432
+ #
433
+ # @return [Item]
413
434
 
414
435
  def reload
415
436
  @links.clear
@@ -426,44 +447,56 @@ module DirectedEdge
426
447
 
427
448
  @cached = false
428
449
  read
450
+ self
429
451
  end
430
452
 
431
- # Returns a set of items that are linked to from this item.
453
+ # @return [Set] Items that are linked to from this item.
454
+ #
455
+ # @param [String] type Only links for the specified link-type will be
456
+ # returned.
432
457
 
433
458
  def links(type='')
434
459
  read
435
460
  @links[type.to_s]
436
461
  end
437
462
 
438
- # Returns a set containing all of this item's tags.
463
+ # @return [Set] The tags for this item.
439
464
 
440
465
  def tags
441
466
  read
442
467
  @tags
443
468
  end
444
469
 
445
- # Returns an ordered list of items that are preselected for this item.
470
+ # An ordered list of preselected recommendations for this item.
471
+ #
472
+ # @return [Array] The preselected recommendations for this item.
446
473
 
447
474
  def preselected
448
475
  read
449
476
  @preselected
450
477
  end
451
478
 
452
- # Returns a set of items that should not ever be recommended for this item.
479
+ # An ordered list of blacklisted recommendations for this item.
480
+ #
481
+ # @return [Array] The items blacklisted from being recommended for this item.
453
482
 
454
483
  def blacklisted
455
484
  read
456
485
  @blacklisted
457
486
  end
458
487
 
459
- # Returns a hash of all of this item's properties.
488
+ # All properties for this item.
489
+ #
490
+ # @return [Hash] All of the properties for this item.
460
491
 
461
492
  def properties
462
493
  read
463
494
  @properties
464
495
  end
465
496
 
466
- # Returns the property for the name specified.
497
+ # Fetches properties of the item.
498
+ #
499
+ # @return [String] The property for this item.
467
500
 
468
501
  def [](property_name)
469
502
  read
@@ -473,17 +506,23 @@ module DirectedEdge
473
506
  # Assigns value to the given property_name.
474
507
  #
475
508
  # This will not be written back to the database until save is called.
509
+ #
510
+ # @return [Item]
476
511
 
477
512
  def []=(property_name, value)
478
513
  @properties_to_remove.delete(property_name)
479
514
  @properties[property_name] = value
515
+ self
480
516
  end
481
517
 
482
518
  # Remove the given property_name.
519
+ #
520
+ # @return [Item]
483
521
 
484
522
  def clear_property(property_name)
485
523
  @properties_to_remove.add(property_name) unless @cached
486
524
  @properties.delete(property_name)
525
+ self
487
526
  end
488
527
 
489
528
  # Removes an item from the database, including deleting all links to and
@@ -491,6 +530,7 @@ module DirectedEdge
491
530
 
492
531
  def destroy
493
532
  @resource.delete
533
+ nil
494
534
  end
495
535
 
496
536
  # Creates a link from this item to other.
@@ -503,33 +543,50 @@ module DirectedEdge
503
543
  # user.link_to(product, 5)
504
544
  # user.save
505
545
  #
506
- # If no link is specified then a tradtional, unweighted link will be
507
- # created. This is typical to, for instance, incidate a purchase or click
508
- # from a user to a page or item.
509
- #
510
- # Weights may be in the range of 1 to 10.
546
+ # @param [String] other An identifier (or Item instance) for an item to be linked
547
+ # to.
548
+ # @param [Integer] weight A weight in the range of 1 to 10 for this link. If not
549
+ # specified (which is fine for most situations) an unweighted link will be
550
+ # created.
551
+ # @param [String] type The link type to be used for this connection, or, the
552
+ # default untyped link. This could be, for example, *purchase* or *rating*.
511
553
  #
512
554
  # Note that 'other' must exist in the database or must be saved before this
513
555
  # item is saved. Otherwise the link will be ignored as the engine tries
514
556
  # to detect 'broken' links that do not terminate at a valid item.
557
+ #
558
+ # @return [String] The item ID just linked to
515
559
 
516
560
  def link_to(other, weight=0, type='')
517
561
  raise RangeError if (weight < 0 || weight > 10)
518
562
  @links_to_remove[type.to_s].delete(other)
519
563
  @links[type.to_s][other.to_s] = weight
564
+ other
520
565
  end
521
566
 
522
- # Deletes a link from this item to other.
567
+ # Removes a relationship from this item to another item.
523
568
  #
524
569
  # The changes will not be reflected in the database until save is called.
570
+ #
571
+ # @param [String] other The ID (or Item instance) for an object to be
572
+ # unlinked.
573
+ # @return [String] The item ID just unlinked from.
574
+ # @see Item#link_to
525
575
 
526
576
  def unlink_from(other, type='')
527
577
  @links_to_remove[type.to_s].add(other.to_s) unless @cached
528
578
  @links[type.to_s].delete(other.to_s)
579
+ other
529
580
  end
530
581
 
531
582
  # If there is a link for "other" then it returns the weight for the given
532
583
  # item. Zero indicates that no weight is assigned.
584
+ #
585
+ # @param [String] other The item being queried for.
586
+ # @param [String] type The link type of the relationship.
587
+ #
588
+ # @return [Integer] The weight for a link from this item to the specified
589
+ # item, or nil if not found.
533
590
 
534
591
  def weight_for(other, type='')
535
592
  read
@@ -538,55 +595,118 @@ module DirectedEdge
538
595
 
539
596
  # Adds a tag to this item.
540
597
  #
598
+ # @param [String] tag The tag to be added to this item's tag set.
599
+ # @return [String] The tag just added.
600
+ #
541
601
  # The changes will not be reflected in the database until save is called.
542
602
 
543
603
  def add_tag(tag)
544
604
  @tags_to_remove.delete(tag)
545
605
  @tags.add(tag)
606
+ tag
546
607
  end
547
608
 
548
609
  # Removes a tag from this item.
549
610
  #
611
+ # @param [String] tag The tag to be removed from this item's set of tags.
612
+ # @return [String] The tag just removed.
613
+ #
550
614
  # The changes will not be reflected in the database until save is called.
551
615
 
552
616
  def remove_tag(tag)
553
617
  @tags_to_remove.add(tag) unless @cached
554
618
  @tags.delete(tag)
619
+ tag
555
620
  end
556
621
 
622
+ # Adds a hand-picked recommendation for this item.
623
+ #
624
+ # Note that preselected recommendations are weighted by the order that they
625
+ # are added, i.e. the first preselected item added will be the first one
626
+ # shown.
627
+ #
628
+ # @param [String] item The ID (or an Item instance) of the item that should
629
+ # be always returned as a recommendation for this item.
630
+ # @return [String] The ID just added.
631
+
557
632
  def add_preselected(item)
558
- @preselected_to_remove.delete(item)
559
- @preselected.push(item)
633
+ @preselected_to_remove.delete(item.to_s)
634
+ @preselected.push(item.to_s)
635
+ item
560
636
  end
561
637
 
638
+ # Removes a hand-picked recommendation for this item.
639
+ #
640
+ # @param [String] item The ID (or an Item instance) of the item that should
641
+ # be removed from the preselected list.
642
+ # @return [String] The ID just removed.
643
+ #
644
+ # @see Item#add_preselected
645
+
562
646
  def remove_preselected(item)
563
- @preselected_to_remove.add(item) unless @cached
564
- @preselected.delete(item)
647
+ @preselected_to_remove.add(item.to_s) unless @cached
648
+ @preselected.delete(item.to_s)
649
+ item
565
650
  end
566
651
 
652
+ # Adds a blacklisted item that should never be shown as recommended for this
653
+ # item.
654
+ #
655
+ # @param [String] item The ID (or an Item instance) of the item that should
656
+ # be blacklisted.
657
+ # @return [String] The ID just blacklisted.
658
+
567
659
  def add_blacklisted(item)
568
- @blacklisted_to_remove.delete(item)
569
- @blacklisted.add(item)
660
+ @blacklisted_to_remove.delete(item.to_s)
661
+ @blacklisted.add(item.to_s)
662
+ item
570
663
  end
571
664
 
665
+ # Removes a blacklisted item.
666
+ #
667
+ # @param [String] item The ID (or an Item instance) of the item that should
668
+ # be removed from the blacklist.
669
+ # @return [String] The ID just delisted.
670
+ #
671
+ # @see Item::add_blacklisted
672
+
572
673
  def remove_blacklisted(item)
573
- @blacklisted_to_remove.add(item) unless @cached
574
- @blacklisted.delete(item)
674
+ @blacklisted_to_remove.add(item.to_s) unless @cached
675
+ @blacklisted.delete(item.to_s)
676
+ item
575
677
  end
576
678
 
577
- # Returns the list of items related to this one. Unlike "recommended" this
578
- # may include items which are directly linked from this item. If any tags
579
- # are specified, only items which have one or more of the specified tags
679
+ # related and recommended are the two main methods for querying for
680
+ # recommendations with the Directed Edge API. Related is for *similar*
681
+ # items, e.g. "products like this product", whereas recommended is for
682
+ # personalized recommendations, i.e. "We think you'd like..."
683
+ #
684
+ # @return [Array] List of item IDs related to this one with the most closely
685
+ # related items first.
686
+ #
687
+ # @param [Set] tags Only items which have at least one of the provided tags
580
688
  # will be returned.
581
689
  #
582
- # Parameters that may be passed in include:
583
- # - :exclude_linked (true / false)
584
- # - :max_results (integer)
690
+ # @param [Hash] options A set of options which are passed directly on to
691
+ # the web services API in the query string.
692
+ #
693
+ # @option params [Boolean] :exclude_linked (false)
694
+ # Exclude items which are linked directly from this item.
695
+ # @option params [Integer] :max_results (20)
696
+ # Only returns up to :max_results items.
697
+ # @option params [Integer] :link_type_weight (1)
698
+ # Here link_type should be replace with one of the actual link types in
699
+ # use in your database, i.e. :purchase_weight and specifies how strongly
700
+ # links of that type should be weighted related to other link types. For
701
+ # Instance if you wanted 20% ratings and 80% purchases you would specify:
702
+ # :purchases_weight => 8, :ratings_weight => 2
585
703
  #
586
704
  # This will not reflect any unsaved changes to items.
705
+ #
706
+ # @see Item#recommended
587
707
 
588
708
  def related(tags=Set.new, params={})
589
- params = normalize_params(params)
709
+ normalize_params!(params)
590
710
  params['tags'] = tags.to_a.join(',')
591
711
  if params['includeProperties'] == 'true'
592
712
  property_hash_from_document(read_document('related', params), 'related')
@@ -595,19 +715,37 @@ module DirectedEdge
595
715
  end
596
716
  end
597
717
 
598
- # Returns the list of items recommended for this item, usually a user.
599
- # Unlike "related" this does not include items linked from this item. If
600
- # any tags are specified, only items which have one or more of the specified
601
- # tags will be returned.
718
+ # related and recommended are the two main methods for querying for
719
+ # recommendations with the Directed Edge API. Related is for *similar*
720
+ # items, e.g. "products like this product", whereas recommended is for
721
+ # personalized recommendations, i.e. "We think you'd like..."
722
+ #
723
+ # @return [Array] List of item IDs recommeded for this item with the most
724
+ # strongly recommended items first.
725
+ #
726
+ # @param [Set] tags Only items which have at least one of the provided tags
727
+ # will be returned.
728
+ #
729
+ # @param [Hash] options A set of options which are passed directly on to
730
+ # the web services API in the query string.
602
731
  #
603
- # Parameters that may be passed in include:
604
- # - :exclude_linked (true / false)
605
- # - :max_results (integer)
732
+ # @option params [Boolean] :exclude_linked (false)
733
+ # Exclude items which are linked directly from this item.
734
+ # @option params [Integer] :max_results (20)
735
+ # Only returns up to :max_results items.
736
+ # @option params [Integer] :link_type_weight (1)
737
+ # Here link_type should be replace with one of the actual link types in
738
+ # use in your database, i.e. :purchase_weight and specifies how strongly
739
+ # links of that type should be weighted related to other link types. For
740
+ # Instance if you wanted 20% ratings and 80% purchases you would specify:
741
+ # :purchases_weight => 8, :ratings_weight => 2
606
742
  #
607
743
  # This will not reflect any unsaved changes to items.
744
+ #
745
+ # @see Item#related
608
746
 
609
747
  def recommended(tags=Set.new, params={})
610
- params = normalize_params(params)
748
+ normalize_params!(params)
611
749
  params['tags'] = tags.to_a.join(',')
612
750
  params.key?('excludeLinked') || params['excludeLinked'] = 'true'
613
751
  if params['includeProperties'] == 'true'
@@ -617,13 +755,13 @@ module DirectedEdge
617
755
  end
618
756
  end
619
757
 
620
- # Returns the ID of the item.
758
+ # @return [String] The ID of the item.
621
759
 
622
760
  def to_s
623
761
  @id
624
762
  end
625
763
 
626
- # Returns an XML representation of the item as a string not including the
764
+ # @return [String] An XML representation of the item as a string not including the
627
765
  # usual document regalia, e.g. starting with <item> (used for exporting the
628
766
  # item to a file)
629
767
 
@@ -638,45 +776,41 @@ module DirectedEdge
638
776
 
639
777
  def read
640
778
  unless @cached
641
- begin
642
- document = read_document
643
-
644
- document.elements.each('//link') do |link_element|
645
- type = link_element.attribute('type')
646
- type = type ? type.to_s : ''
647
- weight = link_element.attribute('weight').to_s.to_i
648
- target = link_element.text
649
- @links[type][target] = weight unless @links[type][target]
650
- end
651
-
652
- @tags.merge(list_from_document(document, 'tag'))
653
- @preselected.concat(list_from_document(document, 'preselected'))
654
- @blacklisted.merge(list_from_document(document, 'blacklisted'))
655
-
656
- document.elements.each('//property') do |element|
657
- name = element.attribute('name').value
658
- @properties[name] = element.text unless @properties.has_key?(name)
659
- end
660
-
661
- @links_to_remove.each do |type, links|
662
- links.each { |link, weight| @links[type].delete(link) }
663
- end
664
-
665
- @tags_to_remove.each { |tag| @tags.delete(tag) }
666
- @preselected_to_remove.each { |p| @preselected.delete(p) }
667
- @blacklisted_to_remove.each { |b| @blacklisted.delete(b) }
668
- @properties_to_remove.each { |property| @properties.delete(property) }
779
+ document = read_document
780
+
781
+ document.elements.each('//link') do |link_element|
782
+ type = link_element.attribute('type')
783
+ type = type ? type.to_s : ''
784
+ weight = link_element.attribute('weight').to_s.to_i
785
+ target = link_element.text
786
+ @links[type][target] = weight unless @links[type][target]
787
+ end
669
788
 
670
- @links_to_remove.clear
671
- @tags_to_remove.clear
672
- @preselected_to_remove.clear
673
- @blacklisted_to_remove.clear
674
- @properties_to_remove.clear
789
+ @tags.merge(list_from_document(document, 'tag'))
790
+ @preselected.concat(list_from_document(document, 'preselected'))
791
+ @blacklisted.merge(list_from_document(document, 'blacklisted'))
792
+
793
+ document.elements.each('//property') do |element|
794
+ name = element.attribute('name').value
795
+ @properties[name] = element.text unless @properties.has_key?(name)
796
+ end
675
797
 
676
- @cached = true
677
- rescue => ex
678
- puts "Couldn't read \"#{@id}\" from the database, #{ex}"
798
+ @links_to_remove.each do |type, links|
799
+ links.each { |link, weight| @links[type].delete(link) }
679
800
  end
801
+
802
+ @tags_to_remove.each { |tag| @tags.delete(tag) }
803
+ @preselected_to_remove.each { |p| @preselected.delete(p) }
804
+ @blacklisted_to_remove.each { |b| @blacklisted.delete(b) }
805
+ @properties_to_remove.each { |property| @properties.delete(property) }
806
+
807
+ @links_to_remove.clear
808
+ @tags_to_remove.clear
809
+ @preselected_to_remove.clear
810
+ @blacklisted_to_remove.clear
811
+ @properties_to_remove.clear
812
+
813
+ @cached = true
680
814
  end
681
815
  end
682
816
 
@@ -258,6 +258,8 @@ class TestDirectedEdge < Test::Unit::TestCase
258
258
  def test_load
259
259
  return if ENV['NO_LOAD_TEST']
260
260
 
261
+ Process.setrlimit(Process::RLIMIT_NOFILE, 4096, 65536)
262
+
261
263
  def run_load_test(prefix, count)
262
264
  (1..count).concurrently do |i|
263
265
  item = DirectedEdge::Item.new(@database, "test_item_#{prefix}_#{i}")
@@ -295,13 +297,13 @@ class TestDirectedEdge < Test::Unit::TestCase
295
297
  # Test an out of range ranking.
296
298
 
297
299
  customer1.links[customer2] = -1
298
- assert_raise(RestClient::RequestFailed) { customer1.save }
300
+ assert_raise(RestClient::UnprocessableEntity) { customer1.save }
299
301
 
300
302
  # And another.
301
303
 
302
304
  customer1.reload
303
305
  customer1.links[customer2] = 100
304
- assert_raise(RestClient::RequestFailed) { customer1.save }
306
+ assert_raise(RestClient::UnprocessableEntity) { customer1.save }
305
307
 
306
308
  customer1.reload
307
309
  customer1.link_to(customer3, 10)
@@ -322,6 +324,13 @@ class TestDirectedEdge < Test::Unit::TestCase
322
324
 
323
325
  item = DirectedEdge::Item.new(@database, ';@%&!')
324
326
  assert(item['foo'] == 'bar')
327
+
328
+ item = DirectedEdge::Item.new(@database, 'foo/bar')
329
+ item['foo'] = 'bar'
330
+ item.save
331
+
332
+ item = DirectedEdge::Item.new(@database, 'foo/bar')
333
+ assert(item['foo'] == 'bar')
325
334
  end
326
335
 
327
336
  def test_bad_links
@@ -330,7 +339,7 @@ class TestDirectedEdge < Test::Unit::TestCase
330
339
 
331
340
  item = DirectedEdge::Item.new(@database, 'customer1')
332
341
  item.link_to('also does not exist')
333
- assert_raise(RestClient::RequestFailed) { item.save }
342
+ assert_raise(RestClient::UnprocessableEntity) { item.save }
334
343
  end
335
344
 
336
345
  def test_query_parameters
@@ -414,4 +423,23 @@ class TestDirectedEdge < Test::Unit::TestCase
414
423
  assert(!customer.blacklisted.include?(first))
415
424
  assert(customer.recommended(['product']).include?(first))
416
425
  end
426
+
427
+ def test_timeout
428
+ timeout = 5
429
+ database = DirectedEdge::Database.new('dummy', 'dummy', 'http',
430
+ :host => 'localhost:4567', :timeout => timeout)
431
+ start = Time.now
432
+ timed_out = false
433
+
434
+ begin
435
+ item = DirectedEdge::Item.new(database, 'dummy')
436
+ item.tags
437
+ rescue RestClient::RequestTimeout
438
+ timed_out = true
439
+ assert(Time.now - start < timeout + 1)
440
+ rescue
441
+ end
442
+
443
+ assert(timed_out)
444
+ end
417
445
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: directed-edge
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 21
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
8
  - 2
8
- - 0
9
- version: 0.2.0
9
+ - 1
10
+ version: 0.2.1
10
11
  platform: ruby
11
12
  authors:
12
13
  - Directed Edge
@@ -14,16 +15,18 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-04-25 00:00:00 +02:00
18
+ date: 2010-11-19 00:00:00 +01:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: rest-client
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
24
26
  requirements:
25
27
  - - ">="
26
28
  - !ruby/object:Gem::Version
29
+ hash: 3
27
30
  segments:
28
31
  - 0
29
32
  version: "0"
@@ -53,28 +56,32 @@ homepage: http://developer.directededge.com/
53
56
  licenses: []
54
57
 
55
58
  post_install_message:
56
- rdoc_options:
57
- - --charset=UTF-8
59
+ rdoc_options: []
60
+
58
61
  require_paths:
59
62
  - lib
60
63
  required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
61
65
  requirements:
62
66
  - - ">="
63
67
  - !ruby/object:Gem::Version
68
+ hash: 3
64
69
  segments:
65
70
  - 0
66
71
  version: "0"
67
72
  required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
68
74
  requirements:
69
75
  - - ">="
70
76
  - !ruby/object:Gem::Version
77
+ hash: 3
71
78
  segments:
72
79
  - 0
73
80
  version: "0"
74
81
  requirements: []
75
82
 
76
83
  rubyforge_project:
77
- rubygems_version: 1.3.6
84
+ rubygems_version: 1.3.7
78
85
  signing_key:
79
86
  specification_version: 3
80
87
  summary: Bindings for the Directed Edge webservices API