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 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