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
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ODI2OTY3MmY1ZmU0OTRiYWJmZmQ5NTk5NTczYzgxYmJhOGVlY2M3Mg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
Mjg2ZTEwOGExNGZjZTcwYTVjOGQ2YjhiOWIwMDUzZjMzZTllNDc4Zg==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MDI1OGYxM2U2ODA3MTRiZGE4NmQ3ZTczMzRiYzAyOTA0NGI5MDYxNWZhMzg0
|
10
|
+
OWI2ODJkMDlhMWY5NThkZmZkNjhhMGVmYjRmY2JkOWU4ZWM2ZmZhYjczMTVk
|
11
|
+
ODg1ODMxOWJhZDZlNjVkZmE1YWE3NjQ4MDFkMjY0MmMxNzNiOTQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
N2U0Yzc3NWM2OGI0MzYwOTdkNjRiNWY2N2M2NGVmNWY5NTMzMmQwZGFiMGVm
|
14
|
+
Zjg3YWUzNDU4ZmM2ZmU1NmE2N2QzNTE4N2FkY2E2Mjk3NTU5ODhlMWE4MTk3
|
15
|
+
ZDM4OGM3NTgyNDA2ZmM5YzQ5MmY0NGM4OGMzNWFlZGFjZjczY2U=
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -653,9 +653,9 @@ Just like with ActiveRecord, you can define named scopes for your models. Scopes
|
|
653
653
|
class User
|
654
654
|
include Her::Model
|
655
655
|
|
656
|
-
scope :by_role,
|
657
|
-
scope :admins,
|
658
|
-
scope :active,
|
656
|
+
scope :by_role, -> { |role| where(role: role) }
|
657
|
+
scope :admins, -> { by_role('admin') }
|
658
|
+
scope :active, -> { where(active: 1) }
|
659
659
|
end
|
660
660
|
|
661
661
|
@admins = User.admins
|
data/UPGRADE.md
CHANGED
@@ -4,71 +4,78 @@ Here is a list of backward-incompatible changes that were introduced while Her i
|
|
4
4
|
|
5
5
|
## 0.6
|
6
6
|
|
7
|
-
|
7
|
+
Associations have been refactored so that calling the association name method doesn’t immediately load or fetch the data.
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
```ruby
|
10
|
+
class User
|
11
|
+
include Her::Model
|
12
|
+
has_many :comments
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
# This doesn’t fetch the data yet and it’s still chainable
|
16
|
+
comments = User.find(1).comments
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
# This actually fetches the data
|
19
|
+
puts comments.inspect
|
19
20
|
|
20
|
-
|
21
|
-
|
21
|
+
# This is no longer possible in her-0.6
|
22
|
+
comments = User.find(1).comments(:approved => 1)
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
## 0.5.4
|
27
|
-
|
28
|
-
* Her does not support Ruby 1.8.7 anymore. You should upgrade to 1.9.2, 1.9.3 or 2.0.0.
|
24
|
+
# To pass additional parameters to the HTTP request, we now have to do this
|
25
|
+
comments = User.find(1).comments.where(:approved => 1)
|
26
|
+
```
|
29
27
|
|
30
28
|
## 0.5
|
31
29
|
|
32
|
-
|
30
|
+
Her is now compatible with `ActiveModel` and includes `ActiveModel::Validations`.
|
33
31
|
|
34
|
-
|
32
|
+
Before 0.5, the `errors` method on an object would return an error list received from the server (the `:errors` key defined by the parsing middleware). But now, `errors` returns the error list generated after calling the `valid?` method (or any other similar validation method from `ActiveModel::Validations`). The error list returned from the server is now accessible from the `response_errors` method.
|
35
33
|
|
36
|
-
|
34
|
+
Since 0.5.5, Her provides a `store_response_errors` method, which allows you to choose the method which will return the response errors. You can use it to revert Her back to its original behavior (ie. `errors` returning the response errors):
|
37
35
|
|
38
|
-
|
39
|
-
|
36
|
+
```ruby
|
37
|
+
class User
|
38
|
+
include Her::Model
|
39
|
+
store_response_errors :errors
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
user = User.create(:email => "foo") # POST /users returns { :errors => ["Email is invalid"] }
|
45
|
-
user.errors # => ["Email is invalid"]
|
42
|
+
user = User.create(:email => "foo") # POST /users returns { :errors => ["Email is invalid"] }
|
43
|
+
user.errors # => ["Email is invalid"]
|
44
|
+
```
|
46
45
|
|
47
46
|
## 0.2.4
|
48
47
|
|
49
|
-
|
48
|
+
Her no longer includes default middleware when making HTTP requests. The user has now to define all the needed middleware. Before:
|
50
49
|
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
```ruby
|
51
|
+
Her::API.setup :url => "https://api.example.com" do |connection|
|
52
|
+
connection.insert(0, FaradayMiddle::OAuth)
|
53
|
+
end
|
54
|
+
```
|
54
55
|
|
55
|
-
|
56
|
+
Now:
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
```ruby
|
59
|
+
Her::API.setup :url => "https://api.example.com" do |connection|
|
60
|
+
connection.use FaradayMiddle::OAuth
|
61
|
+
connection.use Her::Middleware::FirstLevelParseJSON
|
62
|
+
connection.use Faraday::Request::UrlEncoded
|
63
|
+
connection.use Faraday::Adapter::NetHttp
|
64
|
+
end
|
65
|
+
```
|
63
66
|
|
64
67
|
## 0.2
|
65
68
|
|
66
|
-
|
67
|
-
|
68
|
-
{ "data": { "id": 1, "name": "Foo" }, "errors": [] }
|
69
|
+
The default parser middleware has been replaced to treat first-level JSON data as the resource or collection data. Before it expected this:
|
69
70
|
|
70
|
-
|
71
|
+
```json
|
72
|
+
{ "data": { "id": 1, "name": "Foo" }, "errors": [] }
|
73
|
+
```
|
71
74
|
|
72
|
-
|
75
|
+
Now it expects this (the `errors` key is not treated as resource data):
|
76
|
+
|
77
|
+
```json
|
78
|
+
{ "id": 1, "name": "Foo", "errors": [] }
|
79
|
+
```
|
73
80
|
|
74
|
-
|
81
|
+
If you still want to get the old behavior, you can use `Her::Middleware::SecondLevelParseJSON` instead of `Her::Middleware::FirstLevelParseJSON` in your middleware stack.
|
data/lib/her/model.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "her/model/base"
|
2
|
+
require "her/model/deprecated_methods"
|
2
3
|
require "her/model/http"
|
3
4
|
require "her/model/attributes"
|
4
5
|
require "her/model/relation"
|
@@ -26,6 +27,7 @@ module Her
|
|
26
27
|
|
27
28
|
# Her modules
|
28
29
|
include Her::Model::Base
|
30
|
+
include Her::Model::DeprecatedMethods
|
29
31
|
include Her::Model::Attributes
|
30
32
|
include Her::Model::ORM
|
31
33
|
include Her::Model::HTTP
|
@@ -10,17 +10,19 @@ module Her
|
|
10
10
|
extend ActiveSupport::Concern
|
11
11
|
|
12
12
|
# Returns true if the model has a association_name association, false otherwise.
|
13
|
+
#
|
14
|
+
# @private
|
13
15
|
def has_association?(association_name)
|
14
16
|
associations = self.class.associations.values.flatten.map { |r| r[:name] }
|
15
17
|
associations.include?(association_name)
|
16
18
|
end
|
17
|
-
alias :has_relationship? :has_association?
|
18
19
|
|
19
20
|
# Returns the resource/collection corresponding to the association_name association.
|
21
|
+
#
|
22
|
+
# @private
|
20
23
|
def get_association(association_name)
|
21
24
|
send(association_name) if has_association?(association_name)
|
22
25
|
end
|
23
|
-
alias :get_relationship :get_association
|
24
26
|
|
25
27
|
module ClassMethods
|
26
28
|
# Return @_her_associations, lazily initialized with copy of the
|
@@ -32,7 +34,6 @@ module Her
|
|
32
34
|
superclass.respond_to?(:associations) ? superclass.associations.dup : Hash.new { |h,k| h[k] = [] }
|
33
35
|
end
|
34
36
|
end
|
35
|
-
alias :relationships :associations
|
36
37
|
|
37
38
|
# Parse associations data after initializing a new object
|
38
39
|
#
|
@@ -40,29 +41,21 @@ module Her
|
|
40
41
|
def parse_associations(data)
|
41
42
|
associations.each_pair do |type, definitions|
|
42
43
|
definitions.each do |association|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
klass = self.her_nearby_class(association[:class_name])
|
47
|
-
name = association[:name]
|
48
|
-
|
49
|
-
data[name] = case type
|
50
|
-
when :has_many
|
51
|
-
Her::Model::Attributes.initialize_collection(klass, :data => data[data_key])
|
52
|
-
when :has_one, :belongs_to
|
53
|
-
klass.new(data[data_key])
|
54
|
-
else
|
55
|
-
nil
|
56
|
-
end
|
44
|
+
association_class = "her/model/associations/#{type}_association".classify.constantize
|
45
|
+
data.merge! association_class.parse(association, self, data)
|
57
46
|
end
|
58
47
|
end
|
48
|
+
|
59
49
|
data
|
60
50
|
end
|
61
51
|
|
62
52
|
# Define an *has_many* association.
|
63
53
|
#
|
64
|
-
# @param [Symbol] name The name of the
|
65
|
-
# @param [Hash] attrs Options
|
54
|
+
# @param [Symbol] name The name of the method added to resources
|
55
|
+
# @param [Hash] attrs Options
|
56
|
+
# @option attrs [String] :class_name The name of the class to map objects to
|
57
|
+
# @option attrs [Symbol] :data_key The attribute where the data is stored
|
58
|
+
# @option attrs [Path] :path The relative path where to fetch the data (defaults to `/{name}`)
|
66
59
|
#
|
67
60
|
# @example
|
68
61
|
# class User
|
@@ -83,8 +76,10 @@ module Her
|
|
83
76
|
|
84
77
|
# Define an *has_one* association.
|
85
78
|
#
|
86
|
-
# @param [Symbol] name The name of the
|
87
|
-
# @
|
79
|
+
# @param [Symbol] name The name of the method added to resources
|
80
|
+
# @option attrs [String] :class_name The name of the class to map objects to
|
81
|
+
# @option attrs [Symbol] :data_key The attribute where the data is stored
|
82
|
+
# @option attrs [Path] :path The relative path where to fetch the data (defaults to `/{name}`)
|
88
83
|
#
|
89
84
|
# @example
|
90
85
|
# class User
|
@@ -105,8 +100,11 @@ module Her
|
|
105
100
|
|
106
101
|
# Define a *belongs_to* association.
|
107
102
|
#
|
108
|
-
# @param [Symbol] name The name of the
|
109
|
-
# @
|
103
|
+
# @param [Symbol] name The name of the method added to resources
|
104
|
+
# @option attrs [String] :class_name The name of the class to map objects to
|
105
|
+
# @option attrs [Symbol] :data_key The attribute where the data is stored
|
106
|
+
# @option attrs [Path] :path The relative path where to fetch the data (defaults to `/{class_name}.pluralize/{id}`)
|
107
|
+
# @option attrs [Symbol] :foreign_key The foreign key used to build the `:id` part of the path (defaults to `{name}_id`)
|
110
108
|
#
|
111
109
|
# @example
|
112
110
|
# class User
|
@@ -14,11 +14,12 @@ module Her
|
|
14
14
|
@name = @opts[:name]
|
15
15
|
end
|
16
16
|
|
17
|
+
# Add query parameters to the HTTP request performed to fetch the data
|
17
18
|
def where(attrs = {})
|
18
19
|
return self if attrs.blank?
|
19
20
|
self.clone.tap { |a| a.query_attrs = a.query_attrs.merge(attrs) }
|
20
21
|
end
|
21
|
-
alias
|
22
|
+
alias all where
|
22
23
|
|
23
24
|
# @private
|
24
25
|
def nil?
|
@@ -34,7 +35,7 @@ module Her
|
|
34
35
|
def ==(other)
|
35
36
|
fetch.eql?(other)
|
36
37
|
end
|
37
|
-
alias
|
38
|
+
alias eql? ==
|
38
39
|
|
39
40
|
# ruby 1.8.7 compatibility
|
40
41
|
# @private
|
@@ -13,20 +13,59 @@ module Her
|
|
13
13
|
}.merge(attrs)
|
14
14
|
klass.associations[:belongs_to] << attrs
|
15
15
|
|
16
|
-
klass.
|
17
|
-
|
16
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
17
|
+
def #{name}
|
18
18
|
cached_name = :"@_her_association_#{name}"
|
19
19
|
|
20
20
|
cached_data = (instance_variable_defined?(cached_name) && instance_variable_get(cached_name))
|
21
|
-
cached_data || instance_variable_set(cached_name, Her::Model::Associations::BelongsToAssociation.new(self, attrs))
|
21
|
+
cached_data || instance_variable_set(cached_name, Her::Model::Associations::BelongsToAssociation.new(self, #{attrs.inspect}))
|
22
22
|
end
|
23
|
-
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
|
26
|
+
# @private
|
27
|
+
def self.parse(association, klass, data)
|
28
|
+
data_key = association[:data_key]
|
29
|
+
return {} unless data[data_key]
|
30
|
+
|
31
|
+
klass = klass.her_nearby_class(association[:class_name])
|
32
|
+
{ association[:name] => klass.new(data[data_key]) }
|
24
33
|
end
|
25
34
|
|
35
|
+
# Initialize a new object
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# class User
|
39
|
+
# include Her::Model
|
40
|
+
# belongs_to :organization
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# class Organization
|
44
|
+
# include Her::Model
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# user = User.find(1)
|
48
|
+
# new_organization = user.organization.build(:name => "Foo Inc.")
|
49
|
+
# new_organization # => #<Organization name="Foo Inc.">
|
26
50
|
def build(attributes = {})
|
27
51
|
@klass.new(attributes)
|
28
52
|
end
|
29
53
|
|
54
|
+
# Create a new object, save it and associate it to the parent
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# class User
|
58
|
+
# include Her::Model
|
59
|
+
# belongs_to :organization
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# class Organization
|
63
|
+
# include Her::Model
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# user = User.find(1)
|
67
|
+
# user.organization.create(:name => "Foo Inc.")
|
68
|
+
# user.organization # => #<Organization id=2 name="Foo Inc.">
|
30
69
|
def create(attributes = {})
|
31
70
|
resource = build(attributes)
|
32
71
|
@parent.attributes[@name] = resource if resource.save
|
@@ -36,7 +75,7 @@ module Her
|
|
36
75
|
# @private
|
37
76
|
def fetch
|
38
77
|
foreign_key_value = @parent.attributes[@opts[:foreign_key].to_sym]
|
39
|
-
return nil if (@parent.attributes.include?(@name) && @parent.attributes[@name].nil? && @query_attrs.empty?) || foreign_key_value.blank?
|
78
|
+
return nil if (@parent.attributes.include?(@name) && @parent.attributes[@name].nil? && @query_attrs.empty?) || (@parent.persisted? && foreign_key_value.blank?)
|
40
79
|
|
41
80
|
if @parent.attributes[@name].blank? || @query_attrs.any?
|
42
81
|
path = begin
|
@@ -50,6 +89,15 @@ module Her
|
|
50
89
|
@parent.attributes[@name]
|
51
90
|
end
|
52
91
|
end
|
92
|
+
|
93
|
+
# @private
|
94
|
+
def assign_nested_attributes(attributes)
|
95
|
+
if @parent.attributes[@name].blank?
|
96
|
+
@parent.attributes[@name] = @klass.new(@klass.parse(attributes))
|
97
|
+
else
|
98
|
+
@parent.attributes[@name].assign_attributes(attributes)
|
99
|
+
end
|
100
|
+
end
|
53
101
|
end
|
54
102
|
end
|
55
103
|
end
|
@@ -13,20 +13,59 @@ module Her
|
|
13
13
|
}.merge(attrs)
|
14
14
|
klass.associations[:has_many] << attrs
|
15
15
|
|
16
|
-
klass.
|
17
|
-
|
16
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
17
|
+
def #{name}
|
18
18
|
cached_name = :"@_her_association_#{name}"
|
19
19
|
|
20
20
|
cached_data = (instance_variable_defined?(cached_name) && instance_variable_get(cached_name))
|
21
|
-
cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasManyAssociation.new(self, attrs))
|
21
|
+
cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasManyAssociation.new(self, #{attrs.inspect}))
|
22
22
|
end
|
23
|
-
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
|
26
|
+
# @private
|
27
|
+
def self.parse(association, klass, data)
|
28
|
+
data_key = association[:data_key]
|
29
|
+
return {} unless data[data_key]
|
30
|
+
|
31
|
+
klass = klass.her_nearby_class(association[:class_name])
|
32
|
+
{ association[:name] => Her::Model::Attributes.initialize_collection(klass, :data => data[data_key]) }
|
24
33
|
end
|
25
34
|
|
35
|
+
# Initialize a new object with a foreign key to the parent
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# class User
|
39
|
+
# include Her::Model
|
40
|
+
# has_many :comments
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# class Comment
|
44
|
+
# include Her::Model
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# user = User.find(1)
|
48
|
+
# new_comment = user.comments.build(:body => "Hello!")
|
49
|
+
# new_comment # => #<Comment user_id=1 body="Hello!">
|
26
50
|
def build(attributes = {})
|
27
51
|
@klass.new(attributes.merge(:"#{@parent.singularized_resource_name}_id" => @parent.id))
|
28
52
|
end
|
29
53
|
|
54
|
+
# Create a new object, save it and add it to the associated collection
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# class User
|
58
|
+
# include Her::Model
|
59
|
+
# has_many :comments
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# class Comment
|
63
|
+
# include Her::Model
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# user = User.find(1)
|
67
|
+
# user.comments.create(:body => "Hello!")
|
68
|
+
# user.comments # => [#<Comment id=2 user_id=1 body="Hello!">]
|
30
69
|
def create(attributes = {})
|
31
70
|
resource = build(attributes)
|
32
71
|
|
@@ -59,6 +98,11 @@ module Her
|
|
59
98
|
|
60
99
|
output
|
61
100
|
end
|
101
|
+
|
102
|
+
# @private
|
103
|
+
def assign_nested_attributes(attributes)
|
104
|
+
@parent.attributes[@name] = Her::Model::Attributes.initialize_collection(@klass, :data => attributes)
|
105
|
+
end
|
62
106
|
end
|
63
107
|
end
|
64
108
|
end
|