riak-client 2.2.0.pre1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile +0 -2
  4. data/README.markdown +11 -7
  5. data/RELEASE_NOTES.md +29 -2
  6. data/Rakefile +5 -3
  7. data/lib/riak/bucket.rb +55 -23
  8. data/lib/riak/bucket_properties.rb +8 -1
  9. data/lib/riak/bucket_type.rb +29 -0
  10. data/lib/riak/bucket_typed/bucket.rb +15 -7
  11. data/lib/riak/client.rb +24 -8
  12. data/lib/riak/client/beefcake/bucket_properties_operator.rb +8 -8
  13. data/lib/riak/client/beefcake/crdt/set_loader.rb +1 -1
  14. data/lib/riak/client/beefcake/crdt_loader.rb +1 -1
  15. data/lib/riak/client/beefcake/crdt_operator.rb +9 -9
  16. data/lib/riak/client/beefcake/message_codes.rb +4 -0
  17. data/lib/riak/client/beefcake/message_overlay.rb +4 -0
  18. data/lib/riak/client/beefcake/messages.rb +35 -5
  19. data/lib/riak/client/beefcake/object_methods.rb +21 -13
  20. data/lib/riak/client/beefcake/protocol.rb +7 -7
  21. data/lib/riak/client/beefcake/socket.rb +2 -2
  22. data/lib/riak/client/beefcake_protobuffs_backend.rb +58 -33
  23. data/lib/riak/client/protobuffs_backend.rb +5 -5
  24. data/lib/riak/client/yokozuna.rb +3 -3
  25. data/lib/riak/core_ext/deep_dup.rb +1 -1
  26. data/lib/riak/counter.rb +10 -10
  27. data/lib/riak/crdt/base.rb +39 -21
  28. data/lib/riak/crdt/batch_counter.rb +5 -5
  29. data/lib/riak/crdt/batch_map.rb +2 -2
  30. data/lib/riak/crdt/counter.rb +7 -7
  31. data/lib/riak/crdt/inner_counter.rb +4 -4
  32. data/lib/riak/crdt/inner_flag.rb +3 -3
  33. data/lib/riak/crdt/inner_map.rb +1 -1
  34. data/lib/riak/crdt/inner_register.rb +1 -1
  35. data/lib/riak/crdt/inner_set.rb +5 -5
  36. data/lib/riak/crdt/map.rb +9 -9
  37. data/lib/riak/crdt/set.rb +10 -10
  38. data/lib/riak/crdt/typed_collection.rb +39 -36
  39. data/lib/riak/errors/base.rb +1 -1
  40. data/lib/riak/errors/crdt_error.rb +20 -0
  41. data/lib/riak/errors/search_error.rb +6 -0
  42. data/lib/riak/index_collection.rb +1 -1
  43. data/lib/riak/link.rb +5 -3
  44. data/lib/riak/locale/en.yml +5 -1
  45. data/lib/riak/map_reduce.rb +7 -7
  46. data/lib/riak/map_reduce/filter_builder.rb +2 -2
  47. data/lib/riak/map_reduce/phase.rb +2 -2
  48. data/lib/riak/preflist_item.rb +7 -0
  49. data/lib/riak/rcontent.rb +8 -8
  50. data/lib/riak/robject.rb +27 -14
  51. data/lib/riak/search.rb +1 -0
  52. data/lib/riak/search/index.rb +17 -3
  53. data/lib/riak/search/query.rb +14 -6
  54. data/lib/riak/search/result_collection.rb +56 -3
  55. data/lib/riak/search/result_document.rb +71 -1
  56. data/lib/riak/search/schema.rb +6 -6
  57. data/lib/riak/secondary_index.rb +20 -12
  58. data/lib/riak/serializers.rb +0 -1
  59. data/lib/riak/util/escape.rb +2 -2
  60. data/lib/riak/util/translation.rb +1 -2
  61. data/lib/riak/version.rb +1 -1
  62. data/lib/riak/walk_spec.rb +67 -32
  63. data/riak-client.gemspec +5 -4
  64. data/spec/integration/riak/bucket_types_spec.rb +35 -5
  65. data/spec/integration/riak/conflict_resolution_spec.rb +1 -1
  66. data/spec/integration/riak/counters_spec.rb +1 -1
  67. data/spec/integration/riak/crdt/configuration_spec.rb +37 -0
  68. data/spec/integration/riak/crdt_search_spec.rb +176 -0
  69. data/spec/integration/riak/crdt_spec.rb +9 -33
  70. data/spec/integration/riak/crdt_validation/map_spec.rb +4 -4
  71. data/spec/integration/riak/crdt_validation/set_spec.rb +13 -13
  72. data/spec/integration/riak/preflist_spec.rb +31 -0
  73. data/spec/integration/riak/protobuffs/interrupted_request_spec.rb +2 -2
  74. data/spec/integration/riak/protobuffs_backends_spec.rb +9 -2
  75. data/spec/integration/riak/search_spec.rb +3 -3
  76. data/spec/integration/riak/secondary_index_spec.rb +3 -3
  77. data/spec/integration/riak/security_spec.rb +7 -7
  78. data/spec/integration/yokozuna/queries_spec.rb +1 -1
  79. data/spec/riak/beefcake_protobuffs_backend/bucket_properties_operator_spec.rb +9 -9
  80. data/spec/riak/beefcake_protobuffs_backend/crdt_operator_spec.rb +9 -9
  81. data/spec/riak/beefcake_protobuffs_backend/protocol_spec.rb +5 -5
  82. data/spec/riak/beefcake_protobuffs_backend_spec.rb +8 -8
  83. data/spec/riak/bucket_properties_spec.rb +27 -6
  84. data/spec/riak/bucket_spec.rb +5 -5
  85. data/spec/riak/bucket_type_spec.rb +21 -5
  86. data/spec/riak/bucket_typed/bucket_spec.rb +62 -0
  87. data/spec/riak/client_spec.rb +36 -18
  88. data/spec/riak/counter_spec.rb +4 -4
  89. data/spec/riak/crdt/counter_spec.rb +2 -2
  90. data/spec/riak/crdt/inner_flag_spec.rb +2 -2
  91. data/spec/riak/crdt/inner_map_spec.rb +4 -4
  92. data/spec/riak/crdt/inner_register_spec.rb +1 -1
  93. data/spec/riak/crdt/map_spec.rb +4 -4
  94. data/spec/riak/crdt/shared_examples.rb +5 -5
  95. data/spec/riak/crdt/typed_collection_spec.rb +21 -21
  96. data/spec/riak/map_reduce/filter_builder_spec.rb +2 -2
  97. data/spec/riak/map_reduce/phase_spec.rb +4 -4
  98. data/spec/riak/map_reduce_spec.rb +60 -42
  99. data/spec/riak/multiget_spec.rb +2 -2
  100. data/spec/riak/robject_spec.rb +55 -14
  101. data/spec/riak/search/index_spec.rb +12 -2
  102. data/spec/riak/search/query_spec.rb +4 -4
  103. data/spec/riak/search/result_collection_spec.rb +6 -4
  104. data/spec/riak/search/result_document_spec.rb +52 -9
  105. data/spec/riak/search/schema_spec.rb +2 -2
  106. data/spec/riak/secondary_index_spec.rb +6 -6
  107. data/spec/riak/serializers_spec.rb +27 -10
  108. data/spec/riak/walk_spec_spec.rb +10 -6
  109. data/spec/spec_helper.rb +11 -2
  110. data/spec/support/crdt_search_config.rb +112 -0
  111. data/spec/support/crdt_search_fixtures.rb +42 -0
  112. data/spec/support/search_config.rb +7 -5
  113. data/spec/support/search_corpus_setup.rb +2 -2
  114. data/spec/support/test_client.rb +2 -2
  115. data/spec/support/unified_backend_examples.rb +5 -5
  116. data/spec/support/version_filter.rb +5 -3
  117. data/spec/support/wait_until.rb +9 -3
  118. metadata +36 -13
  119. data/spec/riak/bucket_typed/bucket.rb +0 -43
@@ -8,7 +8,7 @@ module Riak::Search
8
8
  class ResultCollection
9
9
  # @return [Riak::Client]
10
10
  attr_reader :client
11
-
11
+
12
12
  # @return [Hash] the de-serialzed hash returned from Solr
13
13
  attr_reader :raw
14
14
 
@@ -52,13 +52,13 @@ module Riak::Search
52
52
  end
53
53
 
54
54
  # @param [Integer] index the index of the [Riak::RObject] to load and return
55
- # @return [Riak::RObject,NilClass] the found object, or nil if the index
55
+ # @return [Riak::RObject,NilClass] the found object, or nil if the index
56
56
  # is out of range
57
57
  def [](index)
58
58
  doc = docs[index]
59
59
  return nil if doc.nil?
60
60
 
61
- doc.robject
61
+ doc.object
62
62
  end
63
63
 
64
64
  # @return [Riak::RObject,NilClass] the first found object, or nil if the
@@ -67,6 +67,59 @@ module Riak::Search
67
67
  self[0]
68
68
  end
69
69
 
70
+ # Materializes and returns an array of objects from search results.
71
+ # You'll probably need to type inspect its members.
72
+ #
73
+ # @return [Array] materialized objects
74
+ def objects
75
+ @objects ||= docs.map do |doc|
76
+ next doc.crdt if doc.crdt?
77
+ doc.robject
78
+ end
79
+ end
80
+
81
+ # Materializes [Riak::RObject]s from any key-value results. Refuses to
82
+ # return RObjects for any CRDT results.
83
+ #
84
+ # @return [Array<Riak::RObject>] key-value objects
85
+ def robjects
86
+ @robjects ||= docs.reject(&:crdt?).map(&:robject)
87
+ end
88
+
89
+ # Materializes [Riak::Crdt::Base] subclasses from any CRDT results.
90
+ #
91
+ # @return [Array<Riak::Crdt::Base>] CRDT objects
92
+ def crdts
93
+ @crdts ||= docs.select(&:crdt?).map(&:crdt)
94
+ end
95
+
96
+ # Materializes [Riak::Crdt::Counter] results.
97
+ #
98
+ # @return [Array<Riak::Crdt::Counter] counter objects
99
+ def counters
100
+ @counters ||= docs.
101
+ select{ |d| d.type_class == Riak::Crdt::Counter }.
102
+ map(&:counter)
103
+ end
104
+
105
+ # Materializes [Riak::Crdt::Map] results.
106
+ #
107
+ # @return [Array<Riak::Crdt::Map] map objects
108
+ def maps
109
+ @maps ||= docs.
110
+ select{ |d| d.type_class == Riak::Crdt::Map }.
111
+ map(&:map)
112
+ end
113
+
114
+ # Materializes [Riak::Crdt::Set] results.
115
+ #
116
+ # @return [Array<Riak::Crdt::Set>]
117
+ def sets
118
+ @sets ||= docs.
119
+ select{ |d| d.type_class == Riak::Crdt::Set }.
120
+ map(&:set)
121
+ end
122
+
70
123
  # {Enumerable}-compatible iterator method. If a block is given, yields with
71
124
  # each {Riak::RObject} in the collection. If no block is given, returns an
72
125
  # {Enumerator} over each {Riak::RObject in the collection.
@@ -1,7 +1,9 @@
1
+ require 'riak/errors/crdt_error'
2
+
1
3
  module Riak::Search
2
4
 
3
5
  # A single document from a Riak Search 2 response. Materializes the document
4
- # fields into {Riak::BucketType}, {Riak::Bucket}, and {Riak::RObject}
6
+ # fields into {Riak::BucketType}, {Riak::Bucket}, and {Riak::RObject}
5
7
  # instances on demand.
6
8
  class ResultDocument
7
9
  # @return [Riak::Client]
@@ -41,6 +43,55 @@ module Riak::Search
41
43
  @score ||= Float(raw['score'])
42
44
  end
43
45
 
46
+ # Determining if the object is a CRDT or regular K-V object requires
47
+ # figuring out what data type the bucket type contains. If the bucket type
48
+ # has no data type, treat it as a regular K-V object.
49
+ #
50
+ # @return [Class] the class of the object referred to by the search result
51
+ def type_class
52
+ bucket_type.data_type_class || Riak::RObject
53
+ end
54
+
55
+ # @return [Boolean] if the object is a CRDT
56
+ def crdt?
57
+ type_class != Riak::RObject
58
+ end
59
+
60
+ # @raise [Riak::CrdtError::NotACrdt] if the result is not a CRDT
61
+ # @return [Riak::Crdt::Base] the materialized CRDT
62
+ def crdt
63
+ fail Riak::CrdtError::NotACrdt unless crdt?
64
+
65
+ type_class.new bucket, key, bucket_type
66
+ end
67
+
68
+ # If the result document describes a counter, return it.
69
+ #
70
+ # @return [Riak::Crdt::Counter]
71
+ # @raise [Riak::CrdtError::NotACrdt] if the result is not a CRDT
72
+ # @raise [Riak::CrdtError::UnexpectedDataType] if the CRDT is not a counter
73
+ def counter
74
+ return crdt if check_type_class Riak::Crdt::Counter
75
+ end
76
+
77
+ # If the result document describes a map, return it.
78
+ #
79
+ # @return [Riak::Crdt::Map]
80
+ # @raise [Riak::CrdtError::NotACrdt] if the result is not a CRDT
81
+ # @raise [Riak::CrdtError::UnexpectedDataType] if the CRDT is not a map
82
+ def map
83
+ return crdt if check_type_class Riak::Crdt::Map
84
+ end
85
+
86
+ # If the result document describes a set, return it.
87
+ #
88
+ # @return [Riak::Crdt::Set]
89
+ # @raise [Riak::CrdtError::NotACrdt] if the result is not a CRDT
90
+ # @raise [Riak::CrdtError::UnexpectedDataType] if the CRDT is not a set
91
+ def set
92
+ return crdt if check_type_class Riak::Crdt::Set
93
+ end
94
+
44
95
  # Provides access to other parts of the result document without
45
96
  # materializing them. Useful when querying non-default fields.
46
97
  #
@@ -53,7 +104,26 @@ module Riak::Search
53
104
  #
54
105
  # @return [Riak::RObject]
55
106
  def robject
107
+ if crdt?
108
+ fail Riak::SearchError::UnexpectedResultError.
109
+ new(Riak::RObject, type_class)
110
+ end
111
+
56
112
  @robject ||= bucket.get key
57
113
  end
114
+
115
+ # Returns an appropriate object, be it CRDT or K-V.
116
+ def object
117
+ return crdt if crdt?
118
+ robject
119
+ end
120
+
121
+ private
122
+
123
+ def check_type_class(klass)
124
+ return true if type_class == klass
125
+ fail Riak::CrdtError::NotACrdt if type_class == Riak::RObject
126
+ fail Riak::CrdtError::UnexpectedDataType.new(klass, type_class)
127
+ end
58
128
  end
59
129
  end
@@ -27,23 +27,24 @@ module Riak::Search
27
27
  def content
28
28
  schema_data.content
29
29
  end
30
-
30
+
31
31
  # @param [String] content the XML content of this schema
32
32
  # @raise [Riak::SearchError::SchemaExistsError] if a schema with the given
33
33
  # name already exists
34
34
  def create!(content)
35
- raise Riak::SearchError::SchemaExistsError.new name if exists?
35
+ fail Riak::SearchError::SchemaExistsError.new name if exists?
36
36
 
37
37
  @client.backend do |b|
38
38
  b.create_search_schema name, content
39
39
  end
40
-
40
+
41
41
  @schema_data = nil
42
42
 
43
43
  true
44
44
  end
45
45
 
46
46
  private
47
+
47
48
  def schema_data
48
49
  return @schema_data if defined?(@schema_data) && @schema_data
49
50
 
@@ -57,9 +58,8 @@ module Riak::Search
57
58
  return nil if e.not_found?
58
59
  raise e
59
60
  end
60
-
61
- return @schema_data = sd
61
+
62
+ @schema_data = sd
62
63
  end
63
64
  end
64
65
  end
65
-
@@ -2,6 +2,9 @@ require 'riak/index_collection'
2
2
  require 'riak/bucket_typed/bucket'
3
3
 
4
4
  module Riak
5
+ # {Riak::SecondaryIndex} provides an object-oriented interface to secondary
6
+ # index ("2i") functionality in Riak, available on the `memory` and `leveldb`
7
+ # backends.
5
8
  class SecondaryIndex
6
9
  include Util::Translation
7
10
  include Client::FeatureDetection
@@ -11,7 +14,7 @@ module Riak
11
14
  # @param [String] index the index name
12
15
  # @param [String,Integer,Range<String,Integer>] query
13
16
  # a single value or range of values to query for
14
- def initialize(bucket, index, query, options={})
17
+ def initialize(bucket, index, query, options = {})
15
18
  @bucket = bucket
16
19
  @client = @bucket.client
17
20
  @index = index
@@ -26,7 +29,7 @@ module Riak
26
29
  end
27
30
 
28
31
  def get_server_version
29
- @client.backend{|b| b.send :get_server_version }
32
+ @client.backend { |b| b.send :get_server_version }
30
33
  end
31
34
 
32
35
  # Get the array of matched keys
@@ -39,17 +42,17 @@ module Riak
39
42
 
40
43
  # Get the array of values
41
44
  def values
42
- @values ||= @bucket.get_many(self.keys).values
45
+ @values ||= @bucket.get_many(keys).values
43
46
  end
44
47
 
45
48
  # Get a new SecondaryIndex fetch for the next page
46
49
  def next_page
47
- raise t('index.no_next_page') unless keys.continuation
50
+ fail t('index.no_next_page') unless keys.continuation
48
51
 
49
- self.class.new(@bucket,
50
- @index,
51
- @query,
52
- @options.merge(:continuation => keys.continuation))
52
+ self.class.new(@bucket,
53
+ @index,
54
+ @query,
55
+ @options.merge(continuation: keys.continuation))
53
56
  end
54
57
 
55
58
  # Determine whether a SecondaryIndex fetch has a next page available
@@ -58,12 +61,17 @@ module Riak
58
61
  end
59
62
 
60
63
  private
64
+
61
65
  def validate_options
62
- raise t('index.pagination_not_available') if paginated? && !index_pagination?
63
- raise t('index.return_terms_not_available') if @options[:return_terms] && !index_return_terms?
64
- raise t('index.include_terms_is_wrong') if @options[:include_terms]
66
+ if paginated? && !index_pagination?
67
+ fail t('index.pagination_not_available')
68
+ end
69
+
70
+ if @options[:return_terms] && !index_return_terms?
71
+ fail t('index.return_terms_not_available')
72
+ end
65
73
 
66
- # raise t('index.streaming_not_available') if @options[:stream] && !index_streaming?
74
+ fail t('index.include_terms_is_wrong') if @options[:include_terms]
67
75
  end
68
76
 
69
77
  def paginated?
@@ -71,4 +71,3 @@ module Riak
71
71
  end
72
72
  end
73
73
  end
74
-
@@ -52,9 +52,9 @@ module Riak
52
52
  # @return [String] the escaped path segment
53
53
  def escape(bucket_or_key)
54
54
  if Riak.escaper == URI
55
- Riak.escaper.escape(bucket_or_key.to_s).gsub(" ", "%20").gsub("+", "%2B").gsub('/', "%2F")
55
+ Riak.escaper.escape(bucket_or_key.to_s).gsub(/[ \+\/]/, { " " => "%20", "+" => "%2B", "/" => "%2F"})
56
56
  else #CGI
57
- Riak.escaper.escape(bucket_or_key.to_s).gsub("+", "%20").gsub('/', "%2F")
57
+ Riak.escaper.escape(bucket_or_key.to_s).gsub(/[\+\/]/, { "+" => "%20", "/" => "%2F"})
58
58
  end
59
59
  end
60
60
 
@@ -10,10 +10,9 @@ module Riak
10
10
  end
11
11
 
12
12
  # Provides the translation for a given internationalized message
13
- def t(message, options={})
13
+ def t(message, options = {})
14
14
  I18n.t("#{i18n_scope}.#{message}", options)
15
15
  end
16
16
  end
17
17
  end
18
18
  end
19
-
data/lib/riak/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Riak
2
- VERSION = "2.2.0.pre1"
2
+ VERSION = "2.2.0"
3
3
  end
@@ -19,13 +19,16 @@ module Riak
19
19
  extend Util::Translation
20
20
  include Util::Escape
21
21
 
22
- # @return [String] The bucket followed links should be restricted to. "_" represents all buckets.
22
+ # @return [String] The bucket followed links should be restricted to.
23
+ # "_" represents all buckets.
23
24
  attr_accessor :bucket
24
25
 
25
- # @return [String] The "riaktag" or "rel" that followed links should be restricted to. "_" represents all tags.
26
+ # @return [String] The "riaktag" or "rel" that followed links should be
27
+ # restricted to. "_" represents all tags.
26
28
  attr_accessor :tag
27
29
 
28
- # @return [Boolean] Whether objects should be returned from this phase of link walking. Default is false.
30
+ # @return [Boolean] Whether objects should be returned from this phase
31
+ # of link walking. Default is false.
29
32
  attr_accessor :keep
30
33
 
31
34
  # Normalize a list of walk specs into WalkSpec objects.
@@ -33,18 +36,13 @@ module Riak
33
36
  params.flatten!
34
37
  specs = []
35
38
  while params.length > 0
36
- param = params.shift
37
- case param
39
+ case param = params.shift
38
40
  when Hash
39
41
  specs << new(param)
40
42
  when WalkSpec
41
43
  specs << param
42
44
  else
43
- if params.length >= 2
44
- specs << new(param, params.shift, params.shift)
45
- else
46
- raise ArgumentError, t("too_few_arguments", :params => params.inspect)
47
- end
45
+ normalize_long_params specs, params, param
48
46
  end
49
47
  end
50
48
  specs
@@ -54,30 +52,34 @@ module Riak
54
52
  # @overload initialize(hash)
55
53
  # Creates a walk-spec from a hash.
56
54
  # @param [Hash] hash options for the walk-spec
57
- # @option hash [String] :bucket ("_") the bucket the links should point to (default '_' is all)
58
- # @option hash [String] :tag ("_") the tag to filter links by (default '_' is all)
59
- # @option hash [Boolean] :keep (false) whether to return results from following this link specification
55
+ # @option hash [String] :bucket ("_") the bucket the links should point to
56
+ # (default '_' is all)
57
+ # @option hash [String] :tag ("_") the tag to filter links by (default '_'
58
+ # is all)
59
+ # @option hash [Boolean] :keep (false) whether to return results from
60
+ # following this link specification
60
61
  # @overload initialize(bucket, tag, keep)
61
62
  # Creates a walk-spec from a bucket-tag-result triple.
62
- # @param [String] bucket the bucket the links should point to (default '_' is all)
63
+ # @param [String] bucket the bucket the links should point to (default '_'
64
+ # is all)
63
65
  # @param [String] tag the tag to filter links by (default '_' is all)
64
- # @param [Boolean] keep whether to return results from following this link specification
66
+ # @param [Boolean] keep whether to return results from following this link
67
+ # specification
65
68
  # @see {Riak::RObject#walk}
66
69
  def initialize(*args)
67
70
  args.flatten!
68
71
  case args.size
69
72
  when 1
70
- hash = args.first
71
- raise ArgumentError, t("hash_type", :hash => hash.inspect) unless Hash === hash
72
- assign(hash[:bucket], hash[:tag], hash[:keep])
73
+ assign_from_hash args.first
73
74
  when 3
74
75
  assign(*args)
75
76
  else
76
- raise ArgumentError, t("wrong_argument_count_walk_spec")
77
+ fail ArgumentError, t('wrong_argument_count_walk_spec')
77
78
  end
78
79
  end
79
80
 
80
- # Converts the walk-spec into the form required by the link-walker resource URL
81
+ # Converts the walk-spec into the form required by the link-walker resource
82
+ # URL
81
83
  def to_s
82
84
  b = @bucket && escape(@bucket) || '_'
83
85
  t = @tag && escape(@tag) || '_'
@@ -85,26 +87,59 @@ module Riak
85
87
  end
86
88
 
87
89
  def ==(other)
88
- other.is_a?(WalkSpec) && other.bucket == bucket && other.tag == tag && other.keep == keep
90
+ return false unless other.is_a? WalkSpec
91
+ return false unless other.bucket == bucket
92
+ return false unless other.tag == tag
93
+ return false unless other.keep == keep
94
+ true
89
95
  end
90
96
 
91
97
  def ===(other)
92
- self == other || case other
93
- when WalkSpec
94
- other.keep == keep &&
95
- (bucket == "_" || bucket == other.bucket) &&
96
- (tag == "_" || tag == other.tag)
97
- when Link
98
- (bucket == "_" || bucket == other.url.split("/")[2]) &&
99
- (tag == "_" || tag == other.rel)
100
- end
98
+ return true if self == other
99
+ case other
100
+ when WalkSpec
101
+ walkspec_threequality(other)
102
+ when Link
103
+ link_threequality(other)
104
+ end
101
105
  end
102
106
 
103
107
  private
108
+
109
+ def self.normalize_long_params(specs, params, param)
110
+ if params.length >= 2
111
+ specs << new(param, params.shift, params.shift)
112
+ else
113
+ fail ArgumentError, t('too_few_arguments',
114
+ params: params.inspect)
115
+ end
116
+ end
117
+
118
+ def assign_from_hash(hash)
119
+ unless hash.is_a? Hash
120
+ fail ArgumentError, t('hash_type', hash: hash.inspect)
121
+ end
122
+
123
+ assign(hash[:bucket], hash[:tag], hash[:keep])
124
+ end
125
+
104
126
  def assign(bucket, tag, result)
105
- @bucket = bucket || "_"
106
- @tag = tag || "_"
127
+ @bucket = bucket || '_'
128
+ @tag = tag || '_'
107
129
  @keep = result || false
108
130
  end
131
+
132
+ def walkspec_threequality(other)
133
+ return false unless other.keep == keep
134
+ return false unless bucket == '_' || bucket == other.bucket
135
+ return false unless tag == '_' || tag == other.tag
136
+ true
137
+ end
138
+
139
+ def link_threequality(other)
140
+ return false unless bucket == '_' || bucket == other.url.split('/')[2]
141
+ return false unless tag == '_' || tag == other.rel
142
+ true
143
+ end
109
144
  end
110
145
  end