growwer 1.0.1 → 1.1.0

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +32 -27
  3. data/docs/AccountApi.md +0 -72
  4. data/docs/{OrdersApi.md → BuyerOrdersApi.md} +45 -45
  5. data/docs/{BuyerMoneyApi.md → BuyerWalletApi.md} +21 -21
  6. data/docs/CatalogueApi.md +6 -6
  7. data/docs/CreateSellerMediaRequest.md +38 -0
  8. data/docs/CreateSellerMediaRequestPostOffer.md +24 -0
  9. data/docs/Error.md +2 -2
  10. data/docs/FilesApi.md +1 -1
  11. data/docs/GetMe200ResponseCatalogue.md +6 -2
  12. data/docs/ManagedServicesApi.md +2 -2
  13. data/docs/ProjectCatalogueMediaAllOfProject.md +3 -1
  14. data/docs/ProjectCatalogueMediaAllOfProjectAiKeywords.md +20 -0
  15. data/docs/SellerAccountApi.md +79 -0
  16. data/docs/SellerMedia.md +2 -0
  17. data/docs/SellerMediaPostOffer.md +7 -1
  18. data/docs/SellerMediasApi.md +148 -5
  19. data/docs/{SellerMoneyApi.md → SellerWalletApi.md} +21 -21
  20. data/docs/UpdateSellerMediaRequest.md +2 -0
  21. data/docs/UpdateSellerMediaRequestPostOffer.md +8 -2
  22. data/lib/growwer/api/account_api.rb +0 -79
  23. data/lib/growwer/api/{orders_api.rb → buyer_orders_api.rb} +62 -62
  24. data/lib/growwer/api/{buyer_money_api.rb → buyer_wallet_api.rb} +24 -24
  25. data/lib/growwer/api/catalogue_api.rb +8 -8
  26. data/lib/growwer/api/files_api.rb +2 -2
  27. data/lib/growwer/api/managed_services_api.rb +4 -4
  28. data/lib/growwer/api/seller_account_api.rb +101 -0
  29. data/lib/growwer/api/seller_medias_api.rb +157 -4
  30. data/lib/growwer/api/{seller_money_api.rb → seller_wallet_api.rb} +24 -24
  31. data/lib/growwer/api_client.rb +1 -1
  32. data/lib/growwer/models/create_seller_media_request.rb +447 -0
  33. data/lib/growwer/models/create_seller_media_request_post_offer.rb +273 -0
  34. data/lib/growwer/models/error.rb +1 -1
  35. data/lib/growwer/models/get_me200_response_catalogue.rb +69 -5
  36. data/lib/growwer/models/project_catalogue_media_all_of_project.rb +34 -5
  37. data/lib/growwer/models/project_catalogue_media_all_of_project_ai_keywords.rb +211 -0
  38. data/lib/growwer/models/seller_media.rb +29 -1
  39. data/lib/growwer/models/seller_media_post_offer.rb +99 -1
  40. data/lib/growwer/models/update_seller_media_request.rb +44 -1
  41. data/lib/growwer/models/update_seller_media_request_post_offer.rb +96 -12
  42. data/lib/growwer/version.rb +1 -1
  43. data/lib/growwer.rb +7 -3
  44. data/spec/api/account_api_spec.rb +1 -14
  45. data/spec/api/{orders_api_spec.rb → buyer_orders_api_spec.rb} +13 -13
  46. data/spec/api/{buyer_money_api_spec.rb → buyer_wallet_api_spec.rb} +7 -7
  47. data/spec/api/seller_account_api_spec.rb +48 -0
  48. data/spec/api/{seller_money_api_spec.rb → seller_wallet_api_spec.rb} +7 -7
  49. data/spec/models/create_seller_media_request_post_offer_spec.rb +62 -0
  50. data/spec/models/create_seller_media_request_spec.rb +100 -0
  51. data/spec/models/project_catalogue_media_all_of_project_ai_keywords_spec.rb +46 -0
  52. metadata +30 -14
@@ -0,0 +1,273 @@
1
+ =begin
2
+ #Growwer API
3
+
4
+ #The Growwer customer API. Authenticate every call with an API key created on your profile page, sent as `Authorization: Bearer <key>`. A read key can call every GET operation; writes need a read and write key and an `Idempotency-Key` header. Idempotency: use a new `Idempotency-Key` (1 to 255 printable ASCII characters without spaces) for each operation and reuse it only to retry that same operation. Keys belong to your account, not to one API key. - A successful write is stored for 30 days: a retry with the same key and the same request replays its response with `Idempotent-Replayed: true` and does nothing again. - A refused write (4xx) has no effect and releases its key: once you fix the cause, retry with the same key and the write runs. - A failed write (5xx) may have taken effect, so it is never run again under its key: a retry gets 409 `idempotency_outcome_unknown`. Check whether it took effect before retrying with a new key. - A retry while the first request is still running gets 409 `idempotency_request_in_progress`. A request still running after 5 minutes is treated as failed: a retry gets 409 `idempotency_outcome_unknown`. - The same key with a different request (method, path, query or body) gets 409 `idempotency_key_reused`. A write without the header gets 400 `idempotency_key_required`. Every response carries `X-Request-Id`. Errors share one shape, `{code, message, requestId, details?}`. Money is integer cents plus a currency code and dates are ISO-8601.
5
+
6
+ The version of the OpenAPI document: 1
7
+ Contact: support@growwer.com
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.24.0
10
+
11
+ =end
12
+
13
+ require 'date'
14
+ require 'time'
15
+
16
+ module Growwer
17
+ class CreateSellerMediaRequestPostOffer < ApiModelBase
18
+ attr_accessor :pricing_options
19
+
20
+ attr_accessor :maximum_links
21
+
22
+ attr_accessor :advertising_labelling_policy
23
+
24
+ attr_accessor :promoted_social_networks
25
+
26
+ class EnumAttributeValidator
27
+ attr_reader :datatype
28
+ attr_reader :allowable_values
29
+
30
+ def initialize(datatype, allowable_values)
31
+ @allowable_values = allowable_values.map do |value|
32
+ case datatype.to_s
33
+ when /Integer/i
34
+ value.to_i
35
+ when /Float/i
36
+ value.to_f
37
+ else
38
+ value
39
+ end
40
+ end
41
+ end
42
+
43
+ def valid?(value)
44
+ !value || allowable_values.include?(value)
45
+ end
46
+ end
47
+
48
+ # Attribute mapping from ruby-style variable name to JSON key.
49
+ def self.attribute_map
50
+ {
51
+ :'pricing_options' => :'pricingOptions',
52
+ :'maximum_links' => :'maximumLinks',
53
+ :'advertising_labelling_policy' => :'advertisingLabellingPolicy',
54
+ :'promoted_social_networks' => :'promotedSocialNetworks'
55
+ }
56
+ end
57
+
58
+ # Returns attribute mapping this model knows about
59
+ def self.acceptable_attribute_map
60
+ attribute_map
61
+ end
62
+
63
+ # Returns all the JSON keys this model knows about
64
+ def self.acceptable_attributes
65
+ acceptable_attribute_map.values
66
+ end
67
+
68
+ # Attribute type mapping.
69
+ def self.openapi_types
70
+ {
71
+ :'pricing_options' => :'Array<UpdateSellerMediaRequestPostOfferPricingOptionsInner>',
72
+ :'maximum_links' => :'Integer',
73
+ :'advertising_labelling_policy' => :'String',
74
+ :'promoted_social_networks' => :'Array<String>'
75
+ }
76
+ end
77
+
78
+ # List of attributes with nullable: true
79
+ def self.openapi_nullable
80
+ Set.new([
81
+ ])
82
+ end
83
+
84
+ # Initializes the object
85
+ # @param [Hash] attributes Model attributes in the form of hash
86
+ def initialize(attributes = {})
87
+ if (!attributes.is_a?(Hash))
88
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Growwer::CreateSellerMediaRequestPostOffer` initialize method"
89
+ end
90
+
91
+ # check to see if the attribute exists and convert string to symbol for hash key
92
+ acceptable_attribute_map = self.class.acceptable_attribute_map
93
+ attributes = attributes.each_with_object({}) { |(k, v), h|
94
+ if (!acceptable_attribute_map.key?(k.to_sym))
95
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Growwer::CreateSellerMediaRequestPostOffer`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
96
+ end
97
+ h[k.to_sym] = v
98
+ }
99
+
100
+ if attributes.key?(:'pricing_options')
101
+ if (value = attributes[:'pricing_options']).is_a?(Array)
102
+ self.pricing_options = value
103
+ end
104
+ else
105
+ self.pricing_options = nil
106
+ end
107
+
108
+ if attributes.key?(:'maximum_links')
109
+ self.maximum_links = attributes[:'maximum_links']
110
+ else
111
+ self.maximum_links = nil
112
+ end
113
+
114
+ if attributes.key?(:'advertising_labelling_policy')
115
+ self.advertising_labelling_policy = attributes[:'advertising_labelling_policy']
116
+ end
117
+
118
+ if attributes.key?(:'promoted_social_networks')
119
+ if (value = attributes[:'promoted_social_networks']).is_a?(Array)
120
+ self.promoted_social_networks = value
121
+ end
122
+ end
123
+ end
124
+
125
+ # Show invalid properties with the reasons. Usually used together with valid?
126
+ # @return Array for valid properties with the reasons
127
+ def list_invalid_properties
128
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
129
+ invalid_properties = Array.new
130
+ if @pricing_options.nil?
131
+ invalid_properties.push('invalid value for "pricing_options", pricing_options cannot be nil.')
132
+ end
133
+
134
+ if @pricing_options.length < 1
135
+ invalid_properties.push('invalid value for "pricing_options", number of items must be greater than or equal to 1.')
136
+ end
137
+
138
+ if @maximum_links.nil?
139
+ invalid_properties.push('invalid value for "maximum_links", maximum_links cannot be nil.')
140
+ end
141
+
142
+ if @maximum_links > 4
143
+ invalid_properties.push('invalid value for "maximum_links", must be smaller than or equal to 4.')
144
+ end
145
+
146
+ if @maximum_links < 1
147
+ invalid_properties.push('invalid value for "maximum_links", must be greater than or equal to 1.')
148
+ end
149
+
150
+ invalid_properties
151
+ end
152
+
153
+ # Check to see if the all the properties in the model are valid
154
+ # @return true if the model is valid
155
+ def valid?
156
+ warn '[DEPRECATED] the `valid?` method is obsolete'
157
+ return false if @pricing_options.nil?
158
+ return false if @pricing_options.length < 1
159
+ return false if @maximum_links.nil?
160
+ return false if @maximum_links > 4
161
+ return false if @maximum_links < 1
162
+ advertising_labelling_policy_validator = EnumAttributeValidator.new('String', ["always", "never"])
163
+ return false unless advertising_labelling_policy_validator.valid?(@advertising_labelling_policy)
164
+ true
165
+ end
166
+
167
+ # Custom attribute writer method with validation
168
+ # @param [Object] pricing_options Value to be assigned
169
+ def pricing_options=(pricing_options)
170
+ if pricing_options.nil?
171
+ fail ArgumentError, 'pricing_options cannot be nil'
172
+ end
173
+
174
+ if pricing_options.length < 1
175
+ fail ArgumentError, 'invalid value for "pricing_options", number of items must be greater than or equal to 1.'
176
+ end
177
+
178
+ @pricing_options = pricing_options
179
+ end
180
+
181
+ # Custom attribute writer method with validation
182
+ # @param [Object] maximum_links Value to be assigned
183
+ def maximum_links=(maximum_links)
184
+ if maximum_links.nil?
185
+ fail ArgumentError, 'maximum_links cannot be nil'
186
+ end
187
+
188
+ if maximum_links > 4
189
+ fail ArgumentError, 'invalid value for "maximum_links", must be smaller than or equal to 4.'
190
+ end
191
+
192
+ if maximum_links < 1
193
+ fail ArgumentError, 'invalid value for "maximum_links", must be greater than or equal to 1.'
194
+ end
195
+
196
+ @maximum_links = maximum_links
197
+ end
198
+
199
+ # Custom attribute writer method checking allowed values (enum).
200
+ # @param [Object] advertising_labelling_policy Object to be assigned
201
+ def advertising_labelling_policy=(advertising_labelling_policy)
202
+ validator = EnumAttributeValidator.new('String', ["always", "never"])
203
+ unless validator.valid?(advertising_labelling_policy)
204
+ fail ArgumentError, "invalid value for \"advertising_labelling_policy\", must be one of #{validator.allowable_values}."
205
+ end
206
+ @advertising_labelling_policy = advertising_labelling_policy
207
+ end
208
+
209
+ # Checks equality by comparing each attribute.
210
+ # @param [Object] Object to be compared
211
+ def ==(o)
212
+ return true if self.equal?(o)
213
+ self.class == o.class &&
214
+ pricing_options == o.pricing_options &&
215
+ maximum_links == o.maximum_links &&
216
+ advertising_labelling_policy == o.advertising_labelling_policy &&
217
+ promoted_social_networks == o.promoted_social_networks
218
+ end
219
+
220
+ # @see the `==` method
221
+ # @param [Object] Object to be compared
222
+ def eql?(o)
223
+ self == o
224
+ end
225
+
226
+ # Calculates hash code according to all attributes.
227
+ # @return [Integer] Hash code
228
+ def hash
229
+ [pricing_options, maximum_links, advertising_labelling_policy, promoted_social_networks].hash
230
+ end
231
+
232
+ # Builds the object from hash
233
+ # @param [Hash] attributes Model attributes in the form of hash
234
+ # @return [Object] Returns the model itself
235
+ def self.build_from_hash(attributes)
236
+ return nil unless attributes.is_a?(Hash)
237
+ attributes = attributes.transform_keys(&:to_sym)
238
+ transformed_hash = {}
239
+ openapi_types.each_pair do |key, type|
240
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
241
+ transformed_hash["#{key}"] = nil
242
+ elsif type =~ /\AArray<(.*)>/i
243
+ # check to ensure the input is an array given that the attribute
244
+ # is documented as an array but the input is not
245
+ if attributes[attribute_map[key]].is_a?(Array)
246
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
247
+ end
248
+ elsif !attributes[attribute_map[key]].nil?
249
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
250
+ end
251
+ end
252
+ new(transformed_hash)
253
+ end
254
+
255
+ # Returns the object in the form of hash
256
+ # @return [Hash] Returns the object in the form of hash
257
+ def to_hash
258
+ hash = {}
259
+ self.class.attribute_map.each_pair do |attr, param|
260
+ value = self.send(attr)
261
+ if value.nil?
262
+ is_nullable = self.class.openapi_nullable.include?(attr)
263
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
264
+ end
265
+
266
+ hash[param] = _to_hash(value)
267
+ end
268
+ hash
269
+ end
270
+
271
+ end
272
+
273
+ end
@@ -21,7 +21,7 @@ module Growwer
21
21
  # A human-readable explanation. It may change.
22
22
  attr_accessor :message
23
23
 
24
- # The X-Request-Id of the call. Quote it when reporting a problem.
24
+ # UUID v4 generated per request, or the `X-Request-Id` you sent. Quote it when reporting a problem.
25
25
  attr_accessor :request_id
26
26
 
27
27
  # Extra data on some errors, such as the invalid fields of a validation_failed error.
@@ -15,13 +15,21 @@ require 'time'
15
15
 
16
16
  module Growwer
17
17
  class GetMe200ResponseCatalogue < ApiModelBase
18
- # Whether the catalogue operations (tag Catalogue: searching a project's medias, flash offers and media details) are open to you. They are open to accounts with recent completed orders, as buyer or as seller; until then they answer 403 `catalogue_requires_recent_order`. A media you have ordered is always readable with GET /medias/{id}.
18
+ # Whether you have the full daily cap of the catalogue operations (tag Catalogue: searching a project's medias, flash offers and media details). It is open to accounts with recent completed orders, as buyer or as seller; until then the catalogue answers within a smaller daily trial quota, which rises to the full cap after a completed order. A media you have ordered is always readable with GET /medias/{id} and counts against neither.
19
19
  attr_accessor :eligible
20
20
 
21
+ # How many distinct medias the catalogue operations show you per UTC day: the full cap when `eligible` is true, the trial quota otherwise. 0 means the catalogue answers 403 `catalogue_requires_recent_order` until you have a recent completed order.
22
+ attr_accessor :daily_cap
23
+
24
+ # How many new distinct medias still fit in `dailyCap` today (UTC). Medias you have already seen today are free. Null when the count cannot be read right now; the catalogue operations then answer 503 until it can.
25
+ attr_accessor :remaining_today
26
+
21
27
  # Attribute mapping from ruby-style variable name to JSON key.
22
28
  def self.attribute_map
23
29
  {
24
- :'eligible' => :'eligible'
30
+ :'eligible' => :'eligible',
31
+ :'daily_cap' => :'dailyCap',
32
+ :'remaining_today' => :'remainingToday'
25
33
  }
26
34
  end
27
35
 
@@ -38,13 +46,16 @@ module Growwer
38
46
  # Attribute type mapping.
39
47
  def self.openapi_types
40
48
  {
41
- :'eligible' => :'Boolean'
49
+ :'eligible' => :'Boolean',
50
+ :'daily_cap' => :'Integer',
51
+ :'remaining_today' => :'Integer'
42
52
  }
43
53
  end
44
54
 
45
55
  # List of attributes with nullable: true
46
56
  def self.openapi_nullable
47
57
  Set.new([
58
+ :'remaining_today'
48
59
  ])
49
60
  end
50
61
 
@@ -69,6 +80,18 @@ module Growwer
69
80
  else
70
81
  self.eligible = nil
71
82
  end
83
+
84
+ if attributes.key?(:'daily_cap')
85
+ self.daily_cap = attributes[:'daily_cap']
86
+ else
87
+ self.daily_cap = nil
88
+ end
89
+
90
+ if attributes.key?(:'remaining_today')
91
+ self.remaining_today = attributes[:'remaining_today']
92
+ else
93
+ self.remaining_today = nil
94
+ end
72
95
  end
73
96
 
74
97
  # Show invalid properties with the reasons. Usually used together with valid?
@@ -80,6 +103,18 @@ module Growwer
80
103
  invalid_properties.push('invalid value for "eligible", eligible cannot be nil.')
81
104
  end
82
105
 
106
+ if @daily_cap.nil?
107
+ invalid_properties.push('invalid value for "daily_cap", daily_cap cannot be nil.')
108
+ end
109
+
110
+ if @daily_cap < 0
111
+ invalid_properties.push('invalid value for "daily_cap", must be greater than or equal to 0.')
112
+ end
113
+
114
+ if @remaining_today < 0
115
+ invalid_properties.push('invalid value for "remaining_today", must be greater than or equal to 0.')
116
+ end
117
+
83
118
  invalid_properties
84
119
  end
85
120
 
@@ -88,6 +123,9 @@ module Growwer
88
123
  def valid?
89
124
  warn '[DEPRECATED] the `valid?` method is obsolete'
90
125
  return false if @eligible.nil?
126
+ return false if @daily_cap.nil?
127
+ return false if @daily_cap < 0
128
+ return false if @remaining_today < 0
91
129
  true
92
130
  end
93
131
 
@@ -101,12 +139,38 @@ module Growwer
101
139
  @eligible = eligible
102
140
  end
103
141
 
142
+ # Custom attribute writer method with validation
143
+ # @param [Object] daily_cap Value to be assigned
144
+ def daily_cap=(daily_cap)
145
+ if daily_cap.nil?
146
+ fail ArgumentError, 'daily_cap cannot be nil'
147
+ end
148
+
149
+ if daily_cap < 0
150
+ fail ArgumentError, 'invalid value for "daily_cap", must be greater than or equal to 0.'
151
+ end
152
+
153
+ @daily_cap = daily_cap
154
+ end
155
+
156
+ # Custom attribute writer method with validation
157
+ # @param [Object] remaining_today Value to be assigned
158
+ def remaining_today=(remaining_today)
159
+ if !remaining_today.nil? && remaining_today < 0
160
+ fail ArgumentError, 'invalid value for "remaining_today", must be greater than or equal to 0.'
161
+ end
162
+
163
+ @remaining_today = remaining_today
164
+ end
165
+
104
166
  # Checks equality by comparing each attribute.
105
167
  # @param [Object] Object to be compared
106
168
  def ==(o)
107
169
  return true if self.equal?(o)
108
170
  self.class == o.class &&
109
- eligible == o.eligible
171
+ eligible == o.eligible &&
172
+ daily_cap == o.daily_cap &&
173
+ remaining_today == o.remaining_today
110
174
  end
111
175
 
112
176
  # @see the `==` method
@@ -118,7 +182,7 @@ module Growwer
118
182
  # Calculates hash code according to all attributes.
119
183
  # @return [Integer] Hash code
120
184
  def hash
121
- [eligible].hash
185
+ [eligible, daily_cap, remaining_today].hash
122
186
  end
123
187
 
124
188
  # Builds the object from hash
@@ -14,7 +14,7 @@ require 'date'
14
14
  require 'time'
15
15
 
16
16
  module Growwer
17
- # The media's state in the project.
17
+ # The media's state in the project, and the AI keywords the web lists for the media in this project.
18
18
  class ProjectCatalogueMediaAllOfProject < ApiModelBase
19
19
  attr_accessor :favourite
20
20
 
@@ -31,6 +31,9 @@ module Growwer
31
31
  # Growwer matched the media with the project recently.
32
32
  attr_accessor :new
33
33
 
34
+ # The AI keywords the web lists for the media in this project, one entry per keyword of each stored source, empty when there are none. Keywords an AI answer gave without referencing any domain may be listed for every media of the project, so an entry does not mean that the engine cited the media. Entries keep their order within an engine, and a keyword listed by two engines appears once for each; the order of the engines is not promised.
35
+ attr_accessor :ai_keywords
36
+
34
37
  # Attribute mapping from ruby-style variable name to JSON key.
35
38
  def self.attribute_map
36
39
  {
@@ -39,7 +42,8 @@ module Growwer
39
42
  :'requested' => :'requested',
40
43
  :'recommended' => :'recommended',
41
44
  :'bought' => :'bought',
42
- :'new' => :'new'
45
+ :'new' => :'new',
46
+ :'ai_keywords' => :'aiKeywords'
43
47
  }
44
48
  end
45
49
 
@@ -61,7 +65,8 @@ module Growwer
61
65
  :'requested' => :'Boolean',
62
66
  :'recommended' => :'Boolean',
63
67
  :'bought' => :'Boolean',
64
- :'new' => :'Boolean'
68
+ :'new' => :'Boolean',
69
+ :'ai_keywords' => :'Array<ProjectCatalogueMediaAllOfProjectAiKeywords>'
65
70
  }
66
71
  end
67
72
 
@@ -122,6 +127,14 @@ module Growwer
122
127
  else
123
128
  self.new = nil
124
129
  end
130
+
131
+ if attributes.key?(:'ai_keywords')
132
+ if (value = attributes[:'ai_keywords']).is_a?(Array)
133
+ self.ai_keywords = value
134
+ end
135
+ else
136
+ self.ai_keywords = nil
137
+ end
125
138
  end
126
139
 
127
140
  # Show invalid properties with the reasons. Usually used together with valid?
@@ -153,6 +166,10 @@ module Growwer
153
166
  invalid_properties.push('invalid value for "new", new cannot be nil.')
154
167
  end
155
168
 
169
+ if @ai_keywords.nil?
170
+ invalid_properties.push('invalid value for "ai_keywords", ai_keywords cannot be nil.')
171
+ end
172
+
156
173
  invalid_properties
157
174
  end
158
175
 
@@ -166,6 +183,7 @@ module Growwer
166
183
  return false if @recommended.nil?
167
184
  return false if @bought.nil?
168
185
  return false if @new.nil?
186
+ return false if @ai_keywords.nil?
169
187
  true
170
188
  end
171
189
 
@@ -229,6 +247,16 @@ module Growwer
229
247
  @new = new
230
248
  end
231
249
 
250
+ # Custom attribute writer method with validation
251
+ # @param [Object] ai_keywords Value to be assigned
252
+ def ai_keywords=(ai_keywords)
253
+ if ai_keywords.nil?
254
+ fail ArgumentError, 'ai_keywords cannot be nil'
255
+ end
256
+
257
+ @ai_keywords = ai_keywords
258
+ end
259
+
232
260
  # Checks equality by comparing each attribute.
233
261
  # @param [Object] Object to be compared
234
262
  def ==(o)
@@ -239,7 +267,8 @@ module Growwer
239
267
  requested == o.requested &&
240
268
  recommended == o.recommended &&
241
269
  bought == o.bought &&
242
- new == o.new
270
+ new == o.new &&
271
+ ai_keywords == o.ai_keywords
243
272
  end
244
273
 
245
274
  # @see the `==` method
@@ -251,7 +280,7 @@ module Growwer
251
280
  # Calculates hash code according to all attributes.
252
281
  # @return [Integer] Hash code
253
282
  def hash
254
- [favourite, discarded, requested, recommended, bought, new].hash
283
+ [favourite, discarded, requested, recommended, bought, new, ai_keywords].hash
255
284
  end
256
285
 
257
286
  # Builds the object from hash