It is very easy to have very important choices made inside a rails template, like permissions (can X access Y?) and it is similarly easy to ignore permission checks and just query anything you like.
This makes it also very easy to end up with N+1 issues that are not caught in tests (because the tests don't render views), "forgetting" to scope the data (rendering more records than the user should be able to access) or rendering sensitive attributes the user should not have access to.
Even with tests that do include rendering the views, it is very easy to have a test like:
it "renders my posts" do
post0 = create(:post, user: current_user, title: "Foo")
post1 = create(:post, user: current_user, title: "Bar")
response = get "/my-posts"
document = parse_html response.body
expect(document.css("tr td.title").map(&:text))
.to match(["Foo", "Bar"])
end
Now there's so much wrong with this test, but the primary issue is when the view does something like this:
<%= render "posts", posts: Post.all %>
The test implies the document should only render the current user's posts, but the view / template actually renders all posts. You don't want to test this kind of logic by inspecting HTML, because it is error-prone and expensive to test this way.
What if you want to change the HTML structure? You'll end up rewriting all these tests, and it is VERY EASY to make subtle mistakes when rewriting the tests, resulting in false positives in the future.
Instead, I think you should separate this logic to a model, not to be confused with "activerecord classes" - I'm thinking of a `UserPosts` model, like this:
module UserPosts
extends self
def get_user_posts(user, opts)
user.posts.with_opts_or_something(opts)
end
end
Then you also disable automatic loading of relations (forgot the config name) and, by convention (or enforced in some way), never refer to any model modules or classes in a view (or any method called by the view, like the helpers).
Your controller will likely look like this:
class UserPostsController < Whatever::Base
include UserPosts
def index
@posts = get_user_posts(current_user, page: params[:page])
end
end
By keeping the domain logic inside specialized modules per access scope (i'd have separate `Posts`, `PublicPosts`, `AdminPosts`, `ModeratorPosts` modules), you can test the logic in tests per module (including tests what the module SHOULD NOT access). Then, when writing the views and controllers, you don't need to re-test or re-remember to test the "should not" cases - the test cases you're easy to forget.
You can also design the modules in a way that they only load (select) the properties that context would be allowed to access. If you have sensitive data (like a user's real name or email), you don't even want to load that property when loading the users from a `PublicPosts` context, since public posts should only render the user's id and public nickname for example. You can also wrap the posts/users in presenters using whatever fancy gems you fancy, as long as it gets the job done.
In an ideal world, I'd lock down Ruby templates to only have access to previously loaded data via instance variables and helpers, nothing else.
It is a bit hard to explain, since I have limited time to address it properly, but I hope I get the point across.