her5 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.travis.yml +17 -0
- data/.yardopts +2 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +10 -0
- data/LICENSE +7 -0
- data/README.md +1017 -0
- data/Rakefile +11 -0
- data/UPGRADE.md +101 -0
- data/gemfiles/Gemfile.activemodel-3.2.x +7 -0
- data/gemfiles/Gemfile.activemodel-4.0 +7 -0
- data/gemfiles/Gemfile.activemodel-4.1 +7 -0
- data/gemfiles/Gemfile.activemodel-4.2 +7 -0
- data/gemfiles/Gemfile.activemodel-5.0.x +7 -0
- data/her5.gemspec +30 -0
- data/lib/her.rb +19 -0
- data/lib/her/api.rb +120 -0
- data/lib/her/collection.rb +12 -0
- data/lib/her/errors.rb +104 -0
- data/lib/her/json_api/model.rb +57 -0
- data/lib/her/middleware.rb +12 -0
- data/lib/her/middleware/accept_json.rb +17 -0
- data/lib/her/middleware/first_level_parse_json.rb +36 -0
- data/lib/her/middleware/json_api_parser.rb +68 -0
- data/lib/her/middleware/parse_json.rb +28 -0
- data/lib/her/middleware/second_level_parse_json.rb +36 -0
- data/lib/her/model.rb +75 -0
- data/lib/her/model/associations.rb +141 -0
- data/lib/her/model/associations/association.rb +107 -0
- data/lib/her/model/associations/association_proxy.rb +45 -0
- data/lib/her/model/associations/belongs_to_association.rb +101 -0
- data/lib/her/model/associations/has_many_association.rb +101 -0
- data/lib/her/model/associations/has_one_association.rb +80 -0
- data/lib/her/model/attributes.rb +297 -0
- data/lib/her/model/base.rb +33 -0
- data/lib/her/model/deprecated_methods.rb +61 -0
- data/lib/her/model/http.rb +113 -0
- data/lib/her/model/introspection.rb +65 -0
- data/lib/her/model/nested_attributes.rb +84 -0
- data/lib/her/model/orm.rb +207 -0
- data/lib/her/model/parse.rb +221 -0
- data/lib/her/model/paths.rb +126 -0
- data/lib/her/model/relation.rb +164 -0
- data/lib/her/version.rb +3 -0
- data/spec/api_spec.rb +114 -0
- data/spec/collection_spec.rb +26 -0
- data/spec/json_api/model_spec.rb +305 -0
- data/spec/middleware/accept_json_spec.rb +10 -0
- data/spec/middleware/first_level_parse_json_spec.rb +62 -0
- data/spec/middleware/json_api_parser_spec.rb +32 -0
- data/spec/middleware/second_level_parse_json_spec.rb +35 -0
- data/spec/model/associations/association_proxy_spec.rb +31 -0
- data/spec/model/associations_spec.rb +504 -0
- data/spec/model/attributes_spec.rb +389 -0
- data/spec/model/callbacks_spec.rb +145 -0
- data/spec/model/dirty_spec.rb +91 -0
- data/spec/model/http_spec.rb +158 -0
- data/spec/model/introspection_spec.rb +76 -0
- data/spec/model/nested_attributes_spec.rb +134 -0
- data/spec/model/orm_spec.rb +506 -0
- data/spec/model/parse_spec.rb +345 -0
- data/spec/model/paths_spec.rb +347 -0
- data/spec/model/relation_spec.rb +226 -0
- data/spec/model/validations_spec.rb +42 -0
- data/spec/model_spec.rb +44 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/extensions/array.rb +5 -0
- data/spec/support/extensions/hash.rb +5 -0
- data/spec/support/macros/her_macros.rb +17 -0
- data/spec/support/macros/model_macros.rb +36 -0
- data/spec/support/macros/request_macros.rb +27 -0
- metadata +289 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Her
|
2
|
+
module Model
|
3
|
+
# This module includes basic functionnality to Her::Model
|
4
|
+
module Base
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Returns true if attribute_name is
|
8
|
+
# * in resource attributes
|
9
|
+
# * an association
|
10
|
+
#
|
11
|
+
# @private
|
12
|
+
def has_key?(attribute_name)
|
13
|
+
has_attribute?(attribute_name) ||
|
14
|
+
has_association?(attribute_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns
|
18
|
+
# * the value of the attribute_name attribute if it's in orm data
|
19
|
+
# * the resource/collection corrsponding to attribute_name if it's an association
|
20
|
+
#
|
21
|
+
# @private
|
22
|
+
def [](attribute_name)
|
23
|
+
get_attribute(attribute_name) ||
|
24
|
+
get_association(attribute_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
def singularized_resource_name
|
29
|
+
self.class.name.split('::').last.tableize.singularize
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Her
|
2
|
+
module Model
|
3
|
+
# @private
|
4
|
+
module DeprecatedMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def self.deprecate!(old, new, object, *args)
|
8
|
+
line = begin
|
9
|
+
raise StandardError
|
10
|
+
rescue StandardError => e
|
11
|
+
e.backtrace[2]
|
12
|
+
end
|
13
|
+
|
14
|
+
warn "#{line} - The `#{old}` method is deprecated and may be removed soon. Please update your code with `#{new}` instead."
|
15
|
+
object.send(new, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def data(*args)
|
19
|
+
Her::Model::DeprecatedMethods.deprecate! :data, :attributes, self, *args
|
20
|
+
end
|
21
|
+
|
22
|
+
def data=(*args)
|
23
|
+
Her::Model::DeprecatedMethods.deprecate! :data=, :attributes=, self, *args
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_attributes(*args)
|
27
|
+
Her::Model::DeprecatedMethods.deprecate! :update_attributes, :assign_attributes, self, *args
|
28
|
+
end
|
29
|
+
|
30
|
+
def assign_data(*args)
|
31
|
+
Her::Model::DeprecatedMethods.deprecate! :assign_data, :assign_attributes, self, *args
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_data?(*args)
|
35
|
+
Her::Model::DeprecatedMethods.deprecate! :has_data?, :has_attribute?, self, *args
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_data(*args)
|
39
|
+
Her::Model::DeprecatedMethods.deprecate! :get_data, :get_attribute, self, *args
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
def has_relationship?(*args)
|
44
|
+
Her::Model::DeprecatedMethods.deprecate! :has_relationship?, :has_association?, self, *args
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_relationship(*args)
|
48
|
+
Her::Model::DeprecatedMethods.deprecate! :get_relationship, :get_association, self, *args
|
49
|
+
end
|
50
|
+
|
51
|
+
def relationships(*args)
|
52
|
+
Her::Model::DeprecatedMethods.deprecate! :relationships, :associations, self, *args
|
53
|
+
end
|
54
|
+
|
55
|
+
def her_api(*args)
|
56
|
+
Her::Model::DeprecatedMethods.deprecate! :her_api, :use_api, self, *args
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Her
|
2
|
+
module Model
|
3
|
+
# This module interacts with Her::API to fetch HTTP data
|
4
|
+
module HTTP
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
METHODS = [:get, :post, :put, :patch, :delete]
|
7
|
+
|
8
|
+
# For each HTTP method, define these class methods:
|
9
|
+
#
|
10
|
+
# - <method>(path, params)
|
11
|
+
# - <method>_raw(path, params, &block)
|
12
|
+
# - <method>_collection(path, params, &block)
|
13
|
+
# - <method>_resource(path, params, &block)
|
14
|
+
# - custom_<method>(*paths)
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# class User
|
18
|
+
# include Her::Model
|
19
|
+
# custom_get :active
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# User.get(:popular) # GET "/users/popular"
|
23
|
+
# User.active # GET "/users/active"
|
24
|
+
module ClassMethods
|
25
|
+
# Change which API the model will use to make its HTTP requests
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# secondary_api = Her::API.new :url => "https://api.example" do |connection|
|
29
|
+
# connection.use Faraday::Request::UrlEncoded
|
30
|
+
# connection.use Her::Middleware::DefaultParseJSON
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# class User
|
34
|
+
# include Her::Model
|
35
|
+
# use_api secondary_api
|
36
|
+
# end
|
37
|
+
def use_api(value = nil)
|
38
|
+
@_her_use_api ||= begin
|
39
|
+
superclass.use_api if superclass.respond_to?(:use_api)
|
40
|
+
end
|
41
|
+
|
42
|
+
unless value
|
43
|
+
return (@_her_use_api.respond_to? :call) ? @_her_use_api.call : @_her_use_api
|
44
|
+
end
|
45
|
+
|
46
|
+
@_her_use_api = value
|
47
|
+
end
|
48
|
+
|
49
|
+
alias her_api use_api
|
50
|
+
alias uses_api use_api
|
51
|
+
|
52
|
+
# Main request wrapper around Her::API. Used to make custom request to the API.
|
53
|
+
#
|
54
|
+
# @private
|
55
|
+
def request(params={})
|
56
|
+
request = her_api.request(params)
|
57
|
+
if block_given?
|
58
|
+
yield request[:parsed_data], request[:response]
|
59
|
+
else
|
60
|
+
{ :parsed_data => request[:parsed_data], :response => request[:response] }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
METHODS.each do |method|
|
65
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
66
|
+
def #{method}(path, params={})
|
67
|
+
path = build_request_path_from_string_or_symbol(path, params)
|
68
|
+
params = to_params(params) unless #{method.to_sym.inspect} == :get
|
69
|
+
send(:'#{method}_raw', path, params) do |parsed_data, response|
|
70
|
+
if parsed_data[:data].is_a?(Array) || active_model_serializers_format? || json_api_format?
|
71
|
+
new_collection(parsed_data)
|
72
|
+
else
|
73
|
+
new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def #{method}_raw(path, params={}, &block)
|
79
|
+
path = build_request_path_from_string_or_symbol(path, params)
|
80
|
+
request(params.merge(:_method => #{method.to_sym.inspect}, :_path => path), &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def #{method}_collection(path, params={})
|
84
|
+
path = build_request_path_from_string_or_symbol(path, params)
|
85
|
+
send(:'#{method}_raw', build_request_path_from_string_or_symbol(path, params), params) do |parsed_data, response|
|
86
|
+
new_collection(parsed_data)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def #{method}_resource(path, params={})
|
91
|
+
path = build_request_path_from_string_or_symbol(path, params)
|
92
|
+
send(:"#{method}_raw", path, params) do |parsed_data, response|
|
93
|
+
new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def custom_#{method}(*paths)
|
98
|
+
metaclass = (class << self; self; end)
|
99
|
+
opts = paths.last.is_a?(Hash) ? paths.pop : Hash.new
|
100
|
+
|
101
|
+
paths.each do |path|
|
102
|
+
metaclass.send(:define_method, path) do |*params|
|
103
|
+
params = params.first || Hash.new
|
104
|
+
send(#{method.to_sym.inspect}, path, params)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
RUBY
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Her
|
2
|
+
module Model
|
3
|
+
module Introspection
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
# Inspect an element, returns it for introspection.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# class User
|
9
|
+
# include Her::Model
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# @user = User.find(1)
|
13
|
+
# p @user # => #<User(/users/1) id=1 name="Tobias Fünke">
|
14
|
+
def inspect
|
15
|
+
resource_path = begin
|
16
|
+
request_path
|
17
|
+
rescue Her::Errors::PathError => e
|
18
|
+
"<unknown path, missing `#{e.missing_parameter}`>"
|
19
|
+
end
|
20
|
+
|
21
|
+
"#<#{self.class}(#{resource_path}) #{attributes.keys.map { |k| "#{k}=#{attribute_for_inspect(send(k))}" }.join(" ")}>"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def attribute_for_inspect(value)
|
26
|
+
if value.is_a?(String) && value.length > 50
|
27
|
+
"#{value[0..50]}...".inspect
|
28
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
29
|
+
%("#{value}")
|
30
|
+
else
|
31
|
+
value.inspect
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @private
|
36
|
+
module ClassMethods
|
37
|
+
# Finds a class at the same level as this one or at the global level.
|
38
|
+
#
|
39
|
+
# @private
|
40
|
+
def her_nearby_class(name)
|
41
|
+
her_sibling_class(name) || name.constantize rescue nil
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
# Looks for a class at the same level as this one with the given name.
|
46
|
+
#
|
47
|
+
# @private
|
48
|
+
def her_sibling_class(name)
|
49
|
+
if mod = self.her_containing_module
|
50
|
+
@_her_sibling_class ||= Hash.new { Hash.new }
|
51
|
+
@_her_sibling_class[mod][name] ||= "#{mod.name}::#{name}".constantize rescue nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# If available, returns the containing Module for this class.
|
56
|
+
#
|
57
|
+
# @private
|
58
|
+
def her_containing_module
|
59
|
+
return unless self.name =~ /::/
|
60
|
+
self.name.split("::")[0..-2].join("::").constantize
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Her
|
2
|
+
module Model
|
3
|
+
module NestedAttributes
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def saved_nested_attributes
|
7
|
+
nested_attributes = self.class.saved_nested_associations.each_with_object({}) do |association_name, hash|
|
8
|
+
if association = self.send(association_name)
|
9
|
+
if association.kind_of?(Array)
|
10
|
+
associates = {}
|
11
|
+
association.each_with_index {|a, i|
|
12
|
+
associates[i] = to_params_for_nesting(a)
|
13
|
+
}
|
14
|
+
hash["#{association_name}_attributes".to_sym] = associates
|
15
|
+
else
|
16
|
+
hash["#{association_name}_attributes".to_sym] = to_params_for_nesting(association)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_params_for_nesting(associate)
|
23
|
+
associate_params = associate.to_params
|
24
|
+
associate_params = associate_params[associate.class.included_root_element] if associate.class.include_root_in_json?
|
25
|
+
associate_params['_destroy'] = associate.destroying?
|
26
|
+
associate_params['id'] = associate.id
|
27
|
+
associate_params
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
# Allow nested attributes for an association
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# class User
|
36
|
+
# include Her::Model
|
37
|
+
#
|
38
|
+
# has_one :role
|
39
|
+
# accepts_nested_attributes_for :role
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# class Role
|
43
|
+
# include Her::Model
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# user = User.new(name: "Tobias", role_attributes: { title: "moderator" })
|
47
|
+
# user.role # => #<Role title="moderator">
|
48
|
+
def accepts_nested_attributes_for(*associations)
|
49
|
+
allowed_association_names = association_names
|
50
|
+
|
51
|
+
associations.each do |association_name|
|
52
|
+
unless allowed_association_names.include?(association_name)
|
53
|
+
raise Her::Errors::AssociationUnknownError.new("Unknown association name :#{association_name} in accepts_nested_attributes_for")
|
54
|
+
end
|
55
|
+
|
56
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
57
|
+
if method_defined?(:#{association_name}_attributes=)
|
58
|
+
remove_method(:#{association_name}_attributes=)
|
59
|
+
end
|
60
|
+
|
61
|
+
def #{association_name}_attributes=(attributes)
|
62
|
+
self.#{association_name}.assign_nested_attributes(attributes)
|
63
|
+
end
|
64
|
+
RUBY
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def saved_nested_associations
|
69
|
+
@_her_saved_associations ||= []
|
70
|
+
end
|
71
|
+
|
72
|
+
def sends_nested_attributes_for(*associations)
|
73
|
+
allowed_association_names = association_names
|
74
|
+
associations.each do |association_name|
|
75
|
+
unless allowed_association_names.include?(association_name)
|
76
|
+
raise Her::Errors::AssociationUnknownError.new("Unknown association name :#{association_name} in sends_nested_attributes_for")
|
77
|
+
end
|
78
|
+
saved_nested_associations.push association_name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Her
|
3
|
+
module Model
|
4
|
+
# This module adds ORM-like capabilities to the model
|
5
|
+
module ORM
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Return `true` if a resource was not saved yet
|
9
|
+
def new?
|
10
|
+
id.nil?
|
11
|
+
end
|
12
|
+
alias new_record? new?
|
13
|
+
|
14
|
+
# Return `true` if a resource is not `#new?`
|
15
|
+
def persisted?
|
16
|
+
!new?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return whether the object has been destroyed
|
20
|
+
def destroyed?
|
21
|
+
@destroyed == true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Save a resource and return `false` if the response is not a successful one or
|
25
|
+
# if there are errors in the resource. Otherwise, return the newly updated resource
|
26
|
+
#
|
27
|
+
# @example Save a resource after fetching it
|
28
|
+
# @user = User.find(1)
|
29
|
+
# # Fetched via GET "/users/1"
|
30
|
+
# @user.fullname = "Tobias Fünke"
|
31
|
+
# @user.save
|
32
|
+
# # Called via PUT "/users/1"
|
33
|
+
#
|
34
|
+
# @example Save a new resource by creating it
|
35
|
+
# @user = User.new({ :fullname => "Tobias Fünke" })
|
36
|
+
# @user.save
|
37
|
+
# # Called via POST "/users"
|
38
|
+
def save
|
39
|
+
callback = new? ? :create : :update
|
40
|
+
method = self.class.method_for(callback)
|
41
|
+
|
42
|
+
run_callbacks callback do
|
43
|
+
run_callbacks :save do
|
44
|
+
params = to_params.merge(:_method => method, :_path => request_path)
|
45
|
+
self.class.request(params) do |parsed_data, response|
|
46
|
+
assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
|
47
|
+
@metadata = parsed_data[:metadata]
|
48
|
+
@response_errors = parsed_data[:errors]
|
49
|
+
|
50
|
+
return false if !response.success? || @response_errors.any?
|
51
|
+
if self.changed_attributes.present?
|
52
|
+
@previously_changed = self.changed_attributes.clone
|
53
|
+
self.changed_attributes.clear
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Similar to save(), except that ResourceInvalid is raised if the save fails
|
63
|
+
def save!
|
64
|
+
if !self.save
|
65
|
+
raise Her::Errors::ResourceInvalid, self
|
66
|
+
end
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Destroy a resource
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# @user = User.find(1)
|
74
|
+
# @user.destroy
|
75
|
+
# # Called via DELETE "/users/1"
|
76
|
+
def destroy(params = {})
|
77
|
+
method = self.class.method_for(:destroy)
|
78
|
+
run_callbacks :destroy do
|
79
|
+
self.class.request(params.merge(:_method => method, :_path => request_path)) do |parsed_data, response|
|
80
|
+
assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
|
81
|
+
@metadata = parsed_data[:metadata]
|
82
|
+
@response_errors = parsed_data[:errors]
|
83
|
+
@destroyed = true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
module ClassMethods
|
90
|
+
# Create a new chainable scope
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# class User
|
94
|
+
# include Her::Model
|
95
|
+
#
|
96
|
+
# scope :admins, lambda { where(:admin => 1) }
|
97
|
+
# scope :page, lambda { |page| where(:page => page) }
|
98
|
+
# enc
|
99
|
+
#
|
100
|
+
# User.admins # Called via GET "/users?admin=1"
|
101
|
+
# User.page(2).all # Called via GET "/users?page=2"
|
102
|
+
def scope(name, code)
|
103
|
+
# Add the scope method to the class
|
104
|
+
(class << self; self end).send(:define_method, name) do |*args|
|
105
|
+
instance_exec(*args, &code)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Add the scope method to the Relation class
|
109
|
+
Relation.instance_eval do
|
110
|
+
define_method(name) { |*args| instance_exec(*args, &code) }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# @private
|
115
|
+
def scoped
|
116
|
+
@_her_default_scope || blank_relation
|
117
|
+
end
|
118
|
+
|
119
|
+
# Define the default scope for the model
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# class User
|
123
|
+
# include Her::Model
|
124
|
+
#
|
125
|
+
# default_scope lambda { where(:admin => 1) }
|
126
|
+
# enc
|
127
|
+
#
|
128
|
+
# User.all # Called via GET "/users?admin=1"
|
129
|
+
# User.new.admin # => 1
|
130
|
+
def default_scope(block=nil)
|
131
|
+
@_her_default_scope ||= (!respond_to?(:default_scope) && superclass.respond_to?(:default_scope)) ? superclass.default_scope : scoped
|
132
|
+
@_her_default_scope = @_her_default_scope.instance_exec(&block) unless block.nil?
|
133
|
+
@_her_default_scope
|
134
|
+
end
|
135
|
+
|
136
|
+
# Delegate the following methods to `scoped`
|
137
|
+
[:all, :where, :create, :build, :find, :first_or_create, :first_or_initialize].each do |method|
|
138
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
139
|
+
def #{method}(*params)
|
140
|
+
scoped.send(#{method.to_sym.inspect}, *params)
|
141
|
+
end
|
142
|
+
RUBY
|
143
|
+
end
|
144
|
+
|
145
|
+
# Save an existing resource and return it
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# @user = User.save_existing(1, { :fullname => "Tobias Fünke" })
|
149
|
+
# # Called via PUT "/users/1"
|
150
|
+
def save_existing(id, params)
|
151
|
+
resource = new(params.merge(primary_key => id))
|
152
|
+
resource.save
|
153
|
+
resource
|
154
|
+
end
|
155
|
+
|
156
|
+
# Destroy an existing resource
|
157
|
+
#
|
158
|
+
# @example
|
159
|
+
# User.destroy_existing(1)
|
160
|
+
# # Called via DELETE "/users/1"
|
161
|
+
def destroy_existing(id, params={})
|
162
|
+
request(params.merge(:_method => method_for(:destroy), :_path => build_request_path(params.merge(primary_key => id)))) do |parsed_data, response|
|
163
|
+
new(parse(parsed_data[:data]).merge(:_destroyed => true))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Return or change the HTTP method used to create or update records
|
168
|
+
#
|
169
|
+
# @param [Symbol, String] action The behavior in question (`:create` or `:update`)
|
170
|
+
# @param [Symbol, String] method The HTTP method to use (`'PUT'`, `:post`, etc.)
|
171
|
+
def method_for(action = nil, method = nil)
|
172
|
+
@method_for ||= (superclass.respond_to?(:method_for) ? superclass.method_for : {})
|
173
|
+
return @method_for if action.nil?
|
174
|
+
|
175
|
+
action = action.to_s.downcase.to_sym
|
176
|
+
|
177
|
+
return @method_for[action] if method.nil?
|
178
|
+
@method_for[action] = method.to_s.downcase.to_sym
|
179
|
+
end
|
180
|
+
|
181
|
+
# Build a new resource with the given attributes.
|
182
|
+
# If the request_new_object_on_build flag is set, the new object is requested via API.
|
183
|
+
def build(attributes = {})
|
184
|
+
params = attributes
|
185
|
+
return self.new(params) unless self.request_new_object_on_build?
|
186
|
+
|
187
|
+
path = self.build_request_path(params.merge(self.primary_key => 'new'))
|
188
|
+
method = self.method_for(:new)
|
189
|
+
|
190
|
+
resource = nil
|
191
|
+
self.request(params.merge(:_method => method, :_path => path)) do |parsed_data, response|
|
192
|
+
if response.success?
|
193
|
+
resource = self.new_from_parsed_data(parsed_data)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
resource
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
# @private
|
201
|
+
def blank_relation
|
202
|
+
@blank_relation ||= Relation.new(self)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|