Repository: okuramasafumi/alba (Ruby)

Issue:
`within` kwarg is ignored for nested associations
Greetings! 

## Describe the bug
I am attempting to implement the [JSON:API Resource](https://jsonapi.org/format/#document-resource-objects) object format for two models with circular associations to each other. As I am understanding it, the spec requires associations to be stored in the `"relationships"` key. However, when I replicate this format using Alba, a `SystemStackError` is raised when serializing regardless of restricting the associations using the `within:` kwarg. 

## To Reproduce
Steps to reproduce the behavior:
1. Load IRB and require "alba"
2. Use the following code to create two "models" and their resources:
#### Models
```rb
class TestUser
  attr_accessor :full_name, :created_at, :company

  def type = "users"
end

class TestCompany
  attr_accessor :name, :created_at, :users

  def type = "companies"
end
```

#### Resources
_For simplicity, I am using `"object_id"` instead of `"id"`_
```rb
class TestUserResource
  include Alba::Resource

  attributes :type, :object_id

  nested_attribute :attributes do
    attributes :full_name, :created_at
  end

  nested_attribute :relationships do
    has_one :company, resource: TestCompanyResource
  end
end

class TestCompanyResource
  include Alba::Resource

  attributes :type, :object_id

  nested_attribute :attributes do
    attributes :name, :created_at
  end

  nested_attribute :relationships do
    has_many :users, resource: TestUserResource
  end
end
```

#### Setup
```rb
company = TestCompany.new.tap do |company|
  company.name = "a_new_company"
  company.created_at = Time.current
end

users = 10.times.map do |index|
  TestUser.new.tap do |user|
    user.full_name = "a_new_user_#{index}"
    user.created_at = Time.current
  end
end

company.users = users
users.each { |u| u.company = company }
```

#### Example usage
```rb
# Example 1
TestCompanyResource.new(company).to_h 

# Example 2
TestCompanyResource.new(company, within: [:users]).to_h

# Example 3
TestCompanyResource.new(company, within: {users: :company}).to_h
```

## Expected behavior
I am expecting:
1. Example 1 to raise `SystemStackError`. 
2. Example 2 to include the `users` association, but each user does not include the `company` association
```json
{
  "type": "companies",
  "object_id": 94400,
  "attributes": {
    "name": "a_new_company",
    "created_at": "date"
  },
  "relationships": {
    "users": [
      {
        "type": "users",
        "object_id": 93640,
        "attributes": {
          "full_name": "a_new_user_0",
          "created_at": "date"
        },
        "relationships": {}
      }
      // the other users
    ]
  }
}
```

3. Example 3 to include the `users` association and each user to include the `company` association
```json
{
  "type": "companies",
  "object_id": 94400,
  "attributes": {
    "name": "a_new_company",
    "created_at": "date"
  },
  "relationships": {
    "users": [
      {
        "type": "users",
        "object_id": 93640,
        "attributes": {
          "full_name": "a_new_user_0",
          "created_at": "date"
        },
        "relationships": {
          "company": {
            "type": "companies",
            "object_id": 94400,
            "attributes": {
              "name": "a_new_company",
              "created_at": "date"
            },
            "relationships": {}
          }
        }
      }
      // the other users
    ]
  }
}
```

## Actual behavior
All three examples receive a `stack level too deep (SystemStackError)`

## Environment
- Ruby version: 3.2.2
- Alba version: 3.0.2

## Additional context
I did some debugging and found this can be resolved by passing the `@within` variable into `NestedAttribute#value`. 

[`Alba::Resource#fetch_attribute:249`](https://github.com/okuramasafumi/alba/blob/40cbf5f85e88cf12c2bf2dff4140cf4a7bd59956/lib/alba/resource.rb#L249)
```rb
when NestedAttribute then attribute.value(object: obj, params: params, within: @within)
```

[`Alba::NestedAttribute#value:15`](https://github.com/okuramasafumi/alba/blob/8ba6cbffe1231a246b628e8b9383445411f97b46/lib/alba/nested_attribute.rb#L15)
```rb
 def value(object:, params:, within:)
```

[`Alba::NestedAttribute#value:19`](https://github.com/okuramasafumi/alba/blob/8ba6cbffe1231a246b628e8b9383445411f97b46/lib/alba/nested_attribute.rb#L19)
```rb
 resource_class.new(object, params: params, within: within).serializable_hash
```

This does resolve the issue I am experiencing but I just started using Alba today so I do not know the repercussions of that change. I was hoping you could help. Please let me know if you need anything else. 

Thank you!


Discussion:
@itsthedevman Thank you for reporting. It's an oversight when I added `nested_attribute` method. And your solution should be correct, so I'm adding your example code to test cases and see if it resolves the issue.
@itsthedevman
https://github.com/okuramasafumi/alba/releases/tag/v3.0.3
A new version fixes this bug.
You're awesome. Thank you so much! 



Patch:
diff --git a/lib/alba/nested_attribute.rb b/lib/alba/nested_attribute.rb
index 20a52f1..ecf8634 100644
--- a/lib/alba/nested_attribute.rb
+++ b/lib/alba/nested_attribute.rb
@@ -11,12 +11,13 @@ def initialize(key_transformation: :none, &block)
 
     # @param object [Object] the object being serialized
     # @param params [Hash] params Hash inherited from Resource
+    # @param within [Object, nil, false, true] determines what associations to be serialized. If not set, it serializes all associations.
     # @return [Hash] hash serialized from running the class body in the object
-    def value(object:, params:)
+    def value(object:, params:, within:)
       resource_class = Alba.resource_class
       resource_class.transform_keys(@key_transformation)
       resource_class.class_eval(&@block)
-      resource_class.new(object, params: params).serializable_hash
+      resource_class.new(object, params: params, within: within).serializable_hash
     end
   end
 end
diff --git a/lib/alba/resource.rb b/lib/alba/resource.rb
index 12224ae..c2fcca6 100644
--- a/lib/alba/resource.rb
+++ b/lib/alba/resource.rb
@@ -247,7 +247,7 @@ def fetch_attribute(obj, key, attribute) # rubocop:disable Metrics/CyclomaticCom
                 when Proc then instance_exec(obj, &attribute)
                 when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(obj, params: params, within: within) }
                 when TypedAttribute then attribute.value(obj)
-                when NestedAttribute then attribute.value(object: obj, params: params)
+                when NestedAttribute then attribute.value(object: obj, params: params, within: @within)
                 when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: obj) { |attr| fetch_attribute(obj, key, attr) }
                 else raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
                 end


Test patch:
diff --git a/test/usecases/circular_association_test.rb b/test/usecases/circular_association_test.rb
index b9febee..d33ec72 100644
--- a/test/usecases/circular_association_test.rb
+++ b/test/usecases/circular_association_test.rb
@@ -194,4 +194,125 @@ def test_within_option_with_invalid_type
       BookResource.new(book, within: 'book').serialize # This is not supported
     end
   end
+
+  class User
+    attr_accessor :id, :full_name, :company
+
+    def type
+      'users'
+    end
+  end
+
+  class Company
+    attr_accessor :id, :name, :users
+
+    def type
+      'companies'
+    end
+  end
+
+  class UserResource
+    include Alba::Resource
+
+    attributes :type, :id
+
+    nested_attribute :attributes do
+      attributes :full_name
+    end
+
+    nested_attribute :relationships do
+      has_one :company, resource: CompanyResource
+    end
+  end
+
+  class CompanyResource
+    include Alba::Resource
+
+    attributes :type, :id
+
+    nested_attribute :attributes do
+      attributes :name
+    end
+
+    nested_attribute :relationships do
+      has_many :users, resource: UserResource
+    end
+  end
+
+  # rubocop:disable Style/StringHashKeys
+  def test_within_for_nested_attributes
+    company = Company.new.tap do |c|
+      c.id = 42
+      c.name = 'a_new_company'
+    end
+
+    users = [
+      User.new.tap do |u|
+        u.id = 43
+        u.full_name = 'a_new_user_0'
+      end
+    ]
+
+    company.users = users
+    users.each { |u| u.company = company }
+
+    result1 = {
+      'type' => 'companies',
+      'id' => 42,
+      'attributes' => {
+        'name' => 'a_new_company'
+      },
+      'relationships' => {
+        'users' => [
+          {
+            'type' => 'users',
+            'id' => 43,
+            'attributes' => {
+              'full_name' => 'a_new_user_0'
+            },
+            'relationships' => {}
+          }
+        ]
+      }
+    }
+    assert_equal(
+      result1,
+      CompanyResource.new(company, within: [:users]).to_h
+    )
+
+    result2 = {
+      'type' => 'companies',
+      'id' => 42,
+      'attributes' => {
+        'name' => 'a_new_company'
+      },
+      'relationships' => {
+        'users' => [
+          {
+            'type' => 'users',
+            'id' => 43,
+            'attributes' => {
+              'full_name' => 'a_new_user_0'
+            },
+            'relationships' => {
+              'company' => {
+                'type' => 'companies',
+                'id' => 42,
+                'attributes' => {
+                  'name' => 'a_new_company'
+                },
+                'relationships' => {}
+              }
+            }
+          }
+        ]
+      }
+    }
+
+    assert_equal(
+      result2,
+      CompanyResource.new(company, within: {users: :company}).to_h
+    )
+  end
+  # rubocop:enable Style/StringHashKeys
 end