her 0.6.2 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|