vj-sdk 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/LICENSE +20 -0
- data/README.markdown +0 -0
- data/README.rdoc +7 -0
- data/Rakefile +70 -0
- data/VERSION.yml +4 -0
- data/lib/core_ext/cgi.rb +12 -0
- data/lib/core_ext/hash.rb +52 -0
- data/lib/core_ext/object.rb +15 -0
- data/lib/core_ext/string.rb +11 -0
- data/lib/sdk_connection_harness.rb +101 -0
- data/lib/videojuicer/asset/audio.rb +13 -0
- data/lib/videojuicer/asset/base.rb +80 -0
- data/lib/videojuicer/asset/flash.rb +8 -0
- data/lib/videojuicer/asset/image.rb +10 -0
- data/lib/videojuicer/asset/text.rb +8 -0
- data/lib/videojuicer/asset/video.rb +22 -0
- data/lib/videojuicer/campaign.rb +19 -0
- data/lib/videojuicer/campaign_policy.rb +116 -0
- data/lib/videojuicer/criterion/base.rb +56 -0
- data/lib/videojuicer/criterion/date_range.rb +15 -0
- data/lib/videojuicer/criterion/geolocation.rb +18 -0
- data/lib/videojuicer/criterion/request.rb +15 -0
- data/lib/videojuicer/criterion/time.rb +15 -0
- data/lib/videojuicer/criterion/week_day.rb +20 -0
- data/lib/videojuicer/oauth/multipart_helper.rb +96 -0
- data/lib/videojuicer/oauth/proxy_factory.rb +18 -0
- data/lib/videojuicer/oauth/request_proxy.rb +280 -0
- data/lib/videojuicer/presentation.rb +38 -0
- data/lib/videojuicer/preset.rb +29 -0
- data/lib/videojuicer/promo/base.rb +72 -0
- data/lib/videojuicer/resource/base.rb +175 -0
- data/lib/videojuicer/resource/collection.rb +36 -0
- data/lib/videojuicer/resource/embeddable.rb +30 -0
- data/lib/videojuicer/resource/errors.rb +17 -0
- data/lib/videojuicer/resource/inferrable.rb +141 -0
- data/lib/videojuicer/resource/property_registry.rb +145 -0
- data/lib/videojuicer/resource/relationships/belongs_to.rb +39 -0
- data/lib/videojuicer/resource/types.rb +28 -0
- data/lib/videojuicer/session.rb +74 -0
- data/lib/videojuicer/shared/configurable.rb +103 -0
- data/lib/videojuicer/shared/exceptions.rb +32 -0
- data/lib/videojuicer/user.rb +43 -0
- data/lib/videojuicer.rb +110 -0
- data/spec/assets/audio_spec.rb +25 -0
- data/spec/assets/flash_spec.rb +24 -0
- data/spec/assets/image_spec.rb +25 -0
- data/spec/assets/text_spec.rb +24 -0
- data/spec/assets/video_spec.rb +25 -0
- data/spec/belongs_to_spec.rb +45 -0
- data/spec/campaign_policy_spec.rb +41 -0
- data/spec/campaign_spec.rb +25 -0
- data/spec/collection_spec.rb +31 -0
- data/spec/criterion/date_range_spec.rb +24 -0
- data/spec/criterion/geolocation_spec.rb +23 -0
- data/spec/criterion/request_spec.rb +23 -0
- data/spec/criterion/time_spec.rb +23 -0
- data/spec/criterion/week_day_spec.rb +23 -0
- data/spec/files/audio.mp3 +0 -0
- data/spec/files/empty_file +0 -0
- data/spec/files/flash.swf +0 -0
- data/spec/files/image.jpg +0 -0
- data/spec/files/text.txt +1 -0
- data/spec/files/video.mov +0 -0
- data/spec/helpers/be_equal_to.rb +26 -0
- data/spec/helpers/spec_fixtures.rb +227 -0
- data/spec/helpers/spec_helper.rb +53 -0
- data/spec/presentation_spec.rb +25 -0
- data/spec/preset_spec.rb +30 -0
- data/spec/promos/audio_spec.rb +23 -0
- data/spec/promos/image_spec.rb +24 -0
- data/spec/promos/text_spec.rb +23 -0
- data/spec/promos/video_spec.rb +23 -0
- data/spec/property_registry_spec.rb +130 -0
- data/spec/request_proxy_spec.rb +90 -0
- data/spec/session_spec.rb +98 -0
- data/spec/shared/asset_spec.rb +39 -0
- data/spec/shared/configurable_spec.rb +75 -0
- data/spec/shared/dependent_spec.rb +40 -0
- data/spec/shared/embeddable_spec.rb +34 -0
- data/spec/shared/model_spec.rb +74 -0
- data/spec/shared/resource_spec.rb +140 -0
- data/spec/spec.opts +3 -0
- data/spec/user_spec.rb +42 -0
- data/spec/videojuicer_spec.rb +122 -0
- data/tasks/vj-core.rb +72 -0
- data/vj-sdk.gemspec +168 -0
- metadata +209 -0
@@ -0,0 +1,175 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
|
3
|
+
The Videojuicer::Resource module is a mixin that provides RESTful model features
|
4
|
+
to classes within the Videojuicer SDK. By including Videojuicer::Resource in a model
|
5
|
+
class, it will gain a series of class and instance methods that *approximate* the
|
6
|
+
interface provided by a DataMapper or ActiveRecord-style object.
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
=end
|
11
|
+
|
12
|
+
module Videojuicer
|
13
|
+
module Resource
|
14
|
+
|
15
|
+
include Videojuicer::Exceptions
|
16
|
+
include Videojuicer::Configurable
|
17
|
+
include Videojuicer::OAuth::ProxyFactory
|
18
|
+
include Videojuicer::Resource::Inferrable
|
19
|
+
include Videojuicer::Resource::PropertyRegistry
|
20
|
+
include Videojuicer::Resource::Types
|
21
|
+
include Videojuicer::Resource::Relationships::BelongsTo
|
22
|
+
|
23
|
+
def self.included(base)
|
24
|
+
base.extend(ClassMethods)
|
25
|
+
Inferrable.included(base)
|
26
|
+
PropertyRegistry.included(base)
|
27
|
+
Relationships::BelongsTo.included(base)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Determines if this instance is a new record. For the purposes of the SDK,
|
31
|
+
# this really means "does the item already have an ID?" - because if i reconstitute
|
32
|
+
# a known item without first retrieving it from the API, then saving that
|
33
|
+
# object should push changes to the correct resource rather than creating a new
|
34
|
+
# object.
|
35
|
+
def new_record?
|
36
|
+
(id.to_i > 0)? false : true
|
37
|
+
end
|
38
|
+
|
39
|
+
# Saves the record, creating it if is new and updating it if it is not.
|
40
|
+
# Returns TRUE on success and FALSE on fail.
|
41
|
+
def save
|
42
|
+
proxy = proxy_for(config)
|
43
|
+
param_key = self.class.parameter_name
|
44
|
+
response = if new_record?
|
45
|
+
proxy.post(resource_path, param_key=>returnable_attributes)
|
46
|
+
else
|
47
|
+
proxy.put(resource_path, param_key=>returnable_attributes)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Parse and handle response
|
51
|
+
return validate_response(response)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Attempts to validate this object with the remote service. Returns TRUE on success,
|
55
|
+
# and returns FALSE on failure. Failure will also populate the #errors object on
|
56
|
+
# this instance.
|
57
|
+
def valid?
|
58
|
+
proxy = proxy_for(config)
|
59
|
+
param_key = self.class.parameter_name
|
60
|
+
response = if new_record?
|
61
|
+
proxy.post(resource_path(:validate, :nested=>false), param_key=>returnable_attributes)
|
62
|
+
else
|
63
|
+
proxy.put(resource_path(:validate, :nested=>false), param_key=>returnable_attributes)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Parse and handle response
|
67
|
+
return validate_response(response)
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# The hash of errors on this object - keyed by attribute name,
|
72
|
+
# with the error description as the value.
|
73
|
+
def errors=(arg); @errors = Videojuicer::Resource::Errors.new(arg); end
|
74
|
+
def errors; @errors ||= Videojuicer::Resource::Errors.new({}); end
|
75
|
+
def errors_on(key); errors.on(key); end
|
76
|
+
|
77
|
+
# Takes a response from the API and performs the appropriate actions.
|
78
|
+
def validate_response(response)
|
79
|
+
body = response.body
|
80
|
+
attribs = (body.is_a?(Hash))? body : JSON.parse(body) rescue raise(JSON::ParserError, "Could not parse #{body}: \n\n #{body}")
|
81
|
+
attribs.each do |prop, value|
|
82
|
+
next if (prop == "id") and (value.to_i < 1)
|
83
|
+
self.send("#{prop}=", value) rescue next
|
84
|
+
end
|
85
|
+
|
86
|
+
if e = attribs["errors"]
|
87
|
+
self.errors = e
|
88
|
+
return false
|
89
|
+
else
|
90
|
+
self.id = attribs["id"].to_i
|
91
|
+
self.errors = {}
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Updates the attributes and saves the record in one go.
|
97
|
+
def update_attributes(attrs)
|
98
|
+
self.attributes = attrs
|
99
|
+
return save
|
100
|
+
end
|
101
|
+
|
102
|
+
# Makes a call to the API for the current attributes on this object.
|
103
|
+
# Overwrites the current instance attribute values.
|
104
|
+
def reload
|
105
|
+
raise NoResource, "Cannot load remote attributes for new records" if new_record?
|
106
|
+
response = proxy_for(config).get(resource_path(nil, :nested=>false))
|
107
|
+
return validate_response(response)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Attempts to delete this record. Will raise an exception if the record is new
|
111
|
+
# or if it has already been deleted.
|
112
|
+
def destroy
|
113
|
+
proxy_for(config).delete(resource_path)
|
114
|
+
end
|
115
|
+
|
116
|
+
module ClassMethods
|
117
|
+
|
118
|
+
# Finds all objects matching the criteria. Also allows filterable methods.
|
119
|
+
# Use :limit to throttle the amount of records returned.
|
120
|
+
# Use :offset to return all objects after a certain index. Combine with :limit for pagination.
|
121
|
+
# You may specify comparators on attributes by giving them in the following forms:
|
122
|
+
# > "id"=>"9" #=> Returns only records with ID 9
|
123
|
+
# > "id.gt" => "9" #=> Returns only records with ID greater than 9
|
124
|
+
# > "id.lt" => "9" #=> Returns only records with ID less than 9
|
125
|
+
# > "id.gte" => "9" #=> Returns only records with ID greater than or equal to 9
|
126
|
+
# > "id.lte" => "9" #=> Returns only records with ID less than or equal to than 9
|
127
|
+
def all(options={})
|
128
|
+
# Get a proxy
|
129
|
+
options = (options.empty?)? {} : {parameter_name=>options} # FIXME this is a hacky workaround for singleton scope.
|
130
|
+
response = instance_proxy.get(base_path(:nested=>false), options)
|
131
|
+
op = JSON.parse(response.body)
|
132
|
+
|
133
|
+
items = (op["items"] rescue op) # If "items" is on the returned object then this is a collection hash.
|
134
|
+
# Instantiate objects
|
135
|
+
items = items.collect do |attrs|
|
136
|
+
o = new; o.attributes = attrs
|
137
|
+
o
|
138
|
+
end
|
139
|
+
return (op.is_a?(Hash))? Videojuicer::Resource::Collection.new(items, op["count"], op["offset"], op["limit"]) : items
|
140
|
+
end
|
141
|
+
|
142
|
+
def first(options={})
|
143
|
+
all(options.merge(:limit=>1)).first
|
144
|
+
end
|
145
|
+
|
146
|
+
def create(attrs={})
|
147
|
+
o = new(attrs)
|
148
|
+
o.save
|
149
|
+
return o
|
150
|
+
end
|
151
|
+
|
152
|
+
# Fetches an object given an ID. Straight forward.
|
153
|
+
def get(id)
|
154
|
+
o = new(:id=>id)
|
155
|
+
begin
|
156
|
+
o.reload
|
157
|
+
o
|
158
|
+
rescue Videojuicer::Exceptions::NoResource
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def destroy(id)
|
164
|
+
o = new(:id=>id)
|
165
|
+
o.destroy
|
166
|
+
end
|
167
|
+
|
168
|
+
def instance_proxy
|
169
|
+
o = new
|
170
|
+
o.proxy_for(o.config)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Videojuicer::Resource::Collection is an array extension returned by many common actions within
|
3
|
+
the Videojuicer SDK. Any request that returns paginated data, or a subset of your query results,
|
4
|
+
will return a Collection object containing the object subset, with some additional pagination
|
5
|
+
data.
|
6
|
+
=end
|
7
|
+
|
8
|
+
module Videojuicer
|
9
|
+
module Resource
|
10
|
+
class Collection < ::Array
|
11
|
+
|
12
|
+
attr_accessor :limit
|
13
|
+
attr_accessor :offset
|
14
|
+
attr_accessor :total
|
15
|
+
|
16
|
+
def initialize(objects, total, offset, limit)
|
17
|
+
clear
|
18
|
+
objects.each {|o| self << o }
|
19
|
+
self.total = total
|
20
|
+
self.offset = offset
|
21
|
+
self.limit = limit
|
22
|
+
end
|
23
|
+
|
24
|
+
def page_count
|
25
|
+
return 1 if limit.nil?
|
26
|
+
(total.to_f/limit.to_f).ceil
|
27
|
+
end
|
28
|
+
|
29
|
+
def page_number
|
30
|
+
return 1 if limit.nil?
|
31
|
+
(offset.to_f/limit.to_f).ceil
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
|
3
|
+
A module that provides OEmbed functionality to any Videojuicer::Resource model.
|
4
|
+
|
5
|
+
=end
|
6
|
+
|
7
|
+
module Videojuicer
|
8
|
+
module Resource
|
9
|
+
module Embeddable
|
10
|
+
|
11
|
+
OEMBED_ENDPOINT = "/oembed".freeze
|
12
|
+
|
13
|
+
def oembed_payload(maxwidth, maxheight)
|
14
|
+
proxy = proxy_for(config)
|
15
|
+
result = proxy.get(OEMBED_ENDPOINT, :format=>"json", :url=>"#{proxy.host_stub}#{resource_path}?seed_name=#{seed_name}", :maxwidth=>maxwidth, :maxheight=>maxheight)
|
16
|
+
JSON.parse(result.body)
|
17
|
+
end
|
18
|
+
|
19
|
+
def embed_size(maxwidth, maxheight)
|
20
|
+
p = oembed_payload(maxwidth, maxheight)
|
21
|
+
return p["width"], p["height"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def embed_code(maxwidth, maxheight)
|
25
|
+
oembed_payload(maxwidth, maxheight)["html"]
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Videojuicer
|
2
|
+
module Resource
|
3
|
+
class Errors < Hash
|
4
|
+
|
5
|
+
def initialize(error_hash)
|
6
|
+
self.merge!(error_hash)
|
7
|
+
end
|
8
|
+
|
9
|
+
def on(key)
|
10
|
+
o = self[key.to_s]
|
11
|
+
o = (o.is_a?(Array))? o.uniq : [o]
|
12
|
+
return (o.compact.empty?)? nil : o.compact
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
=Inferred names
|
3
|
+
|
4
|
+
Inferrable contains methods used by Resources to automagically determine various
|
5
|
+
API parameters from the name of the including class.
|
6
|
+
|
7
|
+
E.g.
|
8
|
+
|
9
|
+
class Videojuicer::Presentation
|
10
|
+
include Videojuicer::Resource # Inferrable is included by this line
|
11
|
+
end
|
12
|
+
|
13
|
+
Videojuicer::Presentation.resource_name #=> "presentation" (The base name)
|
14
|
+
Videojuicer::Presentation.parameter_name #=> "presentation" (The key used when crafting parameter keys e.g. presentation[title])
|
15
|
+
Videojuicer::Presentation.resource_path #=> "/presentations" (The base URI used to retrieve data related to objects of this tyle)
|
16
|
+
Videojuicer::Presentation.resource_path(id_of_presentation) #=> "/presentations/id_of_presentation.json" (The base URI used to retrieve data related to objects of this tyle)
|
17
|
+
|
18
|
+
m = Videojuicer::Presentation.new
|
19
|
+
m.resource_path #=> "/presentations" (The URI that will be used to )
|
20
|
+
|
21
|
+
m = Videojuicer::Presentation.first
|
22
|
+
m.id #=> 500, for example
|
23
|
+
m.resource_path #=> "/presentations/500.json"
|
24
|
+
=end
|
25
|
+
|
26
|
+
module Videojuicer
|
27
|
+
module Resource
|
28
|
+
module Inferrable
|
29
|
+
|
30
|
+
def self.included(base)
|
31
|
+
base.extend(SingletonMethods)
|
32
|
+
base.send :include, InstanceMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
module InstanceMethods
|
36
|
+
# Returns the appropriate resource path for this object.
|
37
|
+
# If the object is a new record, then the root object type path
|
38
|
+
# will be given. If the object is not new (has an ID) then the
|
39
|
+
# specific ID will be used.
|
40
|
+
def resource_path(action=nil, route_options={})
|
41
|
+
route_options = {:id=>id}.merge(route_options) unless new_record?
|
42
|
+
r = self.class.resource_route(action, route_options)
|
43
|
+
self.class.compile_route(r, attributes)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module SingletonMethods
|
48
|
+
|
49
|
+
def compile_route(mask, properties={})
|
50
|
+
properties = properties.deep_stringify
|
51
|
+
result = []
|
52
|
+
mask.split("/").each do |member|
|
53
|
+
if member[0..0] == ":"
|
54
|
+
result << (properties[member.delete(":")])
|
55
|
+
else
|
56
|
+
result << member
|
57
|
+
end
|
58
|
+
end
|
59
|
+
"#{result.join("/")}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Nested resources are inferred from the class hierarchy:
|
63
|
+
# Something.parent_class #=> nil
|
64
|
+
# Something::Else.parent_class #=> Something
|
65
|
+
# Monkeys::Are::Delicious
|
66
|
+
def containing_class
|
67
|
+
c = self.to_s.split("::"); c.pop
|
68
|
+
(c.any?)? Object.full_const_get(c.join("::")) : nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# The route fragment under which nested resources should be mapped.
|
72
|
+
def nesting_route
|
73
|
+
r = ["/#{plural_name}/#{nesting_route_key}"]
|
74
|
+
r = ([(self.containing_class.nesting_route rescue nil)] + r).compact.join("")
|
75
|
+
end
|
76
|
+
|
77
|
+
# The key used by other models when referring to this one in resource routes. User.nesting_route_key == ":user_id"
|
78
|
+
def nesting_route_key
|
79
|
+
":#{resource_name}_id"
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the lowercased, underscored version of the including class name.
|
83
|
+
# e.g. Videojuicer::ExampleModel.singular_name => "example_model"
|
84
|
+
def singular_name
|
85
|
+
@singular_name ||= self.to_s.split("::").last.
|
86
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
87
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
88
|
+
tr("-", "_").
|
89
|
+
downcase.snake_case
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns the plural version of the underscored singular name. This method,
|
93
|
+
# when compared directly and fairly to something like ActiveSupport's inflection
|
94
|
+
# module, is an idiot. You would be best to treat it as one.
|
95
|
+
def plural_name
|
96
|
+
if singular_name.match(/y$/)
|
97
|
+
return singular_name.gsub(/y$/, "ies")
|
98
|
+
end
|
99
|
+
# Fall back to the simplest rules
|
100
|
+
(singular_name.match(/s$/))? "#{singular_name}es" : "#{singular_name}s"
|
101
|
+
end
|
102
|
+
|
103
|
+
# The name used to identify this resource to the API, and in responses from the API.
|
104
|
+
def resource_name
|
105
|
+
singular_name
|
106
|
+
end
|
107
|
+
|
108
|
+
# The name used to send parameters to the API. For instance, if a model named Cake has
|
109
|
+
# an attribute is_lie, and the parameter name of Cake is "cake", this attribute will be
|
110
|
+
# sent to the API as a parameter named cake[is_lie] with the appropriate value.
|
111
|
+
def parameter_name
|
112
|
+
singular_name
|
113
|
+
end
|
114
|
+
|
115
|
+
# The path to this class's resource given the desired action route.
|
116
|
+
# Returned as a mask with replaceable keys e.g. /fixed/fixed/:replaceme/fixed
|
117
|
+
def resource_route(action=nil, route_options={})
|
118
|
+
id = route_options.delete(:id)
|
119
|
+
action_stem = (id)? "/#{id}" : ""
|
120
|
+
action_stem += (action)? "/#{action}" : ""
|
121
|
+
action_stem += ".json" unless action_stem.empty?
|
122
|
+
"#{base_path(route_options)}#{action_stem}"
|
123
|
+
end
|
124
|
+
|
125
|
+
# The root route for requests to the API. By default this is inferred from the plural name
|
126
|
+
# e.g. Videojuicer::Presentation uses /presentations as the resource_path.
|
127
|
+
def base_path(options={})
|
128
|
+
options = {
|
129
|
+
:nested=>true
|
130
|
+
}.merge(options)
|
131
|
+
r = "/#{plural_name}"
|
132
|
+
r = "#{self.containing_class.nesting_route rescue nil}#{r}" if options[:nested]
|
133
|
+
return r
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
|
3
|
+
= Property Registry
|
4
|
+
|
5
|
+
This module allows model classes to register properties that have setter and getter methods,
|
6
|
+
and are earmarked to be included in calls between instances of those classes and the API.
|
7
|
+
|
8
|
+
Properties are registered using a DataMapper-style syntax (ActiveRecord-style autodiscovery not
|
9
|
+
being possible).
|
10
|
+
|
11
|
+
=end
|
12
|
+
|
13
|
+
module Videojuicer
|
14
|
+
module Resource
|
15
|
+
module PropertyRegistry
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
# Class-level inheritable reader
|
19
|
+
base.extend(SingletonMethods)
|
20
|
+
base.class_eval do
|
21
|
+
@attributes = {}
|
22
|
+
class << self
|
23
|
+
attr_accessor :attributes
|
24
|
+
end
|
25
|
+
end
|
26
|
+
base.property :id, Integer
|
27
|
+
end
|
28
|
+
|
29
|
+
# Allow subclasses of each resource to get the attributes accessor
|
30
|
+
def self.inherited(subclass)
|
31
|
+
v = "@attributes"
|
32
|
+
subclass.instance_variable_set(v, instance_variable_get(v))
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def initialize(attrs={})
|
37
|
+
self.attributes = default_attributes
|
38
|
+
self.attributes = attrs
|
39
|
+
end
|
40
|
+
|
41
|
+
def attributes
|
42
|
+
@attributes ||= {}
|
43
|
+
@attributes
|
44
|
+
end
|
45
|
+
|
46
|
+
def attributes=(arg)
|
47
|
+
raise ArgumentError, "Attributes must be set as a Hash" unless arg.is_a?(Hash)
|
48
|
+
arg.each do |key, value|
|
49
|
+
self.send("#{key}=", value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_attributes
|
54
|
+
d = {}
|
55
|
+
self.class.attributes.each do |key, props|
|
56
|
+
d[key] = props[:default] || nil
|
57
|
+
end
|
58
|
+
return d
|
59
|
+
end
|
60
|
+
|
61
|
+
def attr_get(key)
|
62
|
+
key = key.to_sym
|
63
|
+
attributes[key]
|
64
|
+
end
|
65
|
+
|
66
|
+
def attr_set(key, value)
|
67
|
+
key = key.to_sym
|
68
|
+
attributes[key] = coerce_value(key, value)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Takes what is normally a string and coerces it into the correct object type for the
|
72
|
+
# attribute's actual type.
|
73
|
+
def coerce_value(key, value)
|
74
|
+
return value unless value
|
75
|
+
klass = self.class.attributes[key][:class]
|
76
|
+
if value.is_a?(String) and value.any?
|
77
|
+
# In-built types
|
78
|
+
if klass.kind_of?(Videojuicer::Resource::Types::Base)
|
79
|
+
return klass.new(value).dump
|
80
|
+
end
|
81
|
+
|
82
|
+
# Dates
|
83
|
+
if klass.respond_to?(:parse)
|
84
|
+
return klass.parse(value) rescue raise "Invalid date: #{value.inspect}"
|
85
|
+
end
|
86
|
+
elsif value.is_a? Hash and value.any?
|
87
|
+
if klass == DateTime
|
88
|
+
if value.is_a?(Hash)
|
89
|
+
year = value[:year]
|
90
|
+
month = value[:month]
|
91
|
+
day = value[:day]
|
92
|
+
hour = value[:hour] or "00"
|
93
|
+
minute = value[:minute] or "00"
|
94
|
+
value = klass.parse("#{year}-#{month}-#{day}T#{hour}:#{minute}:00+00:00")
|
95
|
+
else
|
96
|
+
raise ArgumentError, "Please supply a DateTime, Hash keyed w/ [:day, :month, :year, :hour, :minute] or a String that can be coerced into a date"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
return value
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns a hash of the attributes for this object, minus the
|
104
|
+
# private attributes that should not be included in the response.
|
105
|
+
def returnable_attributes
|
106
|
+
attrs = attributes.dup
|
107
|
+
self.class.attributes.select {|name, props| props[:writer] != :public}.each do |name, props|
|
108
|
+
attrs.delete name
|
109
|
+
end
|
110
|
+
attrs.delete(:id)
|
111
|
+
attrs
|
112
|
+
end
|
113
|
+
|
114
|
+
module SingletonMethods
|
115
|
+
|
116
|
+
# Registers an attribute using a datamapper-style syntax.
|
117
|
+
# Creates setter and getter methods
|
118
|
+
def property(prop_name, klass, options={})
|
119
|
+
# Can't raise twice.
|
120
|
+
prop_name = prop_name.to_sym
|
121
|
+
raise ArgumentError, "Property #{prop_name} already registered." if self.attributes.include?(prop_name)
|
122
|
+
|
123
|
+
options = {:class=>klass, :writer=>:public}.merge(options)
|
124
|
+
# Register with the class
|
125
|
+
self.attributes[prop_name] = options
|
126
|
+
# Create setter methods
|
127
|
+
define_method prop_name do
|
128
|
+
attr_get(prop_name)
|
129
|
+
end
|
130
|
+
|
131
|
+
private if options[:writer] == :private
|
132
|
+
protected if options[:writer] == :protected
|
133
|
+
|
134
|
+
define_method "#{prop_name}=" do |arg|
|
135
|
+
attr_set(prop_name, arg)
|
136
|
+
end
|
137
|
+
|
138
|
+
public
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Videojuicer
|
2
|
+
module Resource
|
3
|
+
module Relationships
|
4
|
+
module BelongsTo
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def belongs_to(name, options={})
|
13
|
+
options = {
|
14
|
+
:class=>name.to_s.capitalize,
|
15
|
+
:foreign_key=>"#{name}_id"
|
16
|
+
}.merge(options)
|
17
|
+
|
18
|
+
define_method name do
|
19
|
+
id = self.send(options[:foreign_key])
|
20
|
+
klass = (options[:class].is_a?(String))? Videojuicer.const_get(options[:class]) : options[:class]
|
21
|
+
return nil unless id
|
22
|
+
begin
|
23
|
+
return klass.get(id)
|
24
|
+
rescue Videojuicer::Exceptions::NoResource
|
25
|
+
return nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
define_method "#{name}=" do |arg|
|
30
|
+
self.send("#{options[:foreign_key]}=", arg.id)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Videojuicer
|
2
|
+
module Resource
|
3
|
+
module Types
|
4
|
+
|
5
|
+
class Base
|
6
|
+
def self.load(value)
|
7
|
+
return self.new(value)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(value)
|
11
|
+
@raw = value
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the source value
|
15
|
+
attr_reader :raw
|
16
|
+
end
|
17
|
+
|
18
|
+
class Boolean < Base
|
19
|
+
# Boolean.new("1").dump #=> true
|
20
|
+
# Returns the coerced value
|
21
|
+
def dump
|
22
|
+
[1, "1", "true", "yes"].include?(raw)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
|
3
|
+
A Videojuicer::Session object is the primary means of giving scope to your API calls.
|
4
|
+
|
5
|
+
=end
|
6
|
+
|
7
|
+
module Videojuicer
|
8
|
+
class Session
|
9
|
+
|
10
|
+
include Videojuicer::Configurable
|
11
|
+
include Videojuicer::OAuth::ProxyFactory
|
12
|
+
|
13
|
+
def initialize(options={})
|
14
|
+
configure!(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Makes a call to the Videojuicer OAuth provider, requesting an unauthorised Request Token
|
18
|
+
# which will be returned as a Mash.
|
19
|
+
#
|
20
|
+
# See http://oauth.net/core/1.0/#auth_step1 for details.
|
21
|
+
#
|
22
|
+
# Each session will make only one request for an unauthorised request token. Further calls to
|
23
|
+
# get_request_token will return the same token and token secret.
|
24
|
+
#
|
25
|
+
# The Mash responds to the following methods:
|
26
|
+
# +oauth_token+ - The token key to be used as the oauth_token in calls using this token.
|
27
|
+
# +oauth_token_secret+ - The token secret to be used when signing requests using this token.
|
28
|
+
# +expires+ - The date and time at which the token will become invalid.
|
29
|
+
# +permissions+ - The permissions that you wish the token to have. Will be one of FooAttributeRegistry, write-user, read-master or write-master.
|
30
|
+
def get_request_token
|
31
|
+
# GET tokens.json
|
32
|
+
response = proxy_for(config).get("/oauth/tokens.json")
|
33
|
+
body = response.body
|
34
|
+
@request_token_response ||= JSON.parse(body)
|
35
|
+
@request_token_response["request_token"]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Generates and returns a fully-qualified signed URL that the user should be directed to
|
39
|
+
# by the consumer.
|
40
|
+
#
|
41
|
+
# See http://oauth.net/core/1.0/#auth_step2 for details.
|
42
|
+
#
|
43
|
+
# Can optionally be given a Mash previously obtained from a call to get_request_token, but in most
|
44
|
+
# cases this is not necessary - a request token will be requested and used automatically if one
|
45
|
+
# is not supplied.
|
46
|
+
def authorize_url(token=get_request_token)
|
47
|
+
proxy_for(config.merge(:token=>token["oauth_token"], :token_secret=>token["oauth_token_secret"])).signed_url(:get, "/oauth/tokens/new", :oauth_callback=>token["oauth_callback"])
|
48
|
+
end
|
49
|
+
|
50
|
+
# Once a user has completed the OAuth authorisation procedure at the provider end, you may call
|
51
|
+
# exchange_request_token to make a request to the provider that will exchange your unauthorised
|
52
|
+
# request token for the corresponding authorised access token.
|
53
|
+
#
|
54
|
+
# See http://oauth.net/core/1.0/#auth_step3 for details.
|
55
|
+
#
|
56
|
+
# Returns a Mash that responds to the following methods:
|
57
|
+
# +oauth_token+ - The token key to be used as the oauth_token in calls using this token.
|
58
|
+
# +oauth_token_secret+ - The token secret to be used when signing requests using this token.
|
59
|
+
# +expires+ - The date and time at which the token will become invalid.
|
60
|
+
# +permissions+ - The permissions that you wish the token to have. Will be one of FooAttributeRegistry, write-user, read-master or write-master.
|
61
|
+
def exchange_request_token(token=get_request_token)
|
62
|
+
proxy = proxy_for(config.merge(:token=>token["oauth_token"], :token_secret=>token["oauth_token_secret"]))
|
63
|
+
response = proxy.get("/oauth/tokens.json")
|
64
|
+
t = response.body
|
65
|
+
Mash.new JSON.parse(t)["access_token"] rescue raise(JSON::ParserError, "could not parse: \n\n #{t}")
|
66
|
+
end
|
67
|
+
|
68
|
+
def authorize!
|
69
|
+
# GET tokens.json with magic params
|
70
|
+
puts "not yet authorized"
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|