Skip to content

Use membership decorator in view #351

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

Merged
merged 12 commits into from
May 17, 2018
Merged

Conversation

rewritten
Copy link
Contributor

@rewritten rewritten commented May 8, 2018

Solves #349

What

I add here a decorator for membership. It has access to the view context, and exposes exactly what's needed to render the view.

Why

User list view (table rows mostly) uses several models in parallel and it's difficult to follow, with all the conditionals. A decorator encapsulates all the logic in a testable class.

Bonus

I have added dry-initializer gem, which is a very lightweight library to help with different kinds of instance initialization strategies. It is not really needed here, but it's a nice addition in general, as I would also like to introduce to more dry-* gems in the future.

  • Write the decorator
  • Specify all the decorator behavior
  • Modify the view so it relies on the decorated member instead of user + membership
  • Pass only the decorated members from UsersController#index

@rewritten rewritten requested a review from markets May 9, 2018 01:21
@rewritten
Copy link
Contributor Author

Lots of spec-related noise, I will squash all commits after CR is done, before merging.

Copy link
Collaborator

@markets markets left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice refactor @rewritten 👏 Decorators (and view models) are really useful for clean up logic inside views.

I also like a lot the dry-* family, but not sure if we should introduce new deps. for things we can easily implement with Ruby stdlib, even if they are nice additions. In this case, it's a really lightweight gem, so not really a big deal, but in general I'd prefer "plain" Ruby vs "syntax sugar" plugins.

I left another small comment above. But really nice PR 👌. Happy to see you here again! 😃

end

def inactive_icon
v.tag(:span, class: %w[glyphicon glyphicon-time]) unless active?
Copy link
Collaborator

@markets markets May 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rewritten I realized this produces a different html compared to the original code: an anchor inside the span (when called along with link_to_self). I think this is because this line returns a self-closed span. It looks like this:

captura de pantalla 2018-05-12 a la s 18 01 52

We can fix that by using the glyph helper:

v.glyph('time') unless active?

I just tried it locally and it looks fine:

captura de pantalla 2018-05-12 a la s 18 18 42

@sauloperez
Copy link
Collaborator

sauloperez commented May 12, 2018

Welcome back home @rewritten !!

I also like a lot the dry-* family, but not sure if we should introduce new deps. for things we can easily implement with Ruby stdlib, even if they are nice additions. In this case, it's a really lightweight gem, so not really a big deal, but in general I'd prefer "plain" Ruby vs "syntax sugar" plugins.

I couldn't agree more. I particular decision we took about the development of the project (and we should state in the documentation) is that we want to keep it as simple and lightweight as possible, in terms of both infrastructure and code, to keep its maintenance cost at the minimum. We don't have enough resources to spend dev time on it and so we aim to be 80% contributor-driven.

This directly affects how serious we take the addition of new dependencies such as gems, which need to be maintained in the long run. So I suggest we remove that dry-initializer gem (I know it's hard. I do also like dry-* gems).

Besides this, I ❤️ the direction this PR is taking.


@members = current_organization.
members.
where(user_id: user_ids).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we rather do an AR merge? This avoids one SQL query which saves a network round trip, etc. PostgreSQL knows how to match against those users better than us.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostgreSQL knows, ActiveRecord sometimes messes up the joins. I will try to see if this is working in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it is not working in this case. As @users already comes from a JOIN, AR is not really able to distinguish how to prepare the association chain.

Here it is a direct ransack on members, so we don't have to mix stuff up.


private

def h
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about a more meaningful name like routes? It would help other devs understand all this.

wrong_email_member.user_id => wrong_email_member,
empty_email_member.user_id => empty_email_member
})
assigns(:members).each { |m| expect(m).to be_a(MemberDecorator) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's a not a good idea to couple to particular class types. Makes future changes harder and it's sort of against Ruby's duck-type principle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok but still somewhat I want to specify that what's sent to the view is a list of decorated objects. What about a general method decorated_object on the decorator? Instead of member. I have used member just for laziness, for instance when using draper I tend to use object instead of the named accessor.

# merge(@search.result(distinct: false)).
# page(params[:page]).
# per(25).
# map { |m| MemberDecorator.new(m, self.class.helpers) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I left this here to remind about making the member table ransack against members itself. I will try to complete the feature, and make the whole page based on members so we don't need the additional @users variable here.

@rewritten
Copy link
Contributor Author

  • Dry removed 😢
  • Base ViewModel class added, it should be more contributor-friendly
  • Refactored users index action to be actually based on members (the only remnant is that it's UsersController, because it's also accessed with the members path...)
  • Slightly tweaked index view so ransack works based on Member relation

Ready for your second review :)

default_sort

@search =
current_organization.members.joins(:account, :user).ransack(params[:q])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There doesn't seem to be any difference between doing joins before ransack.result or after it, and I don't have a clear reason for it to be in any of them instead of the other. The only "intuition" I have is that by joining after the search, we may risk joining twice. Any input?

Maybe I should just ditch the joins and use only eager_load.

@members =
@search.result.page(params[:page]).per(25)

@member_view_models =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no easy way to keep the pagination info exposed and at the same time to decorate the objects. It's this or another "paginated collection decorator", which most of the times I find ugly.

Copy link
Collaborator

@sauloperez sauloperez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 congrats @rewritten ! This improves a lot the view and sets the foundation for further improvements 🎉

@rewritten
Copy link
Contributor Author

@markets, @sauloperez what's the process now? Who merges? Who deploys?

@sauloperez
Copy link
Collaborator

sauloperez commented May 14, 2018

we need @sseerrggii and/or his testing crew to give it a try. As soon as it gets their approve we can go ahead and merge.

@sseerrggii
Copy link
Contributor

I have tested and everything works fine for me 👍

Copy link
Contributor

@enricostano enricostano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks @rewritten . Welcome back! 💪

@sauloperez sauloperez merged commit e45a9bb into develop May 17, 2018
@sauloperez sauloperez deleted the feature/membership-decorator branch May 17, 2018 08:06
@enricostano enricostano mentioned this pull request Aug 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants