directed-edge 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|