Skip to content

Commit

Permalink
[Feat] Add prefer_resource_method!
Browse files Browse the repository at this point in the history
This is a way to change current behavior of when there are two
methods with the same name, one on the resource and one on the
object.
Some say they prefer defining methods instead of using `attribute`
since it's easier to type methods than type DSL.
The problem is that methods on resource are not called if there's
a method with the same name on the object.
I understand this situation and I'd like to support it.
  • Loading branch information
okuramasafumi committed Jul 22, 2023
1 parent 72a884d commit d64e010
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 0 deletions.
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
61 changes: 61 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,61 @@
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
end

0 comments on commit d64e010

Please sign in to comment.