Skip to content
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

Localize list tabs' routes #157

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 32 additions & 31 deletions src/main/java/org/mamute/controllers/ListController.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import org.mamute.dao.QuestionDAO;
import org.mamute.dao.TagDAO;
import org.mamute.factory.MessageFactory;
import org.mamute.list.Tab;
import org.mamute.list.TabsHelper;
import org.mamute.model.LoggedUser;
import org.mamute.model.News;
import org.mamute.model.Question;
Expand All @@ -21,8 +23,6 @@
import javax.servlet.http.HttpServletResponse;
import java.util.List;

import static java.util.Arrays.asList;

@Routed
@Controller
public class ListController {
Expand All @@ -36,7 +36,8 @@ public class ListController {
@Inject private RecentTagsContainer tagsContainer;
@Inject private HttpServletResponse response;
@Inject private MessageFactory messageFactory;

@Inject private TabsHelper tabsHelper;

@Get
public void home(Integer p) {
Integer page = getPage(p);
Expand All @@ -45,8 +46,7 @@ public void home(Integer p) {
result.notFound();
return;
}
List<String> tabs = asList("voted", "answered", "viewed");
result.include("tabs", tabs);
result.include("tabs", this.tabsHelper.getTabs());

result.include("questions", visible);
result.include("totalPages", questions.numberOfPages());
Expand All @@ -57,16 +57,14 @@ public void home(Integer p) {

@Streamed
public void streamedHome(Integer p) {
List<String> tabs = asList("voted", "answered", "viewed");
result.include("tabs", tabs);
result.include("tabs", this.tabsHelper.getTabs());
result.include("currentUser", loggedUser);
}

@Cached(key="questionListPagelet", duration = 30, idleTime = 30)
@Streamed
public void questionListPagelet(Integer p) {
List<String> tabs = asList("voted", "answered", "viewed");
result.include("tabs", tabs);
result.include("tabs", this.tabsHelper.getTabs());
result.include("currentUser", loggedUser);
Integer page = getPage(p);
List<Question> visible = questions.allVisible(page);
Expand All @@ -92,33 +90,24 @@ public void sideBarPagelet() {

@Get
public void topRaw() {
result.redirectTo(this).top("voted");
result.redirectTo(this).topVoted();
}

@Get
public void top(String section) {
Integer count = 35;

List<String> tabs = asList("voted", "answered", "viewed");
if (!tabs.contains(section)) {
section = tabs.get(0);
result.redirectTo(this).top(section);
return;
}
public void topVoted() {
top(this.tabsHelper.tabForType(Tab.Type.VOTED));
}

DateTime since = DateTime.now().minusMonths(2);
List<Question> top = questions.top(section, count, since);

if (top.isEmpty()) {
result.notFound();
return;
}
result.include("tabs", tabs);
result.include("section", section);
result.include("questions", top);
result.include("currentUser", loggedUser);
@Get
public void topAnswered() {
top(this.tabsHelper.tabForType(Tab.Type.ANSWERED));
}


@Get
public void topViewed() {
top(this.tabsHelper.tabForType(Tab.Type.VIEWED));
}

@Get
public void hackedIndex() {
result.redirectTo(this).home(1);
Expand Down Expand Up @@ -191,4 +180,16 @@ private Integer getPage(Integer p) {
return page;
}

private void top(Tab tab) {
Integer count = 35;
DateTime since = DateTime.now().minusMonths(2);

List<Question> top = this.questions.top(tab, count, since);

result.include("tabs", this.tabsHelper.getTabs());
result.include("questions", top);
result.include("currentUser", loggedUser);
result.forwardTo("/WEB-INF/jsp/list/top.jsp");
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reasons to remove the section include?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I haven't seen it would be used anywhere, I'll double check this, thanks

}

}
17 changes: 8 additions & 9 deletions src/main/java/org/mamute/dao/QuestionDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.joda.time.DateTime;
import org.mamute.dao.WithUserPaginatedDAO.OrderType;
import org.mamute.dao.WithUserPaginatedDAO.UserRole;
import org.mamute.list.Tab;
import org.mamute.model.*;
import org.mamute.model.interfaces.RssContent;

Expand Down Expand Up @@ -152,17 +153,15 @@ public List<Question> hot(DateTime since, int count) {
.list();
}

public List<Question> top(String section, int count, DateTime since) {
public List<Question> top(Tab tab, int count, DateTime since) {
Order order;
if (section.equals("viewed")) {
order = Order.desc("q.views");
}
else if (section.equals("answered")) {
order = Order.desc("q.answerCount");
}
else /*if (section.equals("voted"))*/ {
order = Order.desc("q.voteCount");

switch (tab.getType()) {
case VIEWED: order = Order.desc("q.views"); break;
case ANSWERED: order = Order.desc("q.answerCount"); break;
default: order = Order.desc("q.voteCount"); break;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You could create a method at the Tab enum that returns the Order for that tab.

Order order = tab.getOrder();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see your point, however don't you think that would be too much of a mixing concerns? Why would a Tab object need to know about SQL queries? What about rather changing the getType() to getOrder() which would return a order enum, getting rid of type all together? This still gives better idea about what tab is doing, but does not make a dependency between Tag and the SQL

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe you are saying exactly what I meant, no? haha

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I see, so it's just about renaming the type into order. Got it, agree with you!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I see, so it's just about renaming the type into order. Got it, agree with you!


return session.createCriteria(Question.class, "q")
.add(and(Restrictions.eq("q.moderationOptions.invisible", false)))
.add(gt("q.createdAt", since))
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/org/mamute/list/Tab.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.mamute.list;

public class Tab {
private final Type type;
private final String localizationKey;
private final String link;

public Tab(Type type, String localizationKey, String link)
{
this.type = type;
this.localizationKey = localizationKey;
this.link = link;
}

public String getLocalizationKey() { return this.localizationKey; }
public String getLink() { return this.link; }
public Type getType() { return this.type; }

public enum Type {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would make the type separated from the Tab class(it could be within the same package with package-private access modifier)
Is the Tab class really necessary here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for this, as explained I'm pretty new to Java world so I thought nesting classes is considered okay. I'll move it to a separate file.

VOTED("voted"), ANSWERED("answered"), VIEWED("viewed");

private final String stringValue;

Type(String stringValue) {
this.stringValue = stringValue;
}

public String getStringValue() {
return this.stringValue;
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/org/mamute/list/TabsHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.mamute.list;

import br.com.caelum.vraptor.http.route.Router;
import net.vidageek.mirror.dsl.Mirror;
import org.apache.commons.lang.WordUtils;
import org.mamute.controllers.ListController;

import javax.inject.Inject;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import static java.util.Arrays.asList;

public class TabsHelper {
Copy link
Contributor

Choose a reason for hiding this comment

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

I personally don't like this approach, seems like you are separating behaviour from attributes, making the Tab model anemic.
I would put those methods at Tab, it should know how to be created and its possible values

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well the stuff implemented here is not a model business logic per-se, it is rather a mapping of the information model shouldn't know about. Think of a database model doing its own database connection. You would usually abstract that out and inject the values into the model once you fetch them. It doesn't mean that you introduced a anemic model by doing this. Again, your repo, your rules, so let me know if you insist on this :)

Copy link
Contributor

Choose a reason for hiding this comment

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

In that case I could create a ConnectionProvider class or so, which is specific enough for others to know what it is supposed to do. My point is just that I don't like helper classes :)
Maybe if we find a better name for this one, we can keep it that way


private final Router router;

@Inject
public TabsHelper(Router router) {
this.router = router;
}

public Tab tabForType(Tab.Type tabType) {
String localizationKey = "menu.top." + tabType.getStringValue();
String methodName = "top" + WordUtils.capitalize(tabType.getStringValue());
return new Tab(tabType, localizationKey, urlForMethod(methodName));
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be a static factory method at Tab

Tab tab = Tab.forType(type);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I considered this actually. What I didn't want to do is create a DOM with a lot of horizontal dependencies (e.g. the router dependency). Figuring out a controller method responsible for specific tag in the tag itself feels even more scary as this introduces upwards dependency between low level DOM and controller. It's your repo, so I can merge the Helper and Tab if you insist, but if feels wrong?

Copy link
Contributor

Choose a reason for hiding this comment

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

Again, I'm just trying to eliminate this helper class. Of course you could create a factory or so for that if you don't want to put that in the model(which is fair enough)

}

public ArrayList<Tab> getTabs() {
ArrayList<Tab> tabs = new ArrayList<Tab>();

for (Tab.Type tabType : asList(Tab.Type.values())) {
tabs.add(tabForType(tabType));
}

return tabs;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This could also be at Tab

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see comment above


private String urlForMethod(String method) {
return this.router.urlFor(ListController.class, method(ListController.class, method));
}

private Method method(Class<?> clazz, String method) {
return new Mirror().on(clazz).reflect().method(method).withAnyArgs();
}
}
4 changes: 3 additions & 1 deletion src/main/resources/routes.properties
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ HistoryController.reject = /reject/{typeName}/{informationId}

ListController.home = /
ListController.topRaw = /top
ListController.top = /top/{section}
ListController.topViewed = /top/viewed
ListController.topAnswered = /top/answered
ListController.topVoted = /top/voted
ListController.hackedIndex = /questions
ListController.news = /news, /news/
ListController.unsolved = /unsolved
Expand Down
4 changes: 3 additions & 1 deletion src/main/resources/routes.pt-BR.properties
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ HistoryController.reject = /rejeitar/{typeName}/{informationId}

ListController.home = /
ListController.topRaw = /top
ListController.top = /top/{section}
ListController.topViewed = /top/viewed
ListController.topAnswered = /top/answered
ListController.topVoted = /top/voted
ListController.hackedIndex = /perguntas
ListController.news = /noticias, /noticias/
ListController.unsolved = /nao-resolvido
Expand Down
4 changes: 0 additions & 4 deletions src/main/webapp/WEB-INF/jsp/list/top.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@

<tags:header facebookMetas="${true}" title="${genericTitle} - ${title}" description="${description}"/>

<%-- <tags:tabs titleKey="menu.top">
<a href="${linkTo[ListController].top('voted')}">${t['menu.top.voted']}</a>
<a href="${linkTo[ListController].top('answered')}">${t['menu.top.answered']}</a>
</tags:tabs> --%>
<c:set var="title" value="${t['menu.questions']}"/>

<section class="first-content content">
Expand Down
3 changes: 1 addition & 2 deletions src/main/webapp/WEB-INF/tags/rssTagHeader.tag
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
<c:if test="${not empty tabs}">
<tags:tabs title="${title}" useSubheader="${false}">
<c:forEach var="tab" items="${tabs}">
<c:set var="tabText" value="menu.top.${tab}"/>
<a href="${linkTo[ListController].top(tab)}">${t[tabText]}</a>
<a href="${tab.getLink()}">${t[tab.getLocalizationKey()]}</a>
</c:forEach>
</tags:tabs>
</c:if>
Expand Down
49 changes: 49 additions & 0 deletions src/test/java/org/mamute/list/TabsHelperTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.mamute.list;

import br.com.caelum.vraptor.http.route.Router;
import org.junit.Before;
import org.junit.Test;
import org.mamute.controllers.ListController;

import java.util.List;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class TabsHelperTest {

private Router router;

@Before
public void setup() {
router = mock(Router.class);
}

@Test
public void tabForType_creates_tab_with_correct_values() throws NoSuchMethodException {
when(this.router.urlFor(ListController.class, ListController.class.getMethod("topAnswered"))).thenReturn("/answer-link");
TabsHelper subject = new TabsHelper(this.router);

Tab tab = subject.tabForType(Tab.Type.ANSWERED);

assertEquals(tab.getLink(), "/answer-link");
assertEquals(tab.getLocalizationKey(), "menu.top.answered");
assertEquals(tab.getType(), Tab.Type.ANSWERED);
}

@Test
public void getTabs_returns_all_tabs_in_correct_order() throws NoSuchMethodException {
when(this.router.urlFor(ListController.class, ListController.class.getMethod("topAnswered"))).thenReturn("/answer-link");
when(this.router.urlFor(ListController.class, ListController.class.getMethod("topViewed"))).thenReturn("/viewed-link");
when(this.router.urlFor(ListController.class, ListController.class.getMethod("topVoted"))).thenReturn("/voted-link");
TabsHelper subject = new TabsHelper(this.router);

List<Tab> tabs = subject.getTabs();

assertEquals(tabs.size(), 3);
assertEquals(tabs.get(0).getType(), Tab.Type.VOTED);
assertEquals(tabs.get(1).getType(), Tab.Type.ANSWERED);
assertEquals(tabs.get(2).getType(), Tab.Type.VIEWED);
}
}