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
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Jason Waldrip
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Restly
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'restly'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install restly
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Restly::Associations::Base
|
2
|
+
|
3
|
+
attr_reader :name, :association_class, :namespace, :polymorphic, :options
|
4
|
+
|
5
|
+
def initialize(owner, name, options={})
|
6
|
+
@name = name
|
7
|
+
@namespace = options.delete(:namespace) || owner.name.gsub(/::\w+$/, '')
|
8
|
+
@polymorphic = options.delete(:polymorphic)
|
9
|
+
options[:class_name] ||= name.to_s.classify
|
10
|
+
@owner = owner
|
11
|
+
@association_class = [@namespace, options.delete(:class_name)].compact.join('::').constantize
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def collection?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def build(*args)
|
20
|
+
new_instance = @association_class.new(*args)
|
21
|
+
new_instance.write_attribute("#{@owner.resource_name}_id") if @association_class.respond_to?("#{@owner.resource_name}_id") && !self.class.send(:reflect_on_resource_association, :custom_pages).embedded?
|
22
|
+
new_instance
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def authorize(klass, authorization = nil)
|
28
|
+
if (!klass.authorized? && @owner.respond_to?(:authorized?) && @owner.authorized?) || authorization
|
29
|
+
klass.authorize(authorization || @owner.connection)
|
30
|
+
else
|
31
|
+
klass
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Restly::Associations::BelongsTo < Restly::Associations::Base
|
2
|
+
|
3
|
+
def find_with_parent(parent, options)
|
4
|
+
options.reverse_merge!(self.options)
|
5
|
+
association_class = polymorphic ? [@namespace, instance.send("#{name}_type")] : self.association_class
|
6
|
+
association_class = authorize(association_class, options[:authorize])
|
7
|
+
instance = association_class.find(parent.attributes["#{name}_id"])
|
8
|
+
Restly::Proxies::Associations::Instance.new(instance, parent)
|
9
|
+
end
|
10
|
+
|
11
|
+
def collection?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Restly::Associations::Builder
|
2
|
+
|
3
|
+
def build(relationship, opts)
|
4
|
+
|
5
|
+
# Base Model
|
6
|
+
model = opts[:class_name] || relationship.to_s.singularize.camelize
|
7
|
+
|
8
|
+
# Namespace
|
9
|
+
namespace = opts[:namespace] || self.class.name.gsub(/::\w+$/, '')
|
10
|
+
model = [namespace, model].compact.join('::')
|
11
|
+
|
12
|
+
# Polymorphic Relationships
|
13
|
+
if opts[:polymorphic]
|
14
|
+
polymorphic_type = send(:"#{resource}_type")
|
15
|
+
model = [namespace, polymorphic_type].compact.join('::')
|
16
|
+
end
|
17
|
+
|
18
|
+
# Constantize
|
19
|
+
model = model.constantize
|
20
|
+
|
21
|
+
# Auto-authorization, fail with error!
|
22
|
+
if (!model.authorized? && self.respond_to?(:authorized?) && self.authorized?) || (opts[:authorize])
|
23
|
+
model.authorize(opts[:authorize] || self.connection)
|
24
|
+
else
|
25
|
+
model
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Restly::Associations::EmbeddableResources
|
2
|
+
extend ActiveSupport::Autoload
|
3
|
+
|
4
|
+
autoload :EmbeddedIn
|
5
|
+
autoload :EmbedsMany
|
6
|
+
autoload :EmbedsOne
|
7
|
+
|
8
|
+
# Embeds One
|
9
|
+
def embeds_resource(name, options = {})
|
10
|
+
exclude_field(name) if ancestors.include?(Restly::Base)
|
11
|
+
self.resource_associations[name] = association = EmbedsOne.new(self, name, options)
|
12
|
+
|
13
|
+
define_method name do
|
14
|
+
association.build(attributes[name])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Embeds Many
|
19
|
+
def embeds_resources(name, options = {})
|
20
|
+
exclude_field(name) if ancestors.include?(Restly::Base)
|
21
|
+
self.resource_associations[name] = association = EmbedsMany.new(self, name, options)
|
22
|
+
|
23
|
+
define_method name do
|
24
|
+
( self.attributes[name] || [] ).map!{ |i| association.build(i) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Restly::Associations::HasMany < Restly::Associations::Base
|
2
|
+
|
3
|
+
attr_reader :joiner
|
4
|
+
|
5
|
+
def initialize(owner, name, options={})
|
6
|
+
@joiner = options.delete(:through)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def scope_with_parent(parent, options)
|
11
|
+
options.reverse_merge!(self.options)
|
12
|
+
association_class = polymorphic ? [@namespace, instance.send("#{name}_type")] : self.association_class
|
13
|
+
association_class = authorize(association_class, options[:authorize])
|
14
|
+
collection = association_class.with_params("with_#{parent.resource_name}_id" => parent.id).all # (parent.attributes["#{name}_id"])
|
15
|
+
collection.select{|i| i.attributes["#{parent.resource_name}_id"] == parent.id }
|
16
|
+
Restly::Proxies::Associations::Collection.new(collection, parent)
|
17
|
+
end
|
18
|
+
|
19
|
+
def collection?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Restly::Associations::HasOne < Restly::Associations::Base
|
2
|
+
|
3
|
+
attr_reader :joiner
|
4
|
+
|
5
|
+
def initialize(owner, name, options={})
|
6
|
+
@joiner = options.delete(:through)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def scope_with_parent(parent, options)
|
11
|
+
options.reverse_merge!(self.options)
|
12
|
+
association_class = polymorphic ? [@namespace, instance.send("#{name}_type")] : self.association_class
|
13
|
+
association_class = authorize(association_class, options[:authorize])
|
14
|
+
collection = association_class.with_params("with_#{parent.resource_name}_id" => parent.id).all(parent.attributes["#{name}_id"])
|
15
|
+
collection.select{|i| i.attributes["#{parent.resource_name}_id"] == parent.id }.first
|
16
|
+
Restly::Proxies::Associations::Instance.new(instance, parent)
|
17
|
+
end
|
18
|
+
|
19
|
+
def collection?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Restly::Associations
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :Base
|
6
|
+
autoload :BelongsTo
|
7
|
+
autoload :HasMany
|
8
|
+
autoload :HasManyThrough
|
9
|
+
autoload :HasOne
|
10
|
+
autoload :HasOneThrough
|
11
|
+
autoload :EmbeddableResources
|
12
|
+
|
13
|
+
class AssociationHash < HashWithIndifferentAccess
|
14
|
+
end
|
15
|
+
|
16
|
+
included do
|
17
|
+
|
18
|
+
extend EmbeddableResources if self == Restly::Base
|
19
|
+
include Restly::NestedAttributes
|
20
|
+
|
21
|
+
delegate :resource_name, to: :klass
|
22
|
+
class_attribute :resource_associations, instance_reader: false, instance_writer: false
|
23
|
+
self.resource_associations = AssociationHash.new
|
24
|
+
|
25
|
+
inherited do
|
26
|
+
self.resource_associations = resource_associations.dup
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def stub_associations_from_response(response=self.response)
|
32
|
+
parsed = response.parsed || {}
|
33
|
+
parsed = parsed[resource_name] if parsed.is_a?(Hash) && parsed[resource_name]
|
34
|
+
associations = parsed.select{ |i| klass.reflect_on_all_resource_associations.keys.include?(i) }
|
35
|
+
associations.each do |relationship|
|
36
|
+
relationship.collection?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def klass
|
41
|
+
self.class
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
|
46
|
+
def resource_name
|
47
|
+
name.gsub(/.*::/,'').underscore
|
48
|
+
end
|
49
|
+
|
50
|
+
def reflect_on_all_resource_associations
|
51
|
+
resource_associations
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Belongs to
|
57
|
+
def belongs_to_resource(name, options = {})
|
58
|
+
exclude_field(name) if ancestors.include?(Restly::Base)
|
59
|
+
self.resource_associations[name] = association = BelongsTo.new(self, name, options)
|
60
|
+
define_method name do |options={}|
|
61
|
+
association.find_with_parent(self, options)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Has One
|
66
|
+
def has_one_resource(name, options = {})
|
67
|
+
exclude_field(name) if ancestors.include?(Restly::Base)
|
68
|
+
self.resource_associations[name] = association = HasOne.new(self, name, options)
|
69
|
+
define_method name do |options={}|
|
70
|
+
association.scope_with_parent(self, options)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Has One
|
75
|
+
def has_many_resources(name, options = {})
|
76
|
+
exclude_field(name) if ancestors.include?(Restly::Base)
|
77
|
+
self.resource_associations[name] = association = HasMany.new(self, name, options)
|
78
|
+
define_method name do |options={}|
|
79
|
+
association.scope_with_parent(self, options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def reflect_on_resource_association(association_name)
|
85
|
+
reflect_on_all_resource_associations[association_name]
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Restly::Base::Fields
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
extend SharedMethods
|
6
|
+
include SharedMethods
|
7
|
+
extend ClassMethods
|
8
|
+
|
9
|
+
class_attribute :fields
|
10
|
+
self.fields = FieldSet.new
|
11
|
+
field :id
|
12
|
+
|
13
|
+
inherited do
|
14
|
+
self.fields = fields.dup
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
module SharedMethods
|
20
|
+
|
21
|
+
def set_field(attr)
|
22
|
+
base = self.is_a?(Class) ? self : singleton_class
|
23
|
+
unless base.send :instance_method_already_implemented?, attr
|
24
|
+
base.send :define_attribute_method, attr
|
25
|
+
self.fields += [attr]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
|
33
|
+
def field(attr)
|
34
|
+
if attr.is_a?(Hash) && attr[:from_spec]
|
35
|
+
before_initialize do
|
36
|
+
spec[:attributes].each{ |attr| set_field(attr) }
|
37
|
+
end
|
38
|
+
elsif attr.is_a?(Symbol) || attr.is_a?(String)
|
39
|
+
set_field(attr)
|
40
|
+
else
|
41
|
+
raise Restly::Error::InvalidField, "field must be a symbol or string."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def exclude_field(name)
|
46
|
+
|
47
|
+
# Remove from the class
|
48
|
+
self.fields -= [name]
|
49
|
+
|
50
|
+
# Remove from the instance
|
51
|
+
before_initialize do
|
52
|
+
self.fields -= [name]
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
class FieldSet < Set
|
60
|
+
|
61
|
+
def include?(value)
|
62
|
+
super(value.to_sym)
|
63
|
+
end
|
64
|
+
|
65
|
+
def <<(value)
|
66
|
+
super(value.to_sym)
|
67
|
+
end
|
68
|
+
|
69
|
+
def +(other_arry)
|
70
|
+
super(other_arry.map(&:to_sym))
|
71
|
+
end
|
72
|
+
|
73
|
+
def -(other_arry)
|
74
|
+
super(other_arry.map(&:to_sym))
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Restly::Base::GenericMethods
|
2
|
+
|
3
|
+
def authorize(token_object)
|
4
|
+
Restly::Proxies::Auth.new self, token_object
|
5
|
+
end
|
6
|
+
|
7
|
+
def with_params(params={})
|
8
|
+
Restly::Proxies::Params.new self, params
|
9
|
+
end
|
10
|
+
|
11
|
+
def path_with_format(*args)
|
12
|
+
path = [self.path, args].flatten.compact.join('/')
|
13
|
+
[path, format].compact.join('.') if path
|
14
|
+
end
|
15
|
+
|
16
|
+
def format
|
17
|
+
client.format
|
18
|
+
end
|
19
|
+
|
20
|
+
def authorized?
|
21
|
+
connection.token.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Restly::Base::Includes
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
extend ClassMethods
|
6
|
+
|
7
|
+
class_attribute :inherited_callbacks
|
8
|
+
self.inherited_callbacks = []
|
9
|
+
|
10
|
+
inherited do
|
11
|
+
self.inherited_callbacks = inherited_callbacks
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
# Delegate stuff to client
|
19
|
+
delegate :site, :site=, :format, :format=, to: :client
|
20
|
+
|
21
|
+
def client
|
22
|
+
@client ||= Restly::Client.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def connection
|
26
|
+
connection = @connection || Restly::Connection.tokenize(client, current_token)
|
27
|
+
connection.cache ||= cache
|
28
|
+
connection.cache_options ||= cache_options
|
29
|
+
connection
|
30
|
+
end
|
31
|
+
|
32
|
+
def connection=(connection)
|
33
|
+
raise InvalidConnection, "#{connection} is not a valid Restly::Connection" unless connection.is_a?(Restly::Connection)
|
34
|
+
@connection = connection
|
35
|
+
end
|
36
|
+
|
37
|
+
def param_key
|
38
|
+
resource_name
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def inherited(subclass = nil, &block)
|
44
|
+
self.inherited_callbacks << block and return if block_given?
|
45
|
+
|
46
|
+
inherited_callbacks.each do |call_block|
|
47
|
+
subclass.class_eval(&call_block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Restly::Base::Instance::Actions
|
2
|
+
|
3
|
+
def save
|
4
|
+
run_callbacks :save do
|
5
|
+
@previously_changed = changes
|
6
|
+
@changed_attributes.clear
|
7
|
+
new_record? ? create : update
|
8
|
+
end
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def delete
|
13
|
+
run_callbacks :delete do
|
14
|
+
response = connection.delete(path_with_format, params: params)
|
15
|
+
false
|
16
|
+
freeze
|
17
|
+
end
|
18
|
+
response.status < 300
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def update
|
24
|
+
run_callbacks :update do
|
25
|
+
set_attributes_from_response(connection.put path_with_format, body: @request_body, params: params)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def create
|
30
|
+
run_callbacks :create do
|
31
|
+
set_attributes_from_response(connection.post path_with_format, body: @request_body, params: params)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Restly::Base::Instance::Attributes
|
2
|
+
|
3
|
+
def update_attributes(attributes)
|
4
|
+
self.attributes = attributes
|
5
|
+
save
|
6
|
+
end
|
7
|
+
|
8
|
+
def attributes=(attributes)
|
9
|
+
attributes.each do |k, v|
|
10
|
+
write_attribute k, v
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def attributes
|
15
|
+
nil_values = fields.inject({}) do |hash, key|
|
16
|
+
hash[key] = nil
|
17
|
+
hash
|
18
|
+
end
|
19
|
+
@attributes.reverse_merge!(nil_values)
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_attribute(attr, val)
|
23
|
+
if fields.include?(attr)
|
24
|
+
send("#{attr}_will_change!".to_sym) unless val == @attributes[attr.to_sym] || !@loaded
|
25
|
+
@attributes[attr.to_sym] = val
|
26
|
+
else
|
27
|
+
puts "WARNING: Attribute `#{attr}` not written. ".colorize(:yellow) +
|
28
|
+
"To fix this add the following the the model. -- field :#{attr}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_attribute(attr)
|
33
|
+
raise NoMethodError, "undefined method #{attr} for #{klass}" unless fields.include?(attr)
|
34
|
+
attributes[attr.to_sym]
|
35
|
+
end
|
36
|
+
|
37
|
+
alias :attribute :read_attribute
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
inspection = if @attributes
|
41
|
+
fields.collect { |name|
|
42
|
+
"#{name}: #{attribute_for_inspect(name)}"
|
43
|
+
}.compact.join(", ")
|
44
|
+
else
|
45
|
+
"not initialized"
|
46
|
+
end
|
47
|
+
"#<#{self.class} #{inspection}>"
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_attribute?(attr)
|
51
|
+
attribute(attr)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def attribute_for_inspect(attr_name)
|
57
|
+
value = attribute(attr_name)
|
58
|
+
if value.is_a?(String) && value.length > 50
|
59
|
+
"#{value[0..50]}...".inspect
|
60
|
+
else
|
61
|
+
value.inspect
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_attributes_from_response(response=self.response)
|
66
|
+
parsed = response.parsed || {}
|
67
|
+
parsed = parsed[resource_name] if parsed.is_a?(Hash) && parsed[resource_name]
|
68
|
+
self.attributes = parsed
|
69
|
+
end
|
70
|
+
|
71
|
+
def method_missing(m, *args, &block)
|
72
|
+
if !!(/(?<attr>\w+)(?<setter>=)?$/ =~ m.to_s) && fields.include?(m)
|
73
|
+
case !!setter
|
74
|
+
when true
|
75
|
+
write_attribute(m, *args)
|
76
|
+
when false
|
77
|
+
read_attribute(m)
|
78
|
+
end
|
79
|
+
else
|
80
|
+
super(m, *args, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def respond_to_missing?(method_name, include_private = false)
|
85
|
+
!!(/(?<attr>\w+)=?$/ =~ method_name.to_s) && fields.include?(method_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Restly::Base::Instance::Persistence
|
2
|
+
|
3
|
+
def exists?
|
4
|
+
status = @response.try(:status).to_i
|
5
|
+
status < 300 && status >= 200
|
6
|
+
end
|
7
|
+
|
8
|
+
def persisted?
|
9
|
+
exists? && !changed?
|
10
|
+
end
|
11
|
+
|
12
|
+
def new_record?
|
13
|
+
!exists?
|
14
|
+
end
|
15
|
+
|
16
|
+
def reload!
|
17
|
+
self.class.send :instance_from_response, connection.get(path, force: true)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|