Reputation: 7307
I have two models, User
and Account
.
# account.rb
belongs_to :user
# user.rb
has_one :account
Account
has an attribute name
. And in my views, I was calling current_user.account.name
multiple times, and I heard that's not the great of a way to do it. So I was incredibly swift, and I created the following method in my user.rb
def account_name
self.account.name
end
So now in my view, I can simply call current_user.account_name
, and if the association changes, I only update it in one place. BUT my question is, do I test this method? If I do, how do I test it without any mystery guests?
Upvotes: 0
Views: 390
Reputation: 20263
This is going to be a bit off-topic. So, apologies in advance if it's not interesting or helpful.
TL;DR: Don't put knowledge of your models in your views. Keep your controllers skinny. Here's how I've been doing it.
In my current project, I've been working to make sure my views have absolutely no knowledge of anything about the rest of the system (to reduce coupling). This way, if you decide to change how you implement something (say, current_user.account.name
versus current_user.account_name
), then you don't have to go into your views and make changes.
Every controller action provides a @results
hash that contains everything the view needs to render correctly. The structure of the @results
hash is essentially a contract between the view and the controller.
So, in my controller, @results
might look something like {current_user: {account: {name: 'foo'}}}
. And in my view, I'd do something like @results[:current_user][:account][:name]
. I like using a HashWithIndifferentAccess
so I could also do @results['current_user']['account']['name']
and not have things blow up or misbehave.
Also, I've been moving as much logic as I can out of controllers into service objects (I call them 'managers'). I find my managers (which are POROs) a lot easier to test than controllers. So, I might have:
# app/controllers/some_controller.rb
class SomeController
def create
@results = SomeManager.create(params)
if @results[:success]
# happy routing
else
# sad routing
end
end
end
Now, my controllers are super skinny and contain no logic other than routing. They don't know anything about my models. (In fact, almost all of my controller actions look exactly the same with essentially the same six lines of code.) Again, I like this because it creates separation.
Naturally, I need the manager:
#app/managers/some_manager.rb
class SomeManager
class << self
def create(params)
# do stuff that ends up creating the @results hash
# if things went well, the return will include success: true
# if things did not go well, the return will not include a :success key
end
end
end
So, in truth, the structure of @results
is a contract between the view and the manager, not between the view and the controller.
Upvotes: 0
Reputation: 121
I agree there is nothing wrong with current_user.account.name - while Sandi Metz would tell us "User knows too much about Account" this is kind of the thing you can't really avoid w/ Active Record.
If you found you were doing a lot of these methods all over the User model you could use the rails delegate method:
delegate :name, :to => :account, :prefix => true
using the :prefix => true option will prefix the method in the User model so it is account_name. In this case I would assume you could write a very simple unit test on the method that it returns something just incase the attribute in account would ever change your test would fail so you would know you need to update the delegate method.
Upvotes: 1
Reputation: 14610
Personally I see no good reason for any of this. Just use current_user.account.name.
If you are worrying about efficiency, have current_user return a user that joins account.
Upvotes: 0