Node.js sample application with jQuery Mobile and Express.
Revision | 22c55d62993483364156e5986880ad84823755aa (tree) |
---|---|
Zeit | 2012-10-20 19:14:27 |
Autor | hylom <hylom@hylo...> |
Commiter | hylom |
add some fixes...
@@ -0,0 +1,9 @@ | ||
1 | +MOCHA = node_modules/.bin/mocha -R spec | |
2 | + | |
3 | +test: | |
4 | + $(MOCHA) testing/models | |
5 | + | |
6 | +reset-db: | |
7 | + mongo mobbs tools/sampledata.js | |
8 | + | |
9 | + |
@@ -54,7 +54,8 @@ Database.prototype.close = function() { | ||
54 | 54 | Database.prototype._getCollection = function (collectionName, callback) { |
55 | 55 | this._open('mobbs', function(err, db) { |
56 | 56 | if (err) { |
57 | - return callback(err); | |
57 | + callback(err); | |
58 | + return; | |
58 | 59 | } |
59 | 60 | db.createCollection(collectionName, callback); |
60 | 61 | }); |
@@ -83,11 +84,8 @@ Topics.prototype.getLatest = function (start, end, callback) { | ||
83 | 84 | callback(err); |
84 | 85 | return; |
85 | 86 | } |
86 | - // topicsデータの全データにアクセスするカーソルを作成する | |
87 | 87 | var cursor = collection.find({}); |
88 | - // start、endの値に応じてカーソル位置を移動させる | |
89 | 88 | cursor.sort([['date', -1]]).limit(end - start).skip(start); |
90 | - // カーソルの対象となる値を配列として返す | |
91 | 89 | cursor.toArray(callback); |
92 | 90 | }); |
93 | 91 | }; |
@@ -101,7 +99,6 @@ Topics.prototype.findById = function (topicId, callback) { | ||
101 | 99 | callback(err); |
102 | 100 | return; |
103 | 101 | } |
104 | - // topicIdフィールドが指定したトピックIDであるドキュメントを取得 | |
105 | 102 | collection.findOne({topicId: topicId}, callback); |
106 | 103 | }); |
107 | 104 | }; |
@@ -113,11 +110,12 @@ Topics.prototype.insert = function (topic, callback) { | ||
113 | 110 | var self = this; |
114 | 111 | |
115 | 112 | // countersコレクションからカウンタの値を取得する |
116 | - this._getCollection('counters', function (err, collection) { | |
113 | + self._getCollection('counters', function (err, collection) { | |
117 | 114 | if (err) { |
118 | 115 | callback(err); |
119 | 116 | return; |
120 | 117 | } |
118 | + // counterの値を取得すると同時にインクリメントする | |
121 | 119 | collection.findAndModify({name:'topics'}, {}, {$inc: {count:1}}, createTopic); |
122 | 120 | }); |
123 | 121 |
@@ -150,30 +148,9 @@ Topics.prototype.insert = function (topic, callback) { | ||
150 | 148 | }; |
151 | 149 | |
152 | 150 | /** |
153 | - * 指定したトピックのreplies要素に指定したコメントIDを追加する | |
154 | - */ | |
155 | -Topics.prototype.appendReply = function (topicId, commentId, callback) { | |
156 | - this._getCollection('topics', function (err, collection) { | |
157 | - if (err) { | |
158 | - callback(err); | |
159 | - return; | |
160 | - } | |
161 | - var cursor = collection.findAndModify( | |
162 | - {topicId:topicId}, | |
163 | - {}, | |
164 | - {$push: {replies:commentId}}, | |
165 | - callback | |
166 | - ); | |
167 | - }); | |
168 | -}; | |
169 | - | |
170 | -/** | |
171 | 151 | * コメントデータにアクセスするためのオブジェクト |
172 | 152 | */ |
173 | -var Comments = function () { | |
174 | - this._db = null; | |
175 | - this._client = null; | |
176 | -}; | |
153 | +var Comments = function () {}; | |
177 | 154 | Comments.prototype = new Database(); |
178 | 155 | |
179 | 156 | /** |
@@ -192,41 +169,30 @@ Comments.prototype.findById = function (commentId, callback) { | ||
192 | 169 | callback(err); |
193 | 170 | return; |
194 | 171 | } |
195 | - // commentIdフィールドが指定したコメントIDであるドキュメントを取得 | |
196 | - var cursor = collection.findOne({commentId: commentId}, callback); | |
197 | - }); | |
198 | -}; | |
199 | - | |
200 | -/** | |
201 | - * 指定したコメントのreplies要素に指定したコメントIDを追加する | |
202 | - */ | |
203 | -Comments.prototype.appendReply = function (parentId, childId, callback) { | |
204 | - this._getCollection('comments', function (err, collection) { | |
205 | - if (err) { | |
206 | - callback(err); | |
207 | - return; | |
172 | + // commentId引数が数値の場合、コメントオブジェクトを返す | |
173 | + if (typeof commentId === 'number') { | |
174 | + collection.findOne({commentId: commentId}, callback); | |
175 | + } else { | |
176 | + // commentId引数が配列の場合、取得したコメントを配列で返す | |
177 | + var cursor = collection.find({commentId: {$in: commentId}}); | |
178 | + cursor.sort([['date', -1]]); | |
179 | + cursor.toArray(callback); | |
208 | 180 | } |
209 | - var cursor = collection.findAndModify( | |
210 | - {commentId:parentId}, | |
211 | - {}, | |
212 | - {$push: {replies:childId}}, | |
213 | - callback | |
214 | - ); | |
215 | 181 | }); |
216 | 182 | }; |
217 | 183 | |
218 | 184 | /** |
219 | 185 | * コメントを新規作成してデータベースに挿入する |
220 | 186 | */ |
221 | -Comments.prototype.insert = function (comment, callback) { | |
187 | +Comments.prototype.insert = function (topicId, comment, callback) { | |
222 | 188 | var self = this; |
223 | 189 | // countersコレクションからカウンタの値を取得する |
224 | - this._getCollection('counters', function (err, collection) { | |
190 | + self._getCollection('counters', function (err, collection) { | |
225 | 191 | if (err) { |
226 | 192 | callback(err); |
227 | 193 | return; |
228 | 194 | } |
229 | - collection.findAndModify({name:'comment'}, {}, {$inc: {count:1}}, createComment); | |
195 | + collection.findAndModify({name:'comments'}, {}, {$inc: {count:1}}, createComment); | |
230 | 196 | }); |
231 | 197 | |
232 | 198 | // 取得したカウンタの値をコメントIDとするコメントを作成 |
@@ -234,7 +200,7 @@ Comments.prototype.insert = function (comment, callback) { | ||
234 | 200 | var nextId = counter.count; |
235 | 201 | var newComment = { |
236 | 202 | commentId: nextId, |
237 | - topicId: comment.topicId, | |
203 | + topicId: topicId, | |
238 | 204 | title: comment.title, |
239 | 205 | text: comment.text, |
240 | 206 | date: new Date(), |
@@ -244,31 +210,64 @@ Comments.prototype.insert = function (comment, callback) { | ||
244 | 210 | parentCommentId: comment.parentCommentId || null |
245 | 211 | }; |
246 | 212 | |
247 | - // 親コメントが指定されていない場合、トピックのrepliesにIDを追加 | |
248 | - if (comment.parentCommentId === null) { | |
249 | - var topics = database.getTopics(); | |
250 | - topics.appendReply(comment.topicId, nextId, function (err, topic) { | |
251 | - topics.close(); | |
213 | + // 親コメントが指定されていない場合、トピックのrepliesフィールドに | |
214 | + // コメントIDを追加する | |
215 | + if (newComment.parentCommentId === null) { | |
216 | + appendReplyToTopic(newComment, function (err) { | |
252 | 217 | if (err) { |
253 | 218 | callback(err); |
254 | 219 | return; |
255 | 220 | } |
256 | - insertComment(newComment, callback); | |
257 | - }); | |
221 | + insertComment(newComment); | |
222 | + }); | |
258 | 223 | } else { |
259 | - self.appendReply(comment.parentCommentId, nextId, function (err, parent) { | |
224 | + // 親コメントが指定されている場合は親コメントのrepliesフィールドに | |
225 | + // コメントIDを追加する | |
226 | + appendReplyToComment(newComment, function (err) { | |
260 | 227 | if (err) { |
261 | 228 | callback(err); |
262 | 229 | return; |
263 | 230 | } |
264 | - insertComment(newComment, callback); | |
231 | + insertComment(newComment); | |
265 | 232 | }); |
266 | 233 | } |
267 | 234 | } |
268 | 235 | |
236 | + // 指定したコメントのrepliesフィールドに指定したコメントIDを追加する | |
237 | + function appendReplyToComment(comment, callback) { | |
238 | + var parentId = comment.parentCommentId; | |
239 | + var childId = comment.commentId; | |
240 | + self._getCollection('comments', function (err, collection) { | |
241 | + if (err) { | |
242 | + callback(err); | |
243 | + return; | |
244 | + } | |
245 | + collection.findAndModify( {commentId:parentId}, | |
246 | + {}, | |
247 | + {$push: {replies:childId}}, | |
248 | + callback); | |
249 | + }); | |
250 | + }; | |
251 | + | |
252 | + // 指定したトピックのreplesフィールドに指定したコメントIDを追加する | |
253 | + function appendReplyToTopic(comment, callback) { | |
254 | + var parentId = comment.topicId; | |
255 | + var childId = comment.commentId; | |
256 | + self._getCollection('topics', function (err, collection) { | |
257 | + if (err) { | |
258 | + callback(err); | |
259 | + return; | |
260 | + } | |
261 | + collection.findAndModify( {topicId:parentId}, | |
262 | + {}, | |
263 | + {$push: {replies:childId}}, | |
264 | + callback); | |
265 | + }); | |
266 | + }; | |
267 | + | |
269 | 268 | // コメントをデータベースに挿入する |
270 | - function inserteComment(newComment) { | |
271 | - this._getCollection('comments', function (err, collection) { | |
269 | + function insertComment(newComment) { | |
270 | + self._getCollection('comments', function (err, collection) { | |
272 | 271 | if (err) { |
273 | 272 | callback(err); |
274 | 273 | return; |
@@ -278,6 +277,7 @@ Comments.prototype.insert = function (comment, callback) { | ||
278 | 277 | callback(err); |
279 | 278 | return; |
280 | 279 | } |
280 | + // 挿入したオブジェクトを引数に与えてコールバック関数を実行する | |
281 | 281 | callback(err, obj[0]); |
282 | 282 | }); |
283 | 283 | }); |
@@ -285,12 +285,12 @@ Comments.prototype.insert = function (comment, callback) { | ||
285 | 285 | }; |
286 | 286 | |
287 | 287 | /** |
288 | - * コメントに付けられたコメントをツリー状のオブジェクトで取得する | |
288 | + * トピックに付けられたコメントをツリー状のオブジェクトで取得する | |
289 | 289 | */ |
290 | 290 | Comments.prototype.getCommentTree = function (topicId, callback) { |
291 | 291 | var self = this; |
292 | - var topics = database.getTopics(); | |
293 | 292 | |
293 | + var topics = database.getTopics(); | |
294 | 294 | topics.findById(topicId, function (err, topic) { |
295 | 295 | topics.close(); |
296 | 296 | if (err) { |
@@ -314,7 +314,6 @@ Comments.prototype.getCommentTree = function (topicId, callback) { | ||
314 | 314 | items[comment.commentId] = comment; |
315 | 315 | } else { |
316 | 316 | // ループ終了時の処理 |
317 | - comments.close(); | |
318 | 317 | var tree = buildCommentTree(topic.replies, items, []); |
319 | 318 | callback(null, tree); |
320 | 319 | } |
@@ -0,0 +1,137 @@ | ||
1 | + | |
2 | +var assert = require('assert'); | |
3 | +var database = require('../models/database'); | |
4 | + | |
5 | +describe('database', function () { | |
6 | + | |
7 | + describe('.Topics#getLatest(start, end)', function () { | |
8 | + it('can retrive some topic', function(done) { | |
9 | + var topics = database.getTopics(); | |
10 | + topics.getLatest(1, 2, function (err, doc) { | |
11 | + topics.close(); | |
12 | + assert(!err); | |
13 | + console.log('--------'); | |
14 | + assert(doc.length > 0, 'Number of found items is invalid.'); | |
15 | + for (var i = 0; i < doc.length; i++) { | |
16 | + console.log(doc[i]); | |
17 | + } | |
18 | + done(); | |
19 | + return; | |
20 | + }); | |
21 | + }); | |
22 | + }); | |
23 | + | |
24 | + describe('.Topics#findById(topicId)', function () { | |
25 | + it('can retrive one topic', function(done) { | |
26 | + var topics = database.getTopics(); | |
27 | + topics.findById(1, function (err, doc) { | |
28 | + topics.close(); | |
29 | + assert(!err); | |
30 | + assert(doc); | |
31 | + console.log('--------'); | |
32 | + console.log(doc); | |
33 | + done(); | |
34 | + return; | |
35 | + }); | |
36 | + }); | |
37 | + }); | |
38 | + | |
39 | + describe('.Topics#insert(topic, callback)', function () { | |
40 | + it('insert new topic', function(done) { | |
41 | + var topics = database.getTopics(); | |
42 | + var topic = { | |
43 | + title: 'テストトピック', | |
44 | + text: 'テスト用本文' | |
45 | + }; | |
46 | + topics.insert(topic, function (err, topicId) { | |
47 | + topics.close(); | |
48 | + assert(!err); | |
49 | + assert(topicId !== undefined); | |
50 | + done(); | |
51 | + }); | |
52 | + }); | |
53 | + }); | |
54 | + | |
55 | + describe('.Comments#findById(commentId)', function () { | |
56 | + it('can retrive one comments', function(done) { | |
57 | + var comments = database.getComments(); | |
58 | + comments.findById(1, function (err, doc) { | |
59 | + comments.close(); | |
60 | + assert(!err); | |
61 | + console.log('--------'); | |
62 | + console.log(doc); | |
63 | + done(); | |
64 | + return; | |
65 | + }); | |
66 | + }); | |
67 | + }); | |
68 | + | |
69 | + describe('.Comments#findById(commentId)', function () { | |
70 | + it('can retrive some comments', function(done) { | |
71 | + var comments = database.getComments(); | |
72 | + comments.findById([1, 2], function (err, docs) { | |
73 | + comments.close(); | |
74 | + assert(!err); | |
75 | + assert(docs.length == 2, 'length is ' + docs.length); | |
76 | + console.log('--------'); | |
77 | + console.log(docs[0]); | |
78 | + console.log(docs[1]); | |
79 | + done(); | |
80 | + return; | |
81 | + }); | |
82 | + }); | |
83 | + }); | |
84 | + | |
85 | + describe('.Comments#insert(topicId, comment, callback)', function () { | |
86 | + it('can insert comment', function(done) { | |
87 | + var comments = database.getComments(); | |
88 | + var comment = { | |
89 | + title: 'テストコメント', | |
90 | + text: 'テスト用コメント本文', | |
91 | + }; | |
92 | + comments.insert(1, comment, function (err, comment) { | |
93 | + comments.close(); | |
94 | + assert(!err); | |
95 | + assert(comment.commentId !== undefined); | |
96 | + done(); | |
97 | + }); | |
98 | + }); | |
99 | + }); | |
100 | + | |
101 | + describe('.Comments#insert(topicId, comment, callback)', function () { | |
102 | + it('can insert child comment', function(done) { | |
103 | + var comments = database.getComments(); | |
104 | + var comment = { | |
105 | + title: 'テストコメント', | |
106 | + text: 'テスト用コメント本文', | |
107 | + parentCommentId: 2, | |
108 | + }; | |
109 | + comments.insert(1, comment, function (err, comment) { | |
110 | + comments.close(); | |
111 | + assert(!err); | |
112 | + assert(comment.commentId !== undefined); | |
113 | + assert(comment.parentCommentId === 2); | |
114 | + done(); | |
115 | + }); | |
116 | + }); | |
117 | + }); | |
118 | + | |
119 | + describe('.Comments#getCommentTree(topicId)', function () { | |
120 | + it('can retrive some comment', function(done) { | |
121 | + var count = 0; | |
122 | + var comments = database.getComments(); | |
123 | + comments.getCommentTree(1, function (err, doc) { | |
124 | + comments.close(); | |
125 | + assert(!err); | |
126 | + assert(doc); | |
127 | + console.log(doc); | |
128 | + done(); | |
129 | + return; | |
130 | + }); | |
131 | + }); | |
132 | + }); | |
133 | + | |
134 | +}); | |
135 | + | |
136 | + | |
137 | + |
@@ -21,48 +21,3 @@ block content | ||
21 | 21 | h1 |
22 | 22 | a(href='#about') mobbs ver.0.0.1 |
23 | 23 | |
24 | - // aboutページ | |
25 | - #about(data-role='page') | |
26 | - div(data-role='header') | |
27 | - h1 mobbs test BBS | |
28 | - // a要素にdata-rel='back'属性を付けると、要素をクリックしたときに | |
29 | - // 「戻る」動作が行われる | |
30 | - a(href='#', data-rel='back') 戻る | |
31 | - div(data-role='content') | |
32 | - h2 mobbsについて | |
33 | - p mobbsはNode.jsとjQuery Mobileで実装されたスマートフォン/タブレット向け掲示板アプリケーションです。 | |
34 | - div(data-role='footer') | |
35 | - h1 | |
36 | - | mobbs ver.0.0.1 | |
37 | - | |
38 | - // トピック一覧ページ | |
39 | - #topics-list(data-role='page') | |
40 | - div(data-role='header') | |
41 | - h1 トピック一覧 | |
42 | - a.ui-btn-right(href='#dialog-create-topic', data-role='button') 新規作成 | |
43 | - div(data-role='content') | |
44 | - // data-role='listview'を指定すると、リストをリストビュー形式で | |
45 | - // 表示できる | |
46 | - ul#topics-listview(data-role='listview') | |
47 | - div(data-role='footer') | |
48 | - h1 | |
49 | - a(href='#about') mobbs ver.0.0.1 | |
50 | - | |
51 | - #dialog-create-topic(data-role='dialog') | |
52 | - div(data-role='header') | |
53 | - h2 トピックの新規作成 | |
54 | - div(data-role='content') | |
55 | - form(action='/topic/create', method='post', data-ajax='false') | |
56 | - label(for='topic-post-name') お名前: | |
57 | - input(type='text', id='topic-post-name', name='name') | |
58 | - label(for='topic-post-title') タイトル(必須): | |
59 | - input(type='text', id='topic-post-title', name='title') | |
60 | - label(for='topic-post-related') URL: | |
61 | - input(type='url', id='topic-post-related', name='relatedUrl') | |
62 | - label(for='topic-post-text') 本文(必須): | |
63 | - textarea(id='topic-post-text', name='text') | |
64 | - // data-rel='back'を指定すると、クリック時に前ページに戻る | |
65 | - a(href='#', data-rel='back', type='button') トピック一覧に戻る | |
66 | - input(type='submit', id='topic-post-submit', value='トピック作成', disabled='disabled') | |
67 | - | |
68 | - |