rescue_groups 0.0.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.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +90 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +226 -0
  8. data/Rakefile +30 -0
  9. data/config/config.rb +26 -0
  10. data/config/initializer.rb +15 -0
  11. data/docs/animal_field.md +138 -0
  12. data/docs/event_field.md +20 -0
  13. data/docs/organization_field.md +25 -0
  14. data/fields/animal_field.rb +152 -0
  15. data/fields/event_field.rb +35 -0
  16. data/fields/organization_field.rb +40 -0
  17. data/fields/picture_field.rb +30 -0
  18. data/lib/api_client.rb +29 -0
  19. data/lib/queryable.rb +79 -0
  20. data/lib/relationable.rb +76 -0
  21. data/lib/remote_client.rb +29 -0
  22. data/lib/remote_model.rb +47 -0
  23. data/lib/requests/find.rb +29 -0
  24. data/lib/requests/invalid_client.rb +1 -0
  25. data/lib/requests/where.rb +94 -0
  26. data/lib/response.rb +48 -0
  27. data/models/animal.rb +57 -0
  28. data/models/event.rb +41 -0
  29. data/models/organization.rb +41 -0
  30. data/models/picture.rb +26 -0
  31. data/rescue_groups.gemspec +28 -0
  32. data/rescue_groups.rb +27 -0
  33. data/search/animal_search.rb +15 -0
  34. data/search/base_search.rb +72 -0
  35. data/search/event_search.rb +15 -0
  36. data/search/filter.rb +49 -0
  37. data/search/organization_search.rb +15 -0
  38. data/spec/fixtures/animal/find.json +1 -0
  39. data/spec/fixtures/animal/where.json +1 -0
  40. data/spec/fixtures/error.json +20 -0
  41. data/spec/fixtures/event/find.json +1 -0
  42. data/spec/fixtures/event/where.json +1 -0
  43. data/spec/fixtures/organization/find.json +1 -0
  44. data/spec/fixtures/organization/where.json +1 -0
  45. data/spec/fixtures/test_constants.rb +12 -0
  46. data/spec/integration/animal_spec.rb +55 -0
  47. data/spec/integration/event_spec.rb +33 -0
  48. data/spec/integration/organization_spec.rb +35 -0
  49. data/spec/lib/queryable_spec.rb +257 -0
  50. data/spec/lib/relationable_spec.rb +113 -0
  51. data/spec/lib/remote_client_spec.rb +27 -0
  52. data/spec/lib/requests/find_spec.rb +97 -0
  53. data/spec/lib/requests/where_spec.rb +267 -0
  54. data/spec/lib/response_spec.rb +99 -0
  55. data/spec/models/animal_spec.rb +131 -0
  56. data/spec/models/event_spec.rb +105 -0
  57. data/spec/models/organization_spec.rb +112 -0
  58. data/spec/models/picture_spec.rb +87 -0
  59. data/spec/search/animal_search_spec.rb +8 -0
  60. data/spec/search/event_search_spec.rb +8 -0
  61. data/spec/search/filter_spec.rb +39 -0
  62. data/spec/search/organization_search_spec.rb +8 -0
  63. data/spec/spec_helper.rb +340 -0
  64. data/spec/support/model_spec.rb +47 -0
  65. data/spec/support/searchable_spec.rb +15 -0
  66. data/support/animal_mock.rb +215 -0
  67. data/support/base_mock.rb +44 -0
  68. data/support/event_mock.rb +48 -0
  69. data/support/organization_mock.rb +53 -0
  70. data/version.rb +3 -0
  71. metadata +242 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2dfbb921748d1d79a6af5e575737467216dc5c1d
4
+ data.tar.gz: 222eb29ce4facded94bd669d84c729b49c55848e
5
+ SHA512:
6
+ metadata.gz: bb6ad7ded14addd702c324deea89983dd4743f5217154634de9fca526f77ffadcb26cb85a4dedca08d5ee7d3d38b5b2886666c7f94cfe2cad6f30d1f6e5b4513
7
+ data.tar.gz: b4f9fb29d5c2306aecf9e4c971129e8b466206a630bab0d72581e9cd56f2cadab0fd39c7b4a9733202350226debd12d08ebc2926a0eef60adfa0a2783a140fec
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .ruby-*
2
+ Guardfile
3
+ scripts/*
4
+ .ENV
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm: 2.2.1
3
+ script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,90 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rescue_groups (0.0.1)
5
+ httparty (>= 0.13.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.3.8)
11
+ celluloid (0.16.0)
12
+ timers (~> 4.0.0)
13
+ coderay (1.1.0)
14
+ crack (0.4.2)
15
+ safe_yaml (~> 1.0.0)
16
+ diff-lcs (1.2.5)
17
+ ffi (1.9.8)
18
+ formatador (0.2.5)
19
+ guard (2.12.5)
20
+ formatador (>= 0.2.4)
21
+ listen (~> 2.7)
22
+ lumberjack (~> 1.0)
23
+ nenv (~> 0.1)
24
+ notiffany (~> 0.0)
25
+ pry (>= 0.9.12)
26
+ shellany (~> 0.0)
27
+ thor (>= 0.18.1)
28
+ guard-compat (1.2.1)
29
+ guard-rspec (4.5.0)
30
+ guard (~> 2.1)
31
+ guard-compat (~> 1.1)
32
+ rspec (>= 2.99.0, < 4.0)
33
+ hitimes (1.2.2)
34
+ httparty (0.13.3)
35
+ json (~> 1.8)
36
+ multi_xml (>= 0.5.2)
37
+ json (1.8.3)
38
+ listen (2.10.0)
39
+ celluloid (~> 0.16.0)
40
+ rb-fsevent (>= 0.9.3)
41
+ rb-inotify (>= 0.9)
42
+ lumberjack (1.0.9)
43
+ method_source (0.8.2)
44
+ multi_xml (0.5.5)
45
+ nenv (0.2.0)
46
+ notiffany (0.0.6)
47
+ nenv (~> 0.1)
48
+ shellany (~> 0.0)
49
+ pry (0.10.1)
50
+ coderay (~> 1.1.0)
51
+ method_source (~> 0.8.1)
52
+ slop (~> 3.4)
53
+ rake (10.4.2)
54
+ rb-fsevent (0.9.4)
55
+ rb-inotify (0.9.5)
56
+ ffi (>= 0.5.0)
57
+ rspec (3.2.0)
58
+ rspec-core (~> 3.2.0)
59
+ rspec-expectations (~> 3.2.0)
60
+ rspec-mocks (~> 3.2.0)
61
+ rspec-core (3.2.3)
62
+ rspec-support (~> 3.2.0)
63
+ rspec-expectations (3.2.1)
64
+ diff-lcs (>= 1.2.0, < 2.0)
65
+ rspec-support (~> 3.2.0)
66
+ rspec-mocks (3.2.1)
67
+ diff-lcs (>= 1.2.0, < 2.0)
68
+ rspec-support (~> 3.2.0)
69
+ rspec-support (3.2.2)
70
+ safe_yaml (1.0.4)
71
+ shellany (0.0.1)
72
+ slop (3.6.0)
73
+ thor (0.19.1)
74
+ timers (4.0.1)
75
+ hitimes
76
+ webmock (1.21.0)
77
+ addressable (>= 2.3.6)
78
+ crack (>= 0.3.2)
79
+
80
+ PLATFORMS
81
+ ruby
82
+
83
+ DEPENDENCIES
84
+ bundler (~> 1.3)
85
+ guard-rspec
86
+ pry
87
+ rake
88
+ rescue_groups!
89
+ rspec
90
+ webmock
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Jake Yesbeck
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,226 @@
1
+ ## Rescue Groups API
2
+
3
+ Wrapper for RescueGroups.org API
4
+
5
+ ![Build Status](https://travis-ci.org/yez/rescue_groups.svg?branch=master)
6
+
7
+ # Purpose
8
+
9
+ Ruby wrapper for the [HTTP RescueGroups API](https://userguide.rescuegroups.org/display/APIDG/HTTP+API). This is a read only gem that provides basic functionality needed to read from RescueGroups.
10
+
11
+ # Installation
12
+
13
+ In your project's Gemfile:
14
+
15
+ `gem 'rescue_groups'`
16
+
17
+ In a Rails application, all files will be included by default.
18
+
19
+ In other Ruby applications, call `require 'rescue_groups'` where RescueGroups is needed.
20
+
21
+ # Configuration
22
+
23
+ An API key is required to use the RescueGroups API. An `ENV` variable named `'RESCUE_GROUPS_API_KEY'` is automatically accessed on initialization.
24
+
25
+ Otherwise, add the following block to an initializer in your application:
26
+
27
+ ```ruby
28
+ RescueGroups.configuration do |config|
29
+ config.apikey = 'your api key'
30
+ end
31
+ ```
32
+
33
+ # Searching
34
+
35
+ Two methods, `find` and `where`, are used to request three resources: `Animal`, `Organization`, and `Event`.
36
+
37
+ ### `find`
38
+
39
+ The find method accepts one to multiple ids and returns one or many results, respectively.
40
+
41
+ **One ID**
42
+
43
+ ```ruby
44
+ Animal.find(1)
45
+ #=> <Animal id: 1, name: 'Fluffy' ...>
46
+ ```
47
+
48
+ **Multiple IDs**
49
+
50
+ ```ruby
51
+ Animal.find([20, 30, 40])
52
+ #=> [<Animal id: 20, name: 'Mittens' ...>, <Animal id: 30, name: 'Mr. Doom' ...>, <Animal id: 40, name: 'CatDog' ...>]
53
+ ```
54
+
55
+ **404 Response**
56
+
57
+ If the object(s) is not found, an exception is raised `"Unable to find Animal with id: -1"
58
+
59
+ ### `where`
60
+
61
+ The where method accepts a hash of attributes and finds animals that match all the criteria. If the returned list of objects is less than the count found by the server, a configuration variable can be added to make additional requests are automatically made with the same search criteria until all results returned.
62
+
63
+ To enable this feature, set the config in an initializer like:
64
+
65
+ ```ruby
66
+ RescueGroups.configuration do |config|
67
+ config.load_all_results = true
68
+ end
69
+ ```
70
+
71
+ A full set of fields are defined for [`Animals`](docs/animal_field.md), [`Organizations`](docs/organization_field.md), and [`Events`](docs/event_field.md)
72
+
73
+ **One attribute**
74
+
75
+ ```ruby
76
+ Organization.where(name: 'Pets-R-Us')
77
+ # => [<Organization id: 1, name: 'Pets-R-Us' ...>]
78
+ ```
79
+
80
+ To search multiple values on the same attribute, use an array:
81
+
82
+ ```ruby
83
+ Animal.where(color: ['black', 'brown'])
84
+ # => [<Animal id: 1, color: 'black' ..>, <Animal id: 5, color: 'brown' ..>]
85
+ ```
86
+
87
+ **Multiple attributes**
88
+
89
+ ```ruby
90
+ Organization.where(name: 'Big Bobs Pets', city: 'Kansas City')
91
+ # => [<Organization id: 42, name: 'Big Bobs Pets', city: 'Kansas City' ...>]
92
+ ```
93
+
94
+ **Complex attributes**
95
+
96
+ For more sophisticated searches, the following attributes are provided:
97
+
98
+ * equal
99
+ * not_equal
100
+ * less_than
101
+ * less_than_or_equal
102
+ * greater_than
103
+ * greater_than_or_equal
104
+ * contains
105
+ * not_contain
106
+ * blank
107
+ * not_blank
108
+
109
+ These attributes may be used in addition to others in a single `where` call or alone
110
+
111
+ ```ruby
112
+ Animal.where(general_age: { less_than: 5})
113
+ # => [<Animal id: 1, age: 2 ..>, <Animal id: 3, age: 1 ..>]
114
+
115
+ Organization.where(name: { contains: 'shelter'}, location: 90210)
116
+ # => [<Organization id: 1, name: 'Big Animal Shelter', location: 90210 ...>, <Organization id: 2, name: 'Small Animal Shelter', location: 90210 ...>,]
117
+ ```
118
+
119
+ **No results**
120
+
121
+ ```ruby
122
+ Organization.where(name: 'Bad Dogs R Us')
123
+ # => []
124
+ ```
125
+ # Relationships
126
+
127
+ `Animals`, `Organizations`, and `Events` have relationships to one another. `Animals` have a single associated `Organization`, as do `Events`. `Organizations` have 1 or many `Events` and `Animals`.
128
+
129
+ Calling a relationship that does not exist in memory will automatically fetch the associated object.
130
+
131
+ ```ruby
132
+ organization = Organization.find(1)
133
+ # => <Organization id: 1, name: 'Pets', city: 'Dallas' ...>
134
+
135
+ # This will issue a remote call that is equivalent to Animal.where(organization_id: 1)
136
+ organization.animals
137
+ # => [<Animal id: 1, organization_id: 1 ...>, <Animal id: 2, organization_id: 1 ...>, ...]
138
+ ```
139
+
140
+ # Test Mocks
141
+
142
+ For testing, each model has a corresponding mock class. Mocked classes are available by default.
143
+
144
+ ### `find`
145
+
146
+ Returns a single object as if it was from a successful `find` method call.
147
+
148
+ ```ruby
149
+ RescueGroups::AnimalMock.find
150
+ #=> <Animal id: 123, name: 'fluffy' ...>
151
+ ```
152
+
153
+ Use this method while testing in place of writing your own custom factories/stubbed versions of RescueGroups objects.
154
+
155
+ ```ruby
156
+ describe SomeTestClass do
157
+ let(:animal) { RescueGroups::AnimalMock.find }
158
+
159
+ specify do
160
+ # test method body
161
+ end
162
+ end
163
+ ```
164
+
165
+ ### `find_not_found`
166
+
167
+ To emulate an error during a find call for testing, the `find_not_found` method will raise the same error as a typical `find` method.
168
+
169
+ ```ruby
170
+ RescueGroups::AnimalMock.find_not_found
171
+ # => Unable to find Animal
172
+ ```
173
+
174
+ ### `where`
175
+
176
+ Returns an array containing a single object as if it was from a successful `where` method call.
177
+
178
+ ```ruby
179
+ RescueGroups::AnimalMock.where
180
+ #=> [<Animal id: 123, name: 'fluffy' ...>]
181
+ ```
182
+
183
+ Use this method while testing in place of writing your own custom factories/stubbed versions of RescueGroups objects.
184
+
185
+ ```ruby
186
+ describe SomeTestClass do
187
+ let(:animal_response) { RescueGroups::AnimalMock.where }
188
+
189
+ specify do
190
+ # test method body
191
+ end
192
+ end
193
+ ```
194
+
195
+ ### `where_not_found`
196
+
197
+ The `where_not_found` method emulates a `where` method that returns no results, and returns an empty array.
198
+
199
+ ```ruby
200
+ RescueGroups::Animal.where_not_found
201
+ #=> []
202
+ ```
203
+
204
+ ## Pictures
205
+
206
+ `Animals` have many `Pictures`
207
+
208
+ A Picture exposes two methods: `url` and `url_thumb`
209
+
210
+ Each of these methods accepts an option keyword parameter `secure:`. This param returns a secure url (https) if passed. It it is ommitted, a default http url is returned.
211
+
212
+ ### Default
213
+
214
+ ```ruby
215
+ animal = Animal.find(1)
216
+ animal.pictures.first.url
217
+ #=> "http://image.to.my.animal/1234"
218
+ ```
219
+
220
+ ### Secure
221
+
222
+ ```ruby
223
+ animal = Animal.find(1)
224
+ animal.pictures.first.url(secure: true)
225
+ #=> "https://secure.image.to.my.animal/1234"
226
+ ```
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require './config/initializer'
2
+ require './spec/fixtures/test_constants'
3
+
4
+ namespace :fixtures do
5
+ desc 'reload fixtures from remote API'
6
+ task :reload do
7
+ fail 'No API key given' if RescueGroups.config.apikey == ""
8
+
9
+ {
10
+ RescueGroups::Animal => [TEST_ANIMAL_ID, breed: TEST_ANIMAL_BREED],
11
+ RescueGroups::Organization => [TEST_ORG_ID, name: TEST_ORG_NAME],
12
+ RescueGroups::Event => [TEST_EVENT_ID, name: TEST_EVENT_NAME],
13
+ }.each do |klass, test_values|
14
+ find_request = RescueGroups::Requests::Find.new([*test_values[0]].flatten, klass, klass.api_client)
15
+ where_request = RescueGroups::Requests::Where.new(test_values[1], klass, klass.api_client, klass.search_engine_class)
16
+
17
+ find_results = klass.api_client.post_and_respond(find_request.as_json).parsed_body
18
+ where_results = klass.api_client.post_and_respond(where_request.as_json).parsed_body
19
+
20
+ model_name = klass.to_s.split('::').last.downcase
21
+
22
+ find_json_file = "#{ File.expand_path('..', __FILE__) }/spec/fixtures/#{ model_name }/find.json"
23
+ where_json_file = "#{ File.expand_path('..', __FILE__) }/spec/fixtures/#{ model_name }/where.json"
24
+
25
+ File.open(find_json_file, 'w') { |f| f.write(JSON(find_results)) }
26
+ File.open(where_json_file, 'w') { |f| f.write(JSON(where_results)) }
27
+ end
28
+ end
29
+ end
30
+
data/config/config.rb ADDED
@@ -0,0 +1,26 @@
1
+ module RescueGroups
2
+ class Config
3
+ attr_accessor :apikey, :load_all_results
4
+ end
5
+
6
+ # method: config
7
+ # purpose: class method to instantiate or return an already
8
+ # instantiated Config class
9
+ # param: none
10
+ # return: <Config> - instance of the config class
11
+ def self.config
12
+ @config ||= Config.new
13
+ end
14
+
15
+ # method: configuration
16
+ # purpose: yield to a block of configuration parameters
17
+ # example:
18
+ # RescueGroups.configuration do |config|
19
+ # config.api_key = 'anything'
20
+ # end
21
+ # param: none
22
+ # return: none
23
+ def self.configuration
24
+ yield config
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+ require 'httparty'
3
+ # This must be required before configuration can happen
4
+ require_relative '../rescue_groups'
5
+ require_relative './config'
6
+
7
+ RescueGroups.configuration do |config|
8
+ # Your API key should be set in an ENV variable called RESCUE_GROUPS_API_KEY
9
+ config.apikey = ENV['RESCUE_GROUPS_API_KEY'] || ''
10
+ config.load_all_results = false
11
+ end
12
+
13
+ require_relative '../models/animal'
14
+ require_relative '../models/organization'
15
+ require_relative '../models/event'
@@ -0,0 +1,138 @@
1
+ ## Animal Fields
2
+ * id
3
+ * organization_id
4
+ * activity_level
5
+ * adoption_fee
6
+ * altered
7
+ * available_date
8
+ * birthdate
9
+ * birthdate_exact
10
+ * breed
11
+ * coat_length
12
+ * color
13
+ * color_id
14
+ * color_details
15
+ * courtesy
16
+ * declawed
17
+ * description
18
+ * description_plain
19
+ * distinguishing_marks
20
+ * ear_type
21
+ * energy_level
22
+ * excercise_needs
23
+ * eye_color
24
+ * fence
25
+ * found
26
+ * found_date
27
+ * found_postal_code
28
+ * general_age
29
+ * general_size_potential
30
+ * grooming_needs
31
+ * house_trained
32
+ * indoor_outdoor
33
+ * kill_date
34
+ * kill_reason
35
+ * location
36
+ * location_distance
37
+ * location_city_state
38
+ * microchipped
39
+ * mixed_breed
40
+ * name
41
+ * special_needs
42
+ * special_needs_description
43
+ * needs_foster
44
+ * new_people
45
+ * not_house_trained_reason
46
+ * obedience_training
47
+ * ok_with_adults
48
+ * ok_with_cats
49
+ * ok_with_dogs
50
+ * ok_with_kids
51
+ * owner_experience
52
+ * pattern
53
+ * pattern_id
54
+ * adoption_pending
55
+ * primary_breed
56
+ * primary_breed_id
57
+ * rescue_id
58
+ * search_string
59
+ * secondary_breed
60
+ * secondary_breed_id
61
+ * sex
62
+ * shedding
63
+ * size_current
64
+ * size_potential
65
+ * size_uom
66
+ * species
67
+ * species_id
68
+ * sponsorable
69
+ * sponsors
70
+ * sponsorhip_details
71
+ * sponsorship_minimum
72
+ * status
73
+ * status_id
74
+ * summary
75
+ * tail_type
76
+ * thumbnail_url
77
+ * up_to_date
78
+ * updated_date
79
+ * url
80
+ * vocal
81
+ * yard_required
82
+ * affectionate
83
+ * apartment
84
+ * crate_trained
85
+ * drools
86
+ * eager_to_please
87
+ * escapes
88
+ * even_tempered
89
+ * fetches
90
+ * gentle
91
+ * good_in_car
92
+ * goofy
93
+ * has_allergies
94
+ * hearing_impaired
95
+ * hypoallergenic
96
+ * independent
97
+ * intelligent
98
+ * lap
99
+ * leash_trained
100
+ * needs_companion_animal
101
+ * no_cold
102
+ * no_female_dogs
103
+ * no_heat
104
+ * no_large_dogs
105
+ * no_male_dogs
106
+ * no_small_dogs
107
+ * obedient
108
+ * ok_for_seniors
109
+ * ok_with_farm_animals
110
+ * older_kids_only
111
+ * ongoing_medical
112
+ * playful
113
+ * plays_toys
114
+ * predatory
115
+ * protective
116
+ * sight_impaired
117
+ * skittish
118
+ * special_diet
119
+ * swims
120
+ * timid
121
+ * foster_email
122
+ * foster_first_name
123
+ * foster_last_name
124
+ * foster_name
125
+ * foster_phone_cell
126
+ * foster_phone_home
127
+ * foster_salutation
128
+ * location_addresss
129
+ * location_city
130
+ * location_country
131
+ * location_url
132
+ * location_name
133
+ * location_phone
134
+ * location_state
135
+ * location_postal_code
136
+ * pictures
137
+ * videos
138
+ * video_urls