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
Configurable ordering for messageboards #404
Configurable ordering for messageboards #404
Conversation
Instead of sorting with ruby what do you think about attaching a scope to messageboards that will order w/SQL? That is - if the |
Totally agree in principle, but I tried and the grouping made it non Would like to do a quick fix for this now and then come back to optimise it On Saturday, 17 September 2016, Joel Oliveira notifications@github.com
(I've shortened my email address to tim@red56.uk (three less letters!), but |
@@ -7,7 +7,7 @@ | |||
<% if group.name.present? %> | |||
<h3><%= group.name %></h3> | |||
<% end %> | |||
<%= render group.messageboards %> | |||
<%= render group.messageboards.sort_by{|m| - m.updated_at.to_f } %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be in the view model or the controller
Good call @glebm -- moving it to view model made it easy to test. I also realized that Messageboard groups (I don't use them) should be listed in some kind of consistent order on the main page. I chose alphabetic, but there are better orderings, I'm sure. (we can wait for a PR from someone who uses messageboard groups?) |
So, I didn't realize/remember that the current ordering wasn't emergent, but deliberate (though dependent on database implementation rather than sql). Is it ok to change this ordering for Messageboards in general (or do we need to make another configuration option). Background: I just read that: Within a group, the messageboards are ordered in the order they were created at. Groups themselves are also ordered in the order they were created at. I think that given the UI, the updated_at would be the least surprising ordering for messageboards. And for Messageboard Groups, the least surprising (though possibly not very helpful) would be ordering by name alphabetically. |
@timdiggins @jayroh It seems to me that many public forums have deliberate ordering for both groups and messageboards. Perhaps this should be an option? |
@glebm @jayroh I can see that might be useful in some instances, but I think it would still need to be optional -- It wouldn't fit my usage -- as messageboards in my system are many and created semi-automatically and not managed as such (they correspond to "projects" in the main app). I think this introduction of an ordering will need careful design. But let's say we introduce a ordering field which is optional, and there's a way to manage and order them, in we still need a tie-breaker for cases where this ordering field/management option isn't used. I just think that currently the UI and the ordering are not in alignment. This PR fixes it (perfectly for my use case 😉 ) and would like to defer the ordering improvement to a different issue / PR... Further thoughts on ordering: We could also have configuration for ordering -- this could be complicated to implement (e.g. I'd like everything (messageboards / topics / posts) to be consistently reverse-chronologically ordered, but other people might want messageboards to be ordered by a determined order (or creation order?), topics to be reverse-chronologically ordered and posts to be chronologically ordered)). And then, it's one thing to create a mechanism to allow for multiple orderings, and then the implications (e.g. page on UserTopicReadState) but then these have UI implications not just for now, but for every time any change happens-- quite a big deal. I think better to have a thredded approach if possible, and then stick to it. |
A specific order is perfect for my use case, which is why I'm hesitant to accept this unless it's optional. :) Specific order is also the case with traditional messageboards (like phpbb), and other Ruby forum engines like Forem. For messageboard groups and messageboards, a proper interface would need to be designed carefully, but perhaps for now we could just offer a boolean config setting to enable the reverse chronological order (or maybe separate booleans for messageboard groups and messageboards). We can then deprecate these settings once a proper interface is in place. I don't think the ordering of topics within a messageboard and posts within a topic needs to be customizable. Can't think of a use case for anything but most recently updated first. |
Ok -- that sounds good -- options would be (maybe same for messageboards & messageboard groups for now):
When we get specific order, the ordering field (when not nil/default) trumps the messageboards_order. In fact it would be trivial to add this field ( However, in response to (which is a side-track):
Currently posts are chronological. Would you (and @jayroh and others) be happy just reversing this always (as opposed to configuration: #361) |
Sounds good!
Sorry, yeah the posts are currently chronological. This does need to be an option, as most forums out there have posts in chronological order. |
|
ok, I've been thinking this through a bit more as I've been coding. I think we should have a Another similar configuration option for MessageboardGroups I think (but maybe :alphabetical as another option) |
@timdiggins Sounds good! Some comments on the naming: Perhaps The ordering names should probably match column names for clarity, e.g. |
Messageboards:
Messageboard Groups:
|
have just discovered in messageboard.rb:
I am planning to remove the order(topics_count: :desc) from the default scope and make As a separate issue we should remove the default_scope -- see #407 So:
|
863b71a
to
deca7f8
Compare
(though in the wrong place)
* creates #position field * ensures default value * fills with correct value depending on messageboard_group * ordered scope respects #position
deca7f8
to
dfc2001
Compare
To prevent nasty surprises later on.
@glebm Ok, I think this is all done. See if there's anything surprising in there. The only difference from what was discussed above is that there is no real difference between :position and :created_at_asc -- and I've merged them (created_at provides the default value for :position, so that newly created messageboards with no defined position go to the bottom of the list. see https://github.com/thredded/thredded/pull/404/files#diff-d24aa57ddf38369b21abef6c31f43cae |
What are the advantages of precalculating the Something like: scope :with_order, ->(order) {
case order
when :position
order(position: :asc)
when :last_post_at_desc
left_outer_joins(:last_topic).
order('COALESCE(last_topic.last_post_at, messageboards.created_at) DESC') # arel here
when :topics_count_desc
order(topics_count: :desc)
end.order(id: :desc)
} This would support sorting in the UI, e.g. if someone wanted to implement a sort by dropdown, or sortable column tables for an old-school theme where the messageboards are rendered in a table. Also seems simpler to implement / maintain. |
@glebm I originally had a similar solution (fe9c189 -- though that was with a different set of options). However I felt that it was very strange to produce a field (position) that is then sometimes not used. Worse if you want to optimize sorting with indexes, it will be configuration-dependent. This feels like configuration doing the wrong thing. I felt that having the configuration just determine the algorithm to fill There's no problem with using sorting in the UI -- that's exactly what the I can see that this solution might look more complicated initially. However maintenance comes in many guises -- the algorithm for calculating position is pretty separated from other code and fairly crisp. And if we've got a weird sql error later on when using Think it's worth ruminating on... |
@glebm totally missed your point initially about sorting in the UI (user chosen sorting as opposed to admin chosen position). Probably tips scales in favour of no precalculation |
@glebm after considering it, I tried it, feels better, and provides more functionality the way you suggested. Have split into multiple scopes, think more readable. The only thing I didn't go for was the arel. I'm sure it's conceptually better, I just can't be bothered to work it out -- given that the sql is really quite readable and AFAICT pretty vanilla sql. Feel free to suggest an arel implementation. |
@timdiggins No worries, I can't refactor it into Arel later. Will have another look |
ordered_by_last_post_at_desc | ||
when :topics_count_desc | ||
ordered_by_topics_count_desc | ||
end.order(id: :asc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a thought, perhaps we can always order by position
, so here this would be
case order
when :position
self
when ...
...
end.order(position: :asc, id: :asc)
This way it would serve as a customizable tie-breaker
case order | ||
when :position | ||
ordered_by_position | ||
when :created_at_asc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we don't need this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Useful if you're composing an user-chosen sort (dropddown) -- might want to choose created_at_asc rather than position.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still orders by position in the ordered_by_created_at_asc
though. And position might not be the same as created_at
.
@@ -18,7 +18,8 @@ def posts_count | |||
private | |||
|
|||
def messageboards | |||
@messageboards ||= Messageboard.all | |||
@ordered = Messageboard.ordered |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this could just be:
@messageboards ||= Messageboard.ordered
order(topics_count: :desc) | ||
} | ||
scope :ordered_by_group, ->(order = Thredded.messageboards_order) { | ||
includes(:group).order('thredded_messageboard_groups.position asc').ordered(order) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this should be a concerned of Thredded::MessageboardGroup
, so that here this would look like:
includes(:group).merge(Thredded::MessageboardGroup.ordered).ordered(order)
And then maybe this scope can be removed (inlined), as it doesn't seem like it's ever useful to order by the group without then grouping by it.
|
||
def self.user_display_name_method | ||
@@user_display_name_method || user_name_column | ||
end | ||
|
||
def self.messageboards_order=(value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some simple docs so that folks get warnings in their IDEs if they pass an invalid value:
# @param value [:position, :topics_count_desc, :last_post_at_desc]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. (all done in fact, I'll push in a second). But one point about thredded.rb @glebm -- I don't get why we're using mattr_accessors, and not cattr_accessors. Thredded is a module and is never included in class and so a mattr_accessor is a weird way of saying cattr_accessor. I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get why we're using mattr_accessors, and not cattr_accessors
I don't know either, though I think cattr_accessor
is just an alias for mattr_accessor
(http://api.rubyonrails.org/classes/Module.html#method-i-cattr_accessor).
But yeah would love to know if there is a better way. We could also replace all these with:
module Thredded
class << self
attr_acessor ...
# regular instance methods / variables on the metaclass here
end
end
when :position, :topics_count_desc, :last_post_at_desc | ||
@@messageboards_order = value # rubocop:disable Style/ClassVars | ||
else | ||
fail "Unexpected value for Thredded.messageboards_order: #{value}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be an ArgumentError
over to you @glebm |
# :position (default) set the position manually (new messageboards go to the bottom, by creation timestamp) | ||
# :last_post_at_desc most recent post first | ||
# :topics_count_desc most topics first | ||
# NB: if you change this you should run `Messageboard.recalculate_positions!` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer applies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
One last thing @glebm @jayroh, I think the old default ordering was :topics_count (it was not always obvious, but I'm pretty sure that's true). The new default ordering in this PR is :position (defaults to :created_at_asc) -- which is a good default. Are we happy to go with that change (it's pre 1.0 after all) and just add a note in the release notes? (@glebm you seem to have to do the releases...) |
@timdiggins Good catch, I hadn't realized that! The new default is OK. I plan to do a release shortly after this has been merged and will emphasize this change in the release notes 🚢 |
I think it looks great, guys. One note - this is a tight enough feature where the 13 commits could probably be squashed down to 1. Otherwise, 👍 from me whenever you're ready to merge. |
great @jayroh merging. (PS: I always squash and merge (now via github) unless there's some really good reason for separate commits and merge commit) |
By default, I usually do too :). Sometimes, though, there's some valuable info and history in the commits so I will be a little more discriminatory regarding WHAT I squash. I'm guessing you're the same way? :) In any case - great work here. Glad this is in. |
No description provided.