restly 0.0.1.alpha.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 +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
|