restly 0.0.1.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/restly/associations/base.rb +35 -0
- data/lib/restly/associations/belongs_to.rb +15 -0
- data/lib/restly/associations/builder.rb +30 -0
- data/lib/restly/associations/embeddable_resources/embeds_many.rb +7 -0
- data/lib/restly/associations/embeddable_resources/embeds_one.rb +7 -0
- data/lib/restly/associations/embeddable_resources.rb +28 -0
- data/lib/restly/associations/has_many.rb +23 -0
- data/lib/restly/associations/has_one.rb +23 -0
- data/lib/restly/associations.rb +90 -0
- data/lib/restly/base/fields.rb +79 -0
- data/lib/restly/base/generic_methods.rb +24 -0
- data/lib/restly/base/includes.rb +53 -0
- data/lib/restly/base/instance/actions.rb +35 -0
- data/lib/restly/base/instance/attributes.rb +88 -0
- data/lib/restly/base/instance/persistence.rb +20 -0
- data/lib/restly/base/instance.rb +76 -0
- data/lib/restly/base/mass_assignment_security.rb +19 -0
- data/lib/restly/base/resource/finders.rb +32 -0
- data/lib/restly/base/resource.rb +19 -0
- data/lib/restly/base/write_callbacks.rb +37 -0
- data/lib/restly/base.rb +82 -0
- data/lib/restly/client.rb +34 -0
- data/lib/restly/collection/pagination.rb +36 -0
- data/lib/restly/collection.rb +47 -0
- data/lib/restly/configuration.rb +43 -0
- data/lib/restly/connection.rb +99 -0
- data/lib/restly/controller_methods.rb +16 -0
- data/lib/restly/error.rb +30 -0
- data/lib/restly/middleware.rb +15 -0
- data/lib/restly/nested_attributes.rb +97 -0
- data/lib/restly/proxies/associations/collection.rb +22 -0
- data/lib/restly/proxies/associations/instance.rb +11 -0
- data/lib/restly/proxies/auth.rb +9 -0
- data/lib/restly/proxies/base.rb +39 -0
- data/lib/restly/proxies/params.rb +8 -0
- data/lib/restly/proxies.rb +18 -0
- data/lib/restly/railtie.rb +9 -0
- data/lib/restly/thread_local.rb +33 -0
- data/lib/restly/version.rb +3 -0
- data/lib/restly.rb +24 -0
- data/restly.gemspec +28 -0
- data/spec/fakewebs.rb +0 -0
- data/spec/helper.rb +31 -0
- data/spec/restly/base_spec.rb +60 -0
- metadata +210 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
module Restly::Base::Instance
|
2
|
+
extend ActiveSupport::Autoload
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
autoload :Actions
|
5
|
+
autoload :Attributes
|
6
|
+
autoload :Persistence
|
7
|
+
|
8
|
+
include Restly::Base::GenericMethods
|
9
|
+
include Actions
|
10
|
+
include Attributes
|
11
|
+
include Persistence
|
12
|
+
|
13
|
+
included do
|
14
|
+
attr_reader :init_options, :response
|
15
|
+
delegate :spec, to: :klass
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(attributes = nil, options = {})
|
19
|
+
|
20
|
+
@init_options = options
|
21
|
+
@attributes = HashWithIndifferentAccess.new
|
22
|
+
@association_cache = {}
|
23
|
+
@aggregation_cache = {}
|
24
|
+
@attributes_cache = {}
|
25
|
+
@previously_changed = {}
|
26
|
+
@changed_attributes = {}
|
27
|
+
|
28
|
+
run_callbacks :initialize do
|
29
|
+
|
30
|
+
@readonly = options[:readonly] || false
|
31
|
+
set_response options[:response] if options[:response]
|
32
|
+
@loaded = options.has_key?(:loaded) ? options[:loaded] : true
|
33
|
+
self.attributes = attributes if attributes
|
34
|
+
self.connection = options[:connection] if options[:connection].is_a?(OAuth2::AccessToken)
|
35
|
+
self.path = if response && response.response.env[:url]
|
36
|
+
response.response.env[:url].path.gsub(/\.\w+$/,'')
|
37
|
+
elsif respond_to?(:id) && id
|
38
|
+
[path, id].join('/')
|
39
|
+
else
|
40
|
+
klass.path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def pry_in
|
47
|
+
binding.pry
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_response(response)
|
51
|
+
raise Restly::Error::InvalidResponse unless response.is_a? OAuth2::Response
|
52
|
+
@response = response
|
53
|
+
if response.try(:body)
|
54
|
+
set_attributes_from_response
|
55
|
+
stub_associations_from_response
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def connection
|
60
|
+
@connection || self.class.connection
|
61
|
+
end
|
62
|
+
|
63
|
+
def connection=(val)
|
64
|
+
@connection
|
65
|
+
end
|
66
|
+
|
67
|
+
# Todo: Needed?
|
68
|
+
def instance
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def klass
|
73
|
+
self.class
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Restly::Base::MassAssignmentSecurity
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
def attr_accessible(*args)
|
7
|
+
options = args.dup.extract_options!
|
8
|
+
if options[:from_spec]
|
9
|
+
before_initialize do
|
10
|
+
self._accessible_attributes = spec[:actions].map { |action| action['parameters'] }.flatten
|
11
|
+
end
|
12
|
+
else
|
13
|
+
super(*args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Restly::Base::Resource::Finders
|
2
|
+
|
3
|
+
def find(*args)
|
4
|
+
options = args.extract_options!
|
5
|
+
|
6
|
+
#params[pagination_options[:params][:page]] = options[:page] if pagination
|
7
|
+
instance_from_response connection.get(path_with_format(args.first), params: params)
|
8
|
+
end
|
9
|
+
|
10
|
+
def all
|
11
|
+
collection_from_response connection.get(path_with_format, params: params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create(attributes = nil, options = {})
|
15
|
+
instance = self.new(attributes, options)
|
16
|
+
instance.save
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def collection_from_response(response)
|
22
|
+
raise Restly::Error::InvalidResponse unless response.is_a? OAuth2::Response
|
23
|
+
Restly::Collection.new self, nil, response: response
|
24
|
+
end
|
25
|
+
|
26
|
+
def instance_from_response(response)
|
27
|
+
new(nil, response: response, connection: connection)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :from_response, :instance_from_response
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Restly::Base::Resource
|
2
|
+
extend ActiveSupport::Autoload
|
3
|
+
autoload :Finders
|
4
|
+
|
5
|
+
include Restly::Base::GenericMethods
|
6
|
+
include Finders
|
7
|
+
|
8
|
+
# OPTIONS FOR /:path
|
9
|
+
# Fetches the spec of a remote resource
|
10
|
+
def spec(path=self.path)
|
11
|
+
begin
|
12
|
+
parsed_response = authorize(client_token).connection.request(:options, path, params: params).parsed
|
13
|
+
(parsed_response || {}).with_indifferent_access
|
14
|
+
rescue OAuth2::Error
|
15
|
+
raise Restly::Error::InvalidSpec, "Unable to load the specification for #{self.class}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Restly::Base::WriteCallbacks
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_save :format_request
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def format_request
|
11
|
+
@request_body = case format.to_sym
|
12
|
+
when :json
|
13
|
+
savable_resource.to_json
|
14
|
+
when :xml
|
15
|
+
savable_resource.to_xml
|
16
|
+
else
|
17
|
+
savable_resource.to_param
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def savable_resource
|
22
|
+
{resource_name => attributes_with_present_values}
|
23
|
+
end
|
24
|
+
|
25
|
+
def attributes_with_present_values(attributes=self.attributes)
|
26
|
+
attributes.as_json.reduce({}) do |hash, (key, val)|
|
27
|
+
if val.is_a?(Hash)
|
28
|
+
hash[key] = attributes_with_present_values(val)
|
29
|
+
elsif val.present? && key.to_sym != :id
|
30
|
+
hash[key] = val
|
31
|
+
end
|
32
|
+
hash
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
data/lib/restly/base.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
module Restly
|
2
|
+
class Base
|
3
|
+
# Autoload
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
#autoload :Pagination # Todo!
|
6
|
+
autoload :Resource
|
7
|
+
autoload :Instance
|
8
|
+
autoload :Collection
|
9
|
+
autoload :GenericMethods
|
10
|
+
autoload :Includes
|
11
|
+
autoload :WriteCallbacks
|
12
|
+
autoload :MassAssignmentSecurity
|
13
|
+
autoload :Fields
|
14
|
+
|
15
|
+
# Thread Local Accessor
|
16
|
+
extend Restly::ThreadLocal
|
17
|
+
|
18
|
+
# Active Model
|
19
|
+
extend ActiveModel::Naming
|
20
|
+
extend ActiveModel::Callbacks
|
21
|
+
extend ActiveModel::Translation
|
22
|
+
extend ActiveModel::Callbacks
|
23
|
+
include ActiveModel::MassAssignmentSecurity
|
24
|
+
include ActiveModel::Conversion
|
25
|
+
include ActiveModel::Dirty
|
26
|
+
include ActiveModel::Observing
|
27
|
+
include ActiveModel::Validations
|
28
|
+
include ActiveModel::Serialization
|
29
|
+
include ActiveModel::Serializers::JSON
|
30
|
+
include ActiveModel::Serializers::Xml
|
31
|
+
|
32
|
+
# Set Up Callbacks
|
33
|
+
define_model_callbacks :create, :save, :delete, :update, :initialize
|
34
|
+
|
35
|
+
# Actions & Callbacks
|
36
|
+
extend Resource
|
37
|
+
include Includes
|
38
|
+
include Instance
|
39
|
+
include Fields
|
40
|
+
include MassAssignmentSecurity
|
41
|
+
include WriteCallbacks
|
42
|
+
|
43
|
+
# Relationships
|
44
|
+
include Restly::Associations
|
45
|
+
|
46
|
+
# Set up the Attributes
|
47
|
+
thread_local_accessor :current_token
|
48
|
+
class_attribute :resource_name,
|
49
|
+
:path,
|
50
|
+
:include_root_in_json,
|
51
|
+
:params,
|
52
|
+
:cache,
|
53
|
+
:cache_options,
|
54
|
+
:client_token
|
55
|
+
|
56
|
+
self.include_root_in_json = Restly::Configuration.include_root_in_json
|
57
|
+
self.cache = Restly::Configuration.cache
|
58
|
+
self.cache_options = Restly::Configuration.cache_options
|
59
|
+
self.params = {}
|
60
|
+
self.current_token = {}
|
61
|
+
self.client_token = client.client_credentials.get_token rescue nil
|
62
|
+
|
63
|
+
# Set Defaults on Inheritance
|
64
|
+
inherited do
|
65
|
+
field :id
|
66
|
+
self.resource_name = name.gsub(/.*::/,'').underscore
|
67
|
+
self.path = resource_name.pluralize
|
68
|
+
self.params = params.dup
|
69
|
+
end
|
70
|
+
|
71
|
+
# Run Active Support Load Hooks
|
72
|
+
ActiveSupport.run_load_hooks(:restly, self)
|
73
|
+
|
74
|
+
delegate :client, to: :klass
|
75
|
+
|
76
|
+
# Alias the class for delegation
|
77
|
+
def klass
|
78
|
+
self.class
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Restly::Client < OAuth2::Client
|
2
|
+
|
3
|
+
attr_accessor :id, :secret, :site
|
4
|
+
attr_reader :format
|
5
|
+
|
6
|
+
def initialize(*args, &block)
|
7
|
+
opts = args.extract_options!
|
8
|
+
self.id = args[0] || Restly::Configuration.client_id
|
9
|
+
self.secret = args[1] || Restly::Configuration.client_secret
|
10
|
+
self.site = opts.delete(:site) || Restly::Configuration.site
|
11
|
+
self.options = Restly::Configuration.client_options.merge(opts)
|
12
|
+
self.ssl = opts.delete(:ssl) || Restly::Configuration.ssl
|
13
|
+
self.format = @format = opts.delete(:format) || Restly::Configuration.default_format
|
14
|
+
self.options[:connection_build] = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def ssl=(val)
|
18
|
+
self.options[:connection_opts][:ssl] = val if val
|
19
|
+
end
|
20
|
+
|
21
|
+
def format=(val)
|
22
|
+
self.options[:connection_opts][:headers] = {
|
23
|
+
"Accept" => "application/#{format}",
|
24
|
+
"Content-Type" => "application/#{format}"
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
Restly::Configuration.client_options.keys.each do |m|
|
29
|
+
define_method "#{m}=" do |val|
|
30
|
+
self.options[m] = val
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Restly::Base::Collection::Pagination
|
2
|
+
|
3
|
+
def page(num)
|
4
|
+
with_params(page: num, per_page: per_page).all.paginate(page: num, per_page: per_page)
|
5
|
+
end
|
6
|
+
|
7
|
+
def current_page
|
8
|
+
pagination[:current_page] || pagination[:page]
|
9
|
+
end
|
10
|
+
|
11
|
+
def per_page
|
12
|
+
@pagination_opts[:per_page] || pagination[:per_page]
|
13
|
+
end
|
14
|
+
|
15
|
+
def response_per_page
|
16
|
+
pagination[:per_page]
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_pages
|
20
|
+
pagination[:total_pages]
|
21
|
+
end
|
22
|
+
|
23
|
+
def total_entries
|
24
|
+
pagination[:total_entries]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def pagination
|
30
|
+
parsed = @response.parsed || {}
|
31
|
+
pagination = parsed[:pagination] || parsed
|
32
|
+
pagination.select!{ |k,v| /page|current_page|entries|total_entries|per_page|total_pages|total/ =~ k.to_s }
|
33
|
+
pagination.with_indifferent_access
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Restly::Collection < Array
|
2
|
+
extend ActiveSupport::Autoload
|
3
|
+
|
4
|
+
delegate :resource_name, :connection, to: :resource
|
5
|
+
|
6
|
+
autoload :Pagination
|
7
|
+
|
8
|
+
attr_reader :resource
|
9
|
+
|
10
|
+
def initialize(resource, array, opts={})
|
11
|
+
@resource = resource
|
12
|
+
@response = opts[:response]
|
13
|
+
array = items_from_response if @response.is_a?(OAuth2::Response)
|
14
|
+
super(array)
|
15
|
+
end
|
16
|
+
|
17
|
+
def paginate(opts={})
|
18
|
+
@pagination_opts = opts
|
19
|
+
collection = self.dup
|
20
|
+
collection.extend(Restly::Collection::Pagination)
|
21
|
+
return page(opts[:page]) unless opts[:page] == current_page && opts[:per_page] == response_per_page
|
22
|
+
collection
|
23
|
+
end
|
24
|
+
|
25
|
+
def <<(instance)
|
26
|
+
raise Restly::Error::InvalidObject, "Object is not an instance of #{resource}" unless instance.is_a?(resource)
|
27
|
+
super(instance)
|
28
|
+
end
|
29
|
+
|
30
|
+
def serializable_hash(options = nil)
|
31
|
+
self.collect do |i|
|
32
|
+
i.serializable_hash(options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def items_from_response
|
39
|
+
parsed = @response.parsed || {}
|
40
|
+
parsed = parsed[resource_name.pluralize] if parsed.is_a?(Hash) && parsed[resource_name.pluralize]
|
41
|
+
parsed.collect do |instance|
|
42
|
+
instance = instance[resource_name] if instance[resource_name]
|
43
|
+
resource.new(instance, connection: connection)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Restly::Configuration
|
2
|
+
|
3
|
+
def self.config
|
4
|
+
defaults = {
|
5
|
+
session_key: :access_token,
|
6
|
+
load_middleware: true,
|
7
|
+
:authorize_url => '/oauth/authorize',
|
8
|
+
:token_url => '/oauth/token',
|
9
|
+
:token_method => :post,
|
10
|
+
:connection_opts => {},
|
11
|
+
:max_redirects => 5,
|
12
|
+
:raise_errors => true
|
13
|
+
}
|
14
|
+
defaults.merge(@config || {})
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.client_options
|
18
|
+
config.select do |k,v|
|
19
|
+
[ :authorize_url,
|
20
|
+
:token_url,
|
21
|
+
:token_method,
|
22
|
+
:connection_opts,
|
23
|
+
:max_redirects,
|
24
|
+
:raise_errors
|
25
|
+
].include?(k.to_sym)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.load_config(hash)
|
30
|
+
@config = hash
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.method_missing(m, *args, &block)
|
34
|
+
config.with_indifferent_access[m]
|
35
|
+
end
|
36
|
+
|
37
|
+
def respond_to_missing?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
load_config YAML.load_file(File.join(Rails.root, 'config', 'restly.yml'))[Rails.env] if defined? Rails
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
class Restly::Connection < OAuth2::AccessToken
|
2
|
+
|
3
|
+
attr_accessor :cache, :cache_options
|
4
|
+
|
5
|
+
# TODO: Refactor with subclasses that have their own tokenize methods.
|
6
|
+
def self.tokenize(client, object)
|
7
|
+
|
8
|
+
if object.is_a?(Hash)
|
9
|
+
from_hash(client, object)
|
10
|
+
|
11
|
+
elsif object.is_a?(Rack::Request)
|
12
|
+
from_rack_request(client, object)
|
13
|
+
|
14
|
+
elsif object.is_a?(OAuth2::AccessToken)
|
15
|
+
from_token_object(client, object)
|
16
|
+
|
17
|
+
elsif object.is_a?(Restly::Middleware)
|
18
|
+
|
19
|
+
/(?<token>\w+)$/i =~ object.env['HTTP_AUTHORIZATION']
|
20
|
+
return from_rack_request(client, object) if token
|
21
|
+
token_hash = object.env['rack.session'][Restly::Configuration.session_key] || {}
|
22
|
+
from_hash(client, token_hash)
|
23
|
+
|
24
|
+
else
|
25
|
+
new(client, nil)
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(client, token, opts={})
|
32
|
+
self.cache_options = opts[:cache_options] || {}
|
33
|
+
self.cache = opts[:cache]
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.from_hash(client, token_hash)
|
38
|
+
super(client, token_hash.dup)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.from_rack_request(client, rack_request)
|
42
|
+
/(?<token>\w+)$/i =~ rack_request.env['HTTP_AUTHORIZATION']
|
43
|
+
from_hash(client, { access_token: token })
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.from_token_object(client, token_object)
|
47
|
+
from_hash(client, {
|
48
|
+
access_token: token_object.token,
|
49
|
+
refresh_token: token_object.refresh_token,
|
50
|
+
expires_at: token_object.expires_at
|
51
|
+
})
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_hash
|
55
|
+
{
|
56
|
+
access_token: token,
|
57
|
+
refresh_token: refresh_token,
|
58
|
+
expires_at: expires_at
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
alias_method :forced_request, :request
|
63
|
+
|
64
|
+
def request(verb, path, opts={}, &block)
|
65
|
+
if cache && !opts[:force]
|
66
|
+
cached_request(verb, path, opts, &block)
|
67
|
+
else
|
68
|
+
forced_request(verb, path, opts, &block)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def cached_request(verb, path, opts={}, &block)
|
75
|
+
options_hash = { verb: verb, token: token, opts: opts, block: block }
|
76
|
+
options_packed = [Marshal.dump(options_hash)].pack('m')
|
77
|
+
options_hex = Digest::MD5.hexdigest(options_packed)
|
78
|
+
cache_key = [path.parameterize, options_hex].join('_')
|
79
|
+
|
80
|
+
# Force a cache miss for all methods except get
|
81
|
+
cache_options[:force] = true unless [:get, :options].include?(verb)
|
82
|
+
|
83
|
+
# Set the response
|
84
|
+
response = Rails.cache.fetch cache_key, cache_options.symbolize_keys do
|
85
|
+
|
86
|
+
Rails.cache.delete_matched("#{path.parameterize}*") if ![:get, :options].include?(verb)
|
87
|
+
opts.merge!({force: true})
|
88
|
+
request(verb, path, opts, &block)
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
# Clear the cache if there is an error
|
93
|
+
Rails.cache.delete(cache_key) and puts "deleted cache for: #{verb} #{path}" if response.error
|
94
|
+
|
95
|
+
# Return the response
|
96
|
+
response
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Restly::ControllerMethods
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
|
6
|
+
def auth_token
|
7
|
+
@oauth_token ||= session[Restly::Configuration.session_key].try(:dup) || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def auth_token=(token_object)
|
11
|
+
session[Restly::Configuration.session_key] = Restly::Connection.tokenize(Restly::Base.client, token_object).to_hash
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/restly/error.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Restly::Error
|
2
|
+
|
3
|
+
class NotAnRestly < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class RecordNotFound < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class WrongResourceType < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class InvalidParentAssociation < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
class InvalidJoinerAssociation < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvalidObject < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
class InvalidToken < StandardError
|
22
|
+
end
|
23
|
+
|
24
|
+
class InvalidConnection < StandardError
|
25
|
+
end
|
26
|
+
|
27
|
+
class InvalidSpec < StandardError
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Restly::Middleware
|
2
|
+
|
3
|
+
attr_reader :app, :env
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
@env = env
|
11
|
+
Restly::Base.current_token = Restly::Connection.tokenize(Restly::Base.client, self).to_hash
|
12
|
+
self.app.call(self.env)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Restly::NestedAttributes
|
2
|
+
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :resource_nested_attributes_options, :instance_writer => false
|
7
|
+
self.resource_nested_attributes_options = {}
|
8
|
+
|
9
|
+
inherited do
|
10
|
+
self.resource_nested_attributes_options = resource_nested_attributes_options.dup
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
# One To One Association
|
16
|
+
def assign_nested_attributes_for_one_to_one_resource_association(association_name, attributes, assignment_opts = {})
|
17
|
+
options = self.nested_attributes_options[association_name]
|
18
|
+
association_attributes = attributes.delete("#{association_name}_attributes") || {}
|
19
|
+
associated_instance = send(association_name) || self.class.reflect_on_resource_association(association_name).build
|
20
|
+
associated_instance.attributes = association_attributes
|
21
|
+
end
|
22
|
+
|
23
|
+
# Collection Association
|
24
|
+
def assign_nested_attributes_for_collection_resource_association(association_name, attributes_collection, assignment_opts = {})
|
25
|
+
|
26
|
+
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
27
|
+
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
28
|
+
end
|
29
|
+
|
30
|
+
if attributes_collection.is_a? Hash
|
31
|
+
keys = attributes_collection.keys
|
32
|
+
attributes_collection = if keys.include?('id') || keys.include?(:id)
|
33
|
+
Array.wrap(attributes_collection)
|
34
|
+
else
|
35
|
+
attributes_collection.values
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
association = self.class.reflect_on_resource_association(association_name)
|
40
|
+
existing_records = send(association_name)
|
41
|
+
|
42
|
+
attributes_collection.each do |attributes|
|
43
|
+
attributes = attributes.with_indifferent_access
|
44
|
+
if attributes[:id].blank?
|
45
|
+
send(association_name) << association.build(attributes.except(:id))
|
46
|
+
elsif existing_record = existing_records.find{ |record| record.id.to_s == attributes['id'].to_s }
|
47
|
+
existing_record.attributes = attributes
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_nested_attributes_for_save
|
54
|
+
@attributes = @attributes.inject(HashWithIndifferentAccess.new) do |hash, (k, v)|
|
55
|
+
k = [resource_nested_attributes_options[k.to_sym][:write_prefix], k, resource_nested_attributes_options[k.to_sym][:write_suffix]].compact.join('_') if resource_nested_attributes_options[k.to_sym].present?
|
56
|
+
hash[k] = v
|
57
|
+
hash
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module ClassMethods
|
62
|
+
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
|
63
|
+
|
64
|
+
def accepts_nested_attributes_for_resource(*attr_names)
|
65
|
+
options = { :allow_destroy => false, :update_only => false, :write_prefix => nil, :write_suffix => 'attributes' }
|
66
|
+
options.update(attr_names.extract_options!)
|
67
|
+
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only, :write_prefix, :write_suffix)
|
68
|
+
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
69
|
+
|
70
|
+
before_save :set_nested_attributes_for_save
|
71
|
+
|
72
|
+
attr_names.each do |association_name|
|
73
|
+
if reflection = reflect_on_resource_association(association_name)
|
74
|
+
reflection.options[:autosave] = true
|
75
|
+
|
76
|
+
resource_nested_attributes_options = self.resource_nested_attributes_options.dup
|
77
|
+
resource_nested_attributes_options[association_name.to_sym] = options
|
78
|
+
self.resource_nested_attributes_options = resource_nested_attributes_options
|
79
|
+
|
80
|
+
type = (reflection.collection? ? :collection : :one_to_one)
|
81
|
+
|
82
|
+
if method_defined?("#{association_name}_attributes=")
|
83
|
+
remove_method("#{association_name}_attributes=")
|
84
|
+
end
|
85
|
+
|
86
|
+
define_method "#{association_name}_attributes=" do |attributes|
|
87
|
+
send("assign_nested_attributes_for_#{type}_resource_association", association_name.to_sym, attributes, mass_assignment_options)
|
88
|
+
end
|
89
|
+
|
90
|
+
else
|
91
|
+
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|