vj-sdk 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/.gitignore +4 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +0 -0
  4. data/README.rdoc +7 -0
  5. data/Rakefile +70 -0
  6. data/VERSION.yml +4 -0
  7. data/lib/core_ext/cgi.rb +12 -0
  8. data/lib/core_ext/hash.rb +52 -0
  9. data/lib/core_ext/object.rb +15 -0
  10. data/lib/core_ext/string.rb +11 -0
  11. data/lib/sdk_connection_harness.rb +101 -0
  12. data/lib/videojuicer/asset/audio.rb +13 -0
  13. data/lib/videojuicer/asset/base.rb +80 -0
  14. data/lib/videojuicer/asset/flash.rb +8 -0
  15. data/lib/videojuicer/asset/image.rb +10 -0
  16. data/lib/videojuicer/asset/text.rb +8 -0
  17. data/lib/videojuicer/asset/video.rb +22 -0
  18. data/lib/videojuicer/campaign.rb +19 -0
  19. data/lib/videojuicer/campaign_policy.rb +116 -0
  20. data/lib/videojuicer/criterion/base.rb +56 -0
  21. data/lib/videojuicer/criterion/date_range.rb +15 -0
  22. data/lib/videojuicer/criterion/geolocation.rb +18 -0
  23. data/lib/videojuicer/criterion/request.rb +15 -0
  24. data/lib/videojuicer/criterion/time.rb +15 -0
  25. data/lib/videojuicer/criterion/week_day.rb +20 -0
  26. data/lib/videojuicer/oauth/multipart_helper.rb +96 -0
  27. data/lib/videojuicer/oauth/proxy_factory.rb +18 -0
  28. data/lib/videojuicer/oauth/request_proxy.rb +280 -0
  29. data/lib/videojuicer/presentation.rb +38 -0
  30. data/lib/videojuicer/preset.rb +29 -0
  31. data/lib/videojuicer/promo/base.rb +72 -0
  32. data/lib/videojuicer/resource/base.rb +175 -0
  33. data/lib/videojuicer/resource/collection.rb +36 -0
  34. data/lib/videojuicer/resource/embeddable.rb +30 -0
  35. data/lib/videojuicer/resource/errors.rb +17 -0
  36. data/lib/videojuicer/resource/inferrable.rb +141 -0
  37. data/lib/videojuicer/resource/property_registry.rb +145 -0
  38. data/lib/videojuicer/resource/relationships/belongs_to.rb +39 -0
  39. data/lib/videojuicer/resource/types.rb +28 -0
  40. data/lib/videojuicer/session.rb +74 -0
  41. data/lib/videojuicer/shared/configurable.rb +103 -0
  42. data/lib/videojuicer/shared/exceptions.rb +32 -0
  43. data/lib/videojuicer/user.rb +43 -0
  44. data/lib/videojuicer.rb +110 -0
  45. data/spec/assets/audio_spec.rb +25 -0
  46. data/spec/assets/flash_spec.rb +24 -0
  47. data/spec/assets/image_spec.rb +25 -0
  48. data/spec/assets/text_spec.rb +24 -0
  49. data/spec/assets/video_spec.rb +25 -0
  50. data/spec/belongs_to_spec.rb +45 -0
  51. data/spec/campaign_policy_spec.rb +41 -0
  52. data/spec/campaign_spec.rb +25 -0
  53. data/spec/collection_spec.rb +31 -0
  54. data/spec/criterion/date_range_spec.rb +24 -0
  55. data/spec/criterion/geolocation_spec.rb +23 -0
  56. data/spec/criterion/request_spec.rb +23 -0
  57. data/spec/criterion/time_spec.rb +23 -0
  58. data/spec/criterion/week_day_spec.rb +23 -0
  59. data/spec/files/audio.mp3 +0 -0
  60. data/spec/files/empty_file +0 -0
  61. data/spec/files/flash.swf +0 -0
  62. data/spec/files/image.jpg +0 -0
  63. data/spec/files/text.txt +1 -0
  64. data/spec/files/video.mov +0 -0
  65. data/spec/helpers/be_equal_to.rb +26 -0
  66. data/spec/helpers/spec_fixtures.rb +227 -0
  67. data/spec/helpers/spec_helper.rb +53 -0
  68. data/spec/presentation_spec.rb +25 -0
  69. data/spec/preset_spec.rb +30 -0
  70. data/spec/promos/audio_spec.rb +23 -0
  71. data/spec/promos/image_spec.rb +24 -0
  72. data/spec/promos/text_spec.rb +23 -0
  73. data/spec/promos/video_spec.rb +23 -0
  74. data/spec/property_registry_spec.rb +130 -0
  75. data/spec/request_proxy_spec.rb +90 -0
  76. data/spec/session_spec.rb +98 -0
  77. data/spec/shared/asset_spec.rb +39 -0
  78. data/spec/shared/configurable_spec.rb +75 -0
  79. data/spec/shared/dependent_spec.rb +40 -0
  80. data/spec/shared/embeddable_spec.rb +34 -0
  81. data/spec/shared/model_spec.rb +74 -0
  82. data/spec/shared/resource_spec.rb +140 -0
  83. data/spec/spec.opts +3 -0
  84. data/spec/user_spec.rb +42 -0
  85. data/spec/videojuicer_spec.rb +122 -0
  86. data/tasks/vj-core.rb +72 -0
  87. data/vj-sdk.gemspec +168 -0
  88. 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