her 0.6.2 → 0.6.3
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 +8 -8
- data/.gitignore +2 -7
- data/README.md +3 -3
- data/UPGRADE.md +51 -44
- data/lib/her/model.rb +2 -0
- data/lib/her/model/associations.rb +21 -23
- data/lib/her/model/associations/association.rb +3 -2
- data/lib/her/model/associations/belongs_to_association.rb +53 -5
- data/lib/her/model/associations/has_many_association.rb +48 -4
- data/lib/her/model/associations/has_one_association.rb +52 -4
- data/lib/her/model/attributes.rb +73 -23
- data/lib/her/model/base.rb +4 -0
- data/lib/her/model/deprecated_methods.rb +61 -0
- data/lib/her/model/http.rb +46 -37
- data/lib/her/model/introspection.rb +3 -1
- data/lib/her/model/nested_attributes.rb +25 -37
- data/lib/her/model/orm.rb +29 -2
- data/lib/her/model/parse.rb +2 -2
- data/lib/her/model/relation.rb +12 -0
- data/lib/her/version.rb +1 -1
- data/spec/model/associations_spec.rb +1 -1
- data/spec/model/attributes_spec.rb +7 -7
- data/spec/model/nested_attributes_spec.rb +8 -0
- data/spec/model/relation_spec.rb +56 -3
- data/spec/support/macros/request_macros.rb +1 -1
- metadata +3 -2
@@ -12,20 +12,59 @@ module Her
|
|
12
12
|
}.merge(attrs)
|
13
13
|
klass.associations[:has_one] << attrs
|
14
14
|
|
15
|
-
klass.
|
16
|
-
|
15
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
16
|
+
def #{name}
|
17
17
|
cached_name = :"@_her_association_#{name}"
|
18
18
|
|
19
19
|
cached_data = (instance_variable_defined?(cached_name) && instance_variable_get(cached_name))
|
20
|
-
cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasOneAssociation.new(self, attrs))
|
20
|
+
cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasOneAssociation.new(self, #{attrs.inspect}))
|
21
21
|
end
|
22
|
-
|
22
|
+
RUBY
|
23
|
+
end
|
24
|
+
|
25
|
+
# @private
|
26
|
+
def self.parse(association, klass, data)
|
27
|
+
data_key = association[:data_key]
|
28
|
+
return {} unless data[data_key]
|
29
|
+
|
30
|
+
klass = klass.her_nearby_class(association[:class_name])
|
31
|
+
{ association[:name] => klass.new(data[data_key]) }
|
23
32
|
end
|
24
33
|
|
34
|
+
# Initialize a new object with a foreign key to the parent
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# class User
|
38
|
+
# include Her::Model
|
39
|
+
# has_one :role
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# class Role
|
43
|
+
# include Her::Model
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# user = User.find(1)
|
47
|
+
# new_role = user.role.build(:title => "moderator")
|
48
|
+
# new_role # => #<Role user_id=1 title="moderator">
|
25
49
|
def build(attributes = {})
|
26
50
|
@klass.new(attributes.merge(:"#{@parent.singularized_resource_name}_id" => @parent.id))
|
27
51
|
end
|
28
52
|
|
53
|
+
# Create a new object, save it and associate it to the parent
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# class User
|
57
|
+
# include Her::Model
|
58
|
+
# has_one :role
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# class Role
|
62
|
+
# include Her::Model
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# user = User.find(1)
|
66
|
+
# user.role.create(:title => "moderator")
|
67
|
+
# user.role # => #<Role id=2 user_id=1 title="moderator">
|
29
68
|
def create(attributes = {})
|
30
69
|
resource = build(attributes)
|
31
70
|
@parent.attributes[@name] = resource if resource.save
|
@@ -48,6 +87,15 @@ module Her
|
|
48
87
|
@parent.attributes[@name]
|
49
88
|
end
|
50
89
|
end
|
90
|
+
|
91
|
+
# @private
|
92
|
+
def assign_nested_attributes(attributes)
|
93
|
+
if @parent.attributes[@name].blank?
|
94
|
+
@parent.attributes[@name] = @klass.new(@klass.parse(attributes))
|
95
|
+
else
|
96
|
+
@parent.attributes[@name].assign_attributes(attributes)
|
97
|
+
end
|
98
|
+
end
|
51
99
|
end
|
52
100
|
end
|
53
101
|
end
|
data/lib/her/model/attributes.rb
CHANGED
@@ -3,22 +3,33 @@ module Her
|
|
3
3
|
# This module handles all methods related to model attributes
|
4
4
|
module Attributes
|
5
5
|
extend ActiveSupport::Concern
|
6
|
-
|
7
6
|
attr_accessor :attributes
|
8
|
-
alias :data :attributes
|
9
|
-
alias :data= :attributes=
|
10
7
|
|
11
8
|
# Initialize a new object with data
|
9
|
+
#
|
10
|
+
# @param [Hash] attributes The attributes to initialize the object with
|
11
|
+
# @option attributes [Hash,Array] :_metadata
|
12
|
+
# @option attributes [Hash,Array] :_errors
|
13
|
+
# @option attributes [Boolean] :_destroyed
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# class User
|
17
|
+
# include Her::Model
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# User.new(name: "Tobias") # => #<User name="Tobias">
|
12
21
|
def initialize(attributes={})
|
13
22
|
attributes ||= {}
|
14
23
|
@metadata = attributes.delete(:_metadata) || {}
|
15
24
|
@response_errors = attributes.delete(:_errors) || {}
|
16
25
|
@destroyed = attributes.delete(:_destroyed) || false
|
17
26
|
|
27
|
+
attributes = self.class.default_scope.apply_to(attributes)
|
18
28
|
assign_attributes(attributes)
|
19
29
|
end
|
20
30
|
|
21
31
|
# Initialize a collection of resources
|
32
|
+
#
|
22
33
|
# @private
|
23
34
|
def self.initialize_collection(klass, parsed_data={})
|
24
35
|
collection_data = parsed_data[:data].map do |item_data|
|
@@ -31,6 +42,7 @@ module Her
|
|
31
42
|
|
32
43
|
# Use setter methods of model for each key / value pair in params
|
33
44
|
# Return key / value pairs for which no setter method was defined on the model
|
45
|
+
#
|
34
46
|
# @private
|
35
47
|
def self.use_setter_methods(model, params)
|
36
48
|
setter_method_names = model.class.setter_method_names
|
@@ -48,6 +60,7 @@ module Her
|
|
48
60
|
end
|
49
61
|
|
50
62
|
# Handles missing methods
|
63
|
+
#
|
51
64
|
# @private
|
52
65
|
def method_missing(method, *args, &blk)
|
53
66
|
if method.to_s =~ /[?=]$/ || attributes.include?(method)
|
@@ -74,8 +87,16 @@ module Her
|
|
74
87
|
method.to_s.end_with?('=') || method.to_s.end_with?('?') || @attributes.include?(method) || @attributes.include?(method) || super
|
75
88
|
end
|
76
89
|
|
77
|
-
# Assign new attributes
|
78
|
-
#
|
90
|
+
# Assign new attributes to a resource
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# class User
|
94
|
+
# include Her::Model
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# user = User.find(1) # => #<User id=1 name="Tobias">
|
98
|
+
# user.assign_attributes(name: "Lindsay")
|
99
|
+
# user.changes # => { :name => ["Tobias", "Lindsay"] }
|
79
100
|
def assign_attributes(raw_data)
|
80
101
|
@attributes ||= {}
|
81
102
|
# Use setter methods first, then translate attributes of associations
|
@@ -84,36 +105,37 @@ module Her
|
|
84
105
|
parsed_attributes = self.class.parse_associations(unset_attributes)
|
85
106
|
attributes.update(parsed_attributes)
|
86
107
|
end
|
87
|
-
alias :update_attributes :assign_attributes
|
88
|
-
alias :assign_data :assign_attributes
|
89
108
|
|
90
109
|
# Handles returning true for the accessible attributes
|
110
|
+
#
|
91
111
|
# @private
|
92
112
|
def has_attribute?(attribute_name)
|
93
113
|
attributes.include?(attribute_name)
|
94
114
|
end
|
95
|
-
alias :has_data? :has_attribute?
|
96
115
|
|
97
116
|
# Handles returning data for a specific attribute
|
117
|
+
#
|
98
118
|
# @private
|
99
119
|
def get_attribute(attribute_name)
|
100
120
|
attributes[attribute_name]
|
101
121
|
end
|
102
|
-
alias :get_data :get_attribute
|
103
122
|
|
104
123
|
# Override the method to prevent from returning the object ID
|
124
|
+
#
|
105
125
|
# @private
|
106
126
|
def id
|
107
127
|
attributes[self.class.primary_key]
|
108
128
|
end
|
109
129
|
|
110
130
|
# Return `true` if the other object is also a Her::Model and has matching data
|
131
|
+
#
|
111
132
|
# @private
|
112
133
|
def ==(other)
|
113
134
|
other.is_a?(Her::Model) && attributes == other.attributes
|
114
135
|
end
|
115
136
|
|
116
137
|
# Delegate to the == method
|
138
|
+
#
|
117
139
|
# @private
|
118
140
|
def eql?(other)
|
119
141
|
self == other
|
@@ -137,28 +159,44 @@ module Her
|
|
137
159
|
# Define the attributes that will be used to track dirty attributes and validations
|
138
160
|
#
|
139
161
|
# @param [Array] attributes
|
162
|
+
#
|
163
|
+
# @example
|
164
|
+
# class User
|
165
|
+
# include Her::Model
|
166
|
+
# attributes :name, :email
|
167
|
+
# end
|
140
168
|
def attributes(*attributes)
|
141
169
|
define_attribute_methods attributes
|
142
170
|
|
143
171
|
attributes.each do |attribute|
|
144
172
|
attribute = attribute.to_sym
|
145
173
|
|
146
|
-
|
147
|
-
|
148
|
-
|
174
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
175
|
+
def #{attribute}
|
176
|
+
@attributes.include?(:'#{attribute}') ? @attributes[:'#{attribute}'] : nil
|
177
|
+
end
|
149
178
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
179
|
+
def #{attribute}=(value)
|
180
|
+
self.send(:"#{attribute}_will_change!") if @attributes[:'#{attribute}'] != value
|
181
|
+
@attributes[:'#{attribute}'] = value
|
182
|
+
end
|
154
183
|
|
155
|
-
|
156
|
-
|
157
|
-
|
184
|
+
def #{attribute}?
|
185
|
+
@attributes.include?(:'#{attribute}') && @attributes[:'#{attribute}'].present?
|
186
|
+
end
|
187
|
+
RUBY
|
158
188
|
end
|
159
189
|
end
|
160
190
|
|
161
191
|
# Define the accessor in which the API response errors (obtained from the parsing middleware) will be stored
|
192
|
+
#
|
193
|
+
# @param [Symbol] store_response_errors
|
194
|
+
#
|
195
|
+
# @example
|
196
|
+
# class User
|
197
|
+
# include Her::Model
|
198
|
+
# store_response_errors :server_errors
|
199
|
+
# end
|
162
200
|
def store_response_errors(value = nil)
|
163
201
|
if @_her_store_response_errors
|
164
202
|
remove_method @_her_store_response_errors
|
@@ -172,11 +210,21 @@ module Her
|
|
172
210
|
return @_her_store_response_errors unless value
|
173
211
|
@_her_store_response_errors = value
|
174
212
|
|
175
|
-
|
176
|
-
|
213
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
214
|
+
def #{@_her_store_response_errors}; @response_errors; end
|
215
|
+
def #{@_her_store_response_errors}=(value); @response_errors = value; end
|
216
|
+
RUBY
|
177
217
|
end
|
178
218
|
|
179
219
|
# Define the accessor in which the API response metadata (obtained from the parsing middleware) will be stored
|
220
|
+
#
|
221
|
+
# @param [Symbol] store_metadata
|
222
|
+
#
|
223
|
+
# @example
|
224
|
+
# class User
|
225
|
+
# include Her::Model
|
226
|
+
# store_metadata :server_data
|
227
|
+
# end
|
180
228
|
def store_metadata(value = nil)
|
181
229
|
if @_her_store_metadata
|
182
230
|
remove_method @_her_store_metadata
|
@@ -190,8 +238,10 @@ module Her
|
|
190
238
|
return @_her_store_metadata unless value
|
191
239
|
@_her_store_metadata = value
|
192
240
|
|
193
|
-
|
194
|
-
|
241
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
242
|
+
def #{@_her_store_metadata}; @metadata; end
|
243
|
+
def #{@_her_store_metadata}=(value); @metadata = value; end
|
244
|
+
RUBY
|
195
245
|
end
|
196
246
|
|
197
247
|
# @private
|
data/lib/her/model/base.rb
CHANGED
@@ -7,6 +7,8 @@ module Her
|
|
7
7
|
# Returns true if attribute_name is
|
8
8
|
# * in resource attributes
|
9
9
|
# * an association
|
10
|
+
#
|
11
|
+
# @private
|
10
12
|
def has_key?(attribute_name)
|
11
13
|
has_attribute?(attribute_name) ||
|
12
14
|
has_association?(attribute_name)
|
@@ -15,6 +17,8 @@ module Her
|
|
15
17
|
# Returns
|
16
18
|
# * the value of the attribute_name attribute if it's in orm data
|
17
19
|
# * the resource/collection corrsponding to attribute_name if it's an association
|
20
|
+
#
|
21
|
+
# @private
|
18
22
|
def [](attribute_name)
|
19
23
|
get_attribute(attribute_name) ||
|
20
24
|
get_association(attribute_name)
|
@@ -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
|
data/lib/her/model/http.rb
CHANGED
@@ -7,6 +7,17 @@ module Her
|
|
7
7
|
|
8
8
|
module ClassMethods
|
9
9
|
# Change which API the model will use to make its HTTP requests
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# secondary_api = Her::API.new :url => "https://api.example" do |connection|
|
13
|
+
# connection.use Faraday::Request::UrlEncoded
|
14
|
+
# connection.use Her::Middleware::DefaultParseJSON
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# class User
|
18
|
+
# include Her::Model
|
19
|
+
# use_api secondary_api
|
20
|
+
# end
|
10
21
|
def use_api(value = nil)
|
11
22
|
@_her_use_api ||= begin
|
12
23
|
superclass.use_api if superclass.respond_to?(:use_api)
|
@@ -15,10 +26,11 @@ module Her
|
|
15
26
|
return @_her_use_api unless value
|
16
27
|
@_her_use_api = value
|
17
28
|
end
|
18
|
-
alias
|
19
|
-
alias
|
29
|
+
alias her_api use_api
|
30
|
+
alias uses_api use_api
|
20
31
|
|
21
32
|
# Main request wrapper around Her::API. Used to make custom request to the API.
|
33
|
+
#
|
22
34
|
# @private
|
23
35
|
def request(attrs={})
|
24
36
|
request = her_api.request(attrs)
|
@@ -38,52 +50,49 @@ module Her
|
|
38
50
|
# - <method>_resource(path, attrs, &block)
|
39
51
|
# - custom_<method>(path, attrs)
|
40
52
|
METHODS.each do |method|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
53
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
54
|
+
def #{method}(path, attrs={})
|
55
|
+
path = build_request_path_from_string_or_symbol(path, attrs)
|
56
|
+
send(:'#{method}_raw', path, attrs) do |parsed_data, response|
|
57
|
+
if parsed_data[:data].is_a?(Array)
|
58
|
+
new_collection(parsed_data)
|
59
|
+
else
|
60
|
+
new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
|
61
|
+
end
|
49
62
|
end
|
50
63
|
end
|
51
|
-
end
|
52
64
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
65
|
+
def #{method}_raw(path, attrs={}, &block)
|
66
|
+
path = build_request_path_from_string_or_symbol(path, attrs)
|
67
|
+
request(attrs.merge(:_method => #{method.to_sym.inspect}, :_path => path), &block)
|
68
|
+
end
|
58
69
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
new_collection(parsed_data)
|
70
|
+
def #{method}_collection(path, attrs={})
|
71
|
+
path = build_request_path_from_string_or_symbol(path, attrs)
|
72
|
+
send(:'#{method}_raw', build_request_path_from_string_or_symbol(path, attrs), attrs) do |parsed_data, response|
|
73
|
+
new_collection(parsed_data)
|
74
|
+
end
|
65
75
|
end
|
66
|
-
end
|
67
76
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
77
|
+
def #{method}_resource(path, attrs={})
|
78
|
+
path = build_request_path_from_string_or_symbol(path, attrs)
|
79
|
+
send(:"#{method}_raw", path, attrs) do |parsed_data, response|
|
80
|
+
new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
|
81
|
+
end
|
73
82
|
end
|
74
|
-
end
|
75
83
|
|
76
|
-
|
77
|
-
|
78
|
-
|
84
|
+
def custom_#{method}(*paths)
|
85
|
+
metaclass = (class << self; self; end)
|
86
|
+
opts = paths.last.is_a?(Hash) ? paths.pop : Hash.new
|
79
87
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
88
|
+
paths.each do |path|
|
89
|
+
metaclass.send(:define_method, path) do |*attrs|
|
90
|
+
attrs = attrs.first || Hash.new
|
91
|
+
send(#{method.to_sym.inspect}, path, attrs)
|
92
|
+
end
|
84
93
|
end
|
85
94
|
end
|
86
|
-
|
95
|
+
RUBY
|
87
96
|
end
|
88
97
|
end
|
89
98
|
end
|