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