Bootstrap Iteration 10: Angular usage improvements, Rogue
Here we are again! Bootstrap is evolving and a new contributor, Krzysztof Ciesielski, joined our ranks! This iteration was a technical one but very interesting at the same time. Highlights of the changes:
⚠️ Bootstrap turns into Bootzooka⚠️
- better Angular files layout
- testing Angular directives (= testing DOM manipulation!)
- migrating from Salat to Lift Record + Rogue
Read on to find out the details.
Rearrangement in UI module
Recently Brian Ford from Google, a developer working on AngularJS, blogged about his recommendations on how to structure non-trivial Angular applications. As the Bootstrap project reaches greater maturity, it was a perfect opportunity to compare its layout and conventions to those proposed by the author. After a short research we decided to apply the following updates.
Redistribution of files and directories
All the controllers, services, directives and filters have been extracted to separate files, which feels quite natural in a growing project. Additionaly we divided all the javascripts files into folders representing these ‘layers’, here’s a sneak peek into this new layout:
Reorganization of Angular modules
The concept of ‘module’ in Angular’s sense can be leveraged to split code into logical ‘contexts’:
- entries (everything that’s related to entries)
- profile (user logon, registration, profile update, password recovery)
- maintenance (server uptime recording)
- session (user session and security concerns)
- directives
- filters
Further expansion of directives and filters should also result in putting these items in their respective modules.
Exploring more awesomeness of Angular directives
Piotr’s recent blogpost about directives illustrates how powerful they can be. I highly recommend that you check it out if you haven’t yet done so. In this iteration we would like to show how you can effectively unit test custom directives. If you thought that “it’s DOM manipulation and cannot be tested” then take a look at this example:
Testing AngularJS directives
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
beforeEach(inject(function ($rootScope, $compile) {
elm = angular.element(
'</pre>
<form name="registerForm" novalidate="">' + '<input type="password" name="password" />' + '<input type="password" name="repeatPassword" />' + '</form>
<pre>')
scope = $rootScope;
scope.model = { password1: null, password2: null};
$compile(elm)(scope);
scope.$digest();
form = scope.registerForm;
}));
it('should be valid initially', function () {
expect(form.password.$valid).toBe(true);
expect(form.repeatPassword.$valid).toBe(true);
});
it('should set model to valid after setting two matching passwords', function () {
// when
form.password.$setViewValue('pass123');
form.repeatPassword.$setViewValue('pass123');
// then
expect(form.password.$valid).toBe(true);
expect(form.repeatPassword.$valid).toBe(true);
});
it('should set model to invalid after setting only first input', function () {
// when
form.password.$setViewValue('pass123');
// then
expect(form.password.$valid).toBe(true);
expect(form.repeatPassword.$valid).toBe(false);
});
In the given example, we prepare a html template using our directive and bind it to test model. Then we use Angular’s $compile() function to process it. Now we can verify the form’s behavior in different conditions. The test cases are expressive and concise, which is not common when it comes to javascript, especially DOM manipulations. See the full code on Github. I also highly recommend that you check out this video where Vojta Jina explores more great examples.
Run your tests from IDEA!
Last but not least: we added additional configuration file allowing you to run javascript unit tests directly from the IDE. This means even more instant feedback and quite a nice productivity boost. More details on how to setup and execute such tests can be found onKrzysiek’s blog.
Migrating from Salat to Rogue
The persistence layer of Bootstrap was composed of Casbah (the MongoDB driver for Scala) and Salat (provides serialization of case classes). The DAO code written using this combination proved to be degrading in readability over time. Luckily, Scala provides great tools to create DSLs and that’s exactly what guys at Foursquare did; they’ve built a DSL for queries on MongoDB: Rogue.
Nothing is as good as some examples, so here is how you’d normally implement a DAO method using Casbah/Salat:
Sample DAO implementation
1
2
3
4
5
class MongoUserDAO(implicit val mongo: MongoDB) extends SalatDAO[User, ObjectId](mongo("users")) with UserDAO {
def changeLogin(currentLogin: String, newLogin: String) {
update(MongoDBObject("login" -> currentLogin), $set("login" -> newLogin, "loginLowerCased" -> newLogin.toLowerCase), false, false, WriteConcern.Safe)
}
}
Such updates don’t look particularily well. First of all, parameters are maps with string keys – what happens when you refactor a field name in your case class? You either change all those string occurences by yourself or with help of the IDE. This is error prone, as sometimes you don’t want to check each occurence of such a string in the project.
Secondly the ‘$’ functions look very technical and degrade readibility even further. Sure, they look like original Mongo operators, but why should we pollute our code with such ‘low level’ technicals?
Rogue to the rescue
Rogue alleviates all those drawbacks and provides a type-safe DSL. This is nice! Now when you change a field in a class, compilation will fail if you forget to change it in your queries; but of course usually IDE refactorings do the job for you. Nice thing about DSLs is that they provide great readability. Previously quoted DAO can now be changed to the following code:
DAO with Rogue
1
2
3
4
5
class MongoUserDAO extends UserDAO {
def changeLogin(currentLogin: String, newLogin: String) {
UserRecord where (_.login eqs currentLogin) modify (_.login setTo newLogin) and (_.loginLowerCase setTo newLogin.toLowerCase) updateOne()
}
}
There is a little problem with Rogue though as it uses Lift MongoDB Record which exposes the Active Record pattern. While there are many views on whether Active Record is good or bad, personally I don’t like this pattern. So to satisfy current DAO interfaces an object decoupling domain object from the Lift Record has been introduced. The UserRecord object you can see in the above example is a Lift Record whereas implicits are used to convert between User and UserRecord objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class MongoUserDAO extends UserDAO {
import UserImplicits._
def load(userId: String): Option[User] = {
UserRecord where (_.id eqs new ObjectId(userId)) get()
}
private object UserImplicits {
implicit def fromRecord(user: UserRecord): User = {
User(user.id.get, user.login.get, user.loginLowerCase.get, user.email.get, user.password.get, user.salt.get, user.token.get)
}
implicit def fromOptionalRecord(userOpt: Option[UserRecord]): Option[User] = {
userOpt.map(fromRecord(_))
}
implicit def toRecord(user: User): UserRecord = {
UserRecord.createRecord
.id(user.id)
.login(user.login)
.loginLowerCase(user.loginLowerCased)
.email(user.email)
.password(user.password)
.salt(user.salt)
.token(user.token)
}
}
}
This proved to be working nicely, although some additional code has to be written. Stay tuned for a post describing this pattern in detail.
You can view, clone & fork the whole project at GitHub: https://github.com/softwaremill/bootzooka