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.
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