Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefer resource method #323

Merged
merged 2 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Style/InlineComment:
Enabled: false

Style/MethodCallWithArgsParentheses:
AllowedMethods: ['require', 'require_relative', 'include', 'extend', 'puts', 'p', 'warn', 'raise', 'send', 'public_send']
AllowedMethods: ['require', 'require_relative', 'include', 'extend', 'puts', 'p', 'warn', 'raise', 'send', 'public_send', 'alias_method']
Exclude:
# There are so many `attributes` call without parenthese and that's absolutely fine
- 'test/**/*.rb'
Expand Down
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,72 @@ class UserResource
end
```

#### Prefer methods on resource

By default, Alba prefers methods on the object to methods on the resource. This means if you have a following situation:

```ruby
class User
attr_accessor :id, :name, :email

def initialize(id, name, email)
@id = id
@name = name
@email = email
end

def name_with_email
"dummy!"
end
end

class UserResource
include Alba::Resource

root_key :user, :users # Later is for plural

attributes :id, :name, :name_with_email

# Same method exists in `User` class!
# This is not called
def name_with_email(user)
"#{user.name}: #{user.email}"
end
end

user = User.new(1, 'Masafumi OKURA', '[email protected]')
UserResource.new(user).serialize
# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"dummy!"}}'
```

You can see that `name_with_email` is now `dummy!` from `User#name_with_email`. You cna change this behavior by using `prefer_resource_method!` DSL in a resource class:

```ruby
# With the same `User` class

class UserResource
include Alba::Resource

prefer_resource_method! # This line is important

root_key :user, :users # Later is for plural

attributes :id, :name, :name_with_email

# Same method exists in `User` class!
# But now this is called!
def name_with_email(user)
"#{user.name}: #{user.email}"
end
end

user = User.new(1, 'Masafumi OKURA', '[email protected]')
UserResource.new(user).serialize
# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: [email protected]"}}'
```

The next major version of Alba will change this default behavior to prefer resource methods. In case you want to preserve current behavior, there's `prefer_object_method!` DSL, which does that.

#### Params

You can pass a Hash to the resource for internal use. It can be used as "flags" to control attribute content.
Expand Down
21 changes: 21 additions & 0 deletions lib/alba/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,23 @@ def fetch_attribute(obj, key, attribute) # rubocop:disable Metrics/CyclomaticCom
value.nil? && nil_handler ? instance_exec(obj, key, attribute, &nil_handler) : value
end

# TODO: from version 3, `_fetch_attribute_from_resource_first` is default
def fetch_attribute_from_object_and_resource(obj, attribute)
_fetch_attribute_from_object_first(obj, attribute)
end

def _fetch_attribute_from_object_first(obj, attribute)
obj.__send__(attribute)
rescue NoMethodError
__send__(attribute, obj)
end

def _fetch_attribute_from_resource_first(obj, attribute)
__send__(attribute, obj)
rescue NoMethodError
obj.__send__(attribute)
end

def nil_handler
@_on_nil
end
Expand Down Expand Up @@ -510,6 +521,16 @@ def helper(mod = @_helper || Module.new, &block)
extend mod
@_helper = mod
end

# DSL for alias, purely for readability
def prefer_resource_method!
alias_method :fetch_attribute_from_object_and_resource, :_fetch_attribute_from_resource_first
end

# DSL for alias, purely for readability
def prefer_object_method!
alias_method :fetch_attribute_from_object_and_resource, :_fetch_attribute_from_object_first
end
end
end
end
73 changes: 73 additions & 0 deletions test/usecases/object_method_and_resource_method_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require_relative '../test_helper'

class ObjectMethodAndResourceMethodTest < Minitest::Test
class Foo
attr_reader :id
def initialize(id)
@id = id
end
end

def setup
@foo = Foo.new(1)
end

class FooResource
include Alba::Resource

prefer_resource_method!

attributes :id

def id(_)
42
end
end

def test_prefer_resource_method
assert_equal '{"id":42}', FooResource.new(@foo).serialize
end

class FooResource2
include Alba::Resource

prefer_object_method!

attributes :id

def id(_)
42
end
end

def test_prefer_object_method
assert_equal '{"id":1}', FooResource2.new(@foo).serialize
end

class FooResource3
include Alba::Resource

attributes :id

def id(_)
42
end
end

# TODO: perfer resource method by default from version 3
def test_default_behavior
assert_equal '{"id":1}', FooResource3.new(@foo).serialize
end

class FooResource4
include Alba::Resource

prefer_resource_method!

attributes :id
end

def test_prefer_resource_method_but_it_is_not_there
assert_equal '{"id":1}', FooResource4.new(@foo).serialize
end
end