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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MDYyOTQxNDk5MmVhOGYxZDg4OTI0YTAxN2Y3MzBkYzliODc2MjgzZg==
4
+ ODI2OTY3MmY1ZmU0OTRiYWJmZmQ5NTk5NTczYzgxYmJhOGVlY2M3Mg==
5
5
  data.tar.gz: !binary |-
6
- ZWJmNTZiODg5ZWFkYjZjM2M3YmRkNzhhNzk5MzYzOWQxOTdkYmNjNw==
6
+ Mjg2ZTEwOGExNGZjZTcwYTVjOGQ2YjhiOWIwMDUzZjMzZTllNDc4Zg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- OTM0ZGNmYmRjODRiNDJhMzhkNTY2NGViNjU1Y2EzNzQwMGE2MTE3OWRlZDM4
10
- N2Q4MWJlMjYxMjU5ZTg5MGU5MTdmZWEyZGY3ZmJmNThmYzY4NWNlMGQ5YzMw
11
- ZGM1ZjcwYmRhZjA4ZWYyNjU4MTJiMzdkYjk4YzE0YmU3ZjY4MTc=
9
+ MDI1OGYxM2U2ODA3MTRiZGE4NmQ3ZTczMzRiYzAyOTA0NGI5MDYxNWZhMzg0
10
+ OWI2ODJkMDlhMWY5NThkZmZkNjhhMGVmYjRmY2JkOWU4ZWM2ZmZhYjczMTVk
11
+ ODg1ODMxOWJhZDZlNjVkZmE1YWE3NjQ4MDFkMjY0MmMxNzNiOTQ=
12
12
  data.tar.gz: !binary |-
13
- YmU3ODE5Mzk0YmY4YWQxOTA4YjVkMGNlNmFkNzBmZGMzOTAwNTA2MTFmNThl
14
- MDFkNDQ2MTlkY2FkNWNlYWE2ZjFjZjFkMmJjZmIxY2UyYjY1ZGUyNjExNjVl
15
- ODlmMDUwN2Q2NjhiOGY4ZmMyNmJkNDI4Nzg4OWM5YjZlNWIyNzc=
13
+ N2U0Yzc3NWM2OGI0MzYwOTdkNjRiNWY2N2M2NGVmNWY5NTMzMmQwZGFiMGVm
14
+ Zjg3YWUzNDU4ZmM2ZmU1NmE2N2QzNTE4N2FkY2E2Mjk3NTU5ODhlMWE4MTk3
15
+ ZDM4OGM3NTgyNDA2ZmM5YzQ5MmY0NGM4OGMzNWFlZGFjZjczY2U=
data/.gitignore CHANGED
@@ -1,8 +1,3 @@
1
- *.gem
2
- .bundle
3
1
  /Gemfile.lock
4
- pkg/*
5
- rake
6
- tmp
7
- .env
8
- *.db
2
+ /pkg
3
+ /tmp
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, lambda { |role| where(role: role) }
657
- scope :admins, lambda { by_role('admin') }
658
- scope :active, lambda { where(active: 1) }
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
- * Associations have been refactored so that calling the association name method doesn’t immediately load or fetch the data.
7
+ Associations have been refactored so that calling the association name method doesn’t immediately load or fetch the data.
8
8
 
9
- class User
10
- include Her::Model
11
- has_many :comments
12
- end
9
+ ```ruby
10
+ class User
11
+ include Her::Model
12
+ has_many :comments
13
+ end
13
14
 
14
- # This doesn’t fetch the data yet
15
- comments = User.find(1).comments
15
+ # This doesn’t fetch the data yet and it’s still chainable
16
+ comments = User.find(1).comments
16
17
 
17
- # This actually fetches the data
18
- puts comments.inspect
18
+ # This actually fetches the data
19
+ puts comments.inspect
19
20
 
20
- # This is no longer possible
21
- comments = User.find(1).comments(:approved => 1)
21
+ # This is no longer possible in her-0.6
22
+ comments = User.find(1).comments(:approved => 1)
22
23
 
23
- # To pass additional parameters to the HTTP request, we now have to do this
24
- comments = User.find(1).comments.where(:approved => 1)
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
- * Her is now compatible with `ActiveModel` and includes `ActiveModel::Validations`.
30
+ Her is now compatible with `ActiveModel` and includes `ActiveModel::Validations`.
33
31
 
34
- 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.
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
- 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):
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
- class User
39
- include Her::Model
36
+ ```ruby
37
+ class User
38
+ include Her::Model
39
+ store_response_errors :errors
40
+ end
40
41
 
41
- store_response_errors :errors
42
- end
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
- * Her no longer includes default middleware when making HTTP requests. The user has now to define all the needed middleware. Before:
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
- Her::API.setup :url => "https://api.example.com" do |connection|
52
- connection.insert(0, FaradayMiddle::OAuth)
53
- end
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
- Now:
56
+ Now:
56
57
 
57
- Her::API.setup :url => "https://api.example.com" do |connection|
58
- connection.use FaradayMiddle::OAuth
59
- connection.use Her::Middleware::FirstLevelParseJSON
60
- connection.use Faraday::Request::UrlEncoded
61
- connection.use Faraday::Adapter::NetHttp
62
- end
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
- * The default parser middleware has been replaced to treat first-level JSON data as the resource or collection data. Before it expected this:
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
- Now it expects this (the `errors` key is not treated as resource data):
71
+ ```json
72
+ { "data": { "id": 1, "name": "Foo" }, "errors": [] }
73
+ ```
71
74
 
72
- { "id": 1, "name": "Foo", "errors": [] }
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
- If you still want to get the old behavior, you can use `Her::Middleware::SecondLevelParseJSON` instead of `Her::Middleware::FirstLevelParseJSON` in your middleware stack.
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.
@@ -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
- data_key = association[:data_key]
44
- next unless data[data_key]
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 model
65
- # @param [Hash] attrs Options (currently not used)
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 model
87
- # @param [Hash] attrs Options
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 model
109
- # @param [Hash] attrs Options
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 :all :where
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 :eql? :==
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.instance_eval do
17
- define_method(name) do
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
- end
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.instance_eval do
17
- define_method(name) do
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
- end
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