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 +1 -0
- data/LICENSE +1 -1
- data/README.rdoc +37 -9
- data/VERSION +1 -1
- data/lib/directed_edge.rb +242 -108
- data/test/test_directed_edge.rb +31 -3
- metadata +13 -6
data/.gitignore
CHANGED
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
|
-
==
|
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.
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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.
|
160
|
-
#
|
161
|
-
#
|
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
|
-
|
167
|
-
|
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
|
-
#
|
173
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
375
|
+
# @return [String] The item's ID
|
355
376
|
|
356
377
|
def name
|
357
378
|
@id
|
358
379
|
end
|
359
380
|
|
360
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
507
|
-
#
|
508
|
-
#
|
509
|
-
#
|
510
|
-
#
|
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
|
-
#
|
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
|
-
#
|
578
|
-
#
|
579
|
-
#
|
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
|
-
#
|
583
|
-
#
|
584
|
-
#
|
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
|
-
|
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
|
-
#
|
599
|
-
#
|
600
|
-
#
|
601
|
-
#
|
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
|
-
#
|
604
|
-
#
|
605
|
-
#
|
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
|
-
|
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
|
-
#
|
758
|
+
# @return [String] The ID of the item.
|
621
759
|
|
622
760
|
def to_s
|
623
761
|
@id
|
624
762
|
end
|
625
763
|
|
626
|
-
#
|
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
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
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
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
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
|
-
|
677
|
-
|
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
|
|
data/test/test_directed_edge.rb
CHANGED
@@ -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::
|
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::
|
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::
|
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
|
-
-
|
9
|
-
version: 0.2.
|
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-
|
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
|
-
|
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.
|
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
|