본문 바로가기

Back-end/Mongodb

mongodb mongoose part1

일반적으로 noSQL 기반의 몽고를 사용하게 되면  nodejs용 ODM (Object Data Mapping) 프레임워크를 사용하게 된다.


스키마 정의


const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const PostSchema = new Schema({
title:String
});

module.exports = PostSchema;
가장 일반적인 mongoose 예제이다.

mongoose를 import 한 후 schema 메서드를 이용하여 정의하여준다.

그리고 title을 String으로 정의한다.


const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const CommentSchema = new Schema({
content: String,
user: { type: Schema.Types.ObjectId, ref: 'user' }
});

const Comment = mongoose.model('comment', CommentSchema);

module.exports = Comment;
다른 스키마를 참조하였다.
content의 경우  String 타입이지만 User의 경우 user 스키마에서 데이터를 가져도록 정의한다.
Join의 개념과 비슷하며 실제 사용 예시는 아래쪽에 자세히 다루도록 하겠다.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = require('./post');

const UserSchema = new Schema({
name: {
type: String,
validate: {
validator: (name) => name.length > 2,
message: 'Name must be longer than 2 characters.'
},
required: [true, 'Name is required.']
},
posts: [PostSchema],
likes: Number,
blogPosts: [{
type: Schema.Types.ObjectId,
ref: 'blogPost'
}]
});

UserSchema.virtual('postCount').get(function() {
return this.posts.length;
});

const User = mongoose.model('user', UserSchema);

module.exports = User;

위의 예제를 보자


위에서는 이름을 String 형식으로 그리고 validator를 통하 검증을 넣어주었다.
두번째로 [PostSchema]의 경우 다른 schema 내용을 가져와 이용할 수 있다.


Virtual의 경우 실제로 존재하지는 않는 custom 내용이라 할 수 있다.

get의 경우 어떻게 데이터를 return 하여줄지에 대한 내용이고 setter의 경우 해당 데이터를 넣음으로써
내용을 재정의 하여줄 수 있다.

virtual에서 this는 나 자신의 스키마다


ex)
personSchema.virtual('name.full').get(function () {
  return this.name.full;
}).set(function(name) {
  var split = name.split(' ');
  this.name.first = split[0];
  this.name.last = split[1];
});



CRUD

Create
const joe = new User({name: 'Joe'});

joe.save()
.then(() => {
// Has joe been saved successfully?
assert(!joe.isNew);
done();
});
인스턴스를 만든 후 인스턴스의 save를 통해 저장한다. 이때 isNew를 통하여 데이터가 정상적으로 들어갔는지 검증


Update


인스턴스 메서드

joe.set('name', 'Alex');

set을 통하여 쉽게 바꿀 수 있다.
joe.update({ name: 'Alex' })


클래스 메서드
User.update({ name: 'Joe' }, { name: 'Alex' }),

Joe를 찾아 Alex로 업데이트
User.findByIdAndUpdate(joe._id, { name: 'Alex' }),

User.findOneAndUpdate({ name: 'Joe' }, { name: 'Alex' }),


findOne의 경우 검색되는 첫번째 쿼리만 반영


Delete
joe.remove()
인스턴스 메서드 제거
User.remove({ name: 'Joe' })

Joe인것을 제거
User.findOneAndRemove({ name: 'Joe' })


joe인것 하나만 제거



Nesting 스키마

const joe = new User({
name: 'Joe',
posts: [{title: 'PostTitle'}]
//자동으로 post schema에 데이터를 처리함
});


이렇게 사용할경우 자동으로 Post 스키마에서 title이 postTitle인 것을 생성한 후 Id값을 넣어준다(ref로 참조를 해주어야함)

user.posts.push({title: 'New Post'});


다중선언도 가능

const post = user.posts[0];
post.remove();
return user.save();
post를 검색 한 후 삭제 마지막으로 저장하면 
assert(user.posts.length === 0);

가 True가 된다.

Virtual Type
const joe = new User({
name: 'Joe',
posts: [{title: 'PostTitle'}]
});

// joe.postCount //now undefined

joe.save()
.then(() => User.findOne({name: 'Joe'}))
.then((user) => {
assert(joe.postCount === 1);
done();
})


virtual type의 경우 따로 세팅해 주지 않아도 바로 사용할 수 있다.


Validation Error Test

describe('Validatiing records', ()=>{
it('require a user name', ()=>{

const user = new User({name: undefined});
const validationResult = user.validateSync();
// const message = validationResult.errors.name.message;
const { message } = validationResult.errors.name;

console.log(message);

assert( message == 'Name is required.')

});

it('disallows invalid records from being saved', (done) => {
const user = new User({ name: 'Al' });
user.save()
.catch((validationResult) => {
const { message } = validationResult.errors.name;

assert(message === 'Name must be longer than 2 characters.');
done();
});
});
});

위와 같이 validateSync를 통해 에러 여부를 확인할 수 있다.



Populate


describe('Assocations', () => {
let joe, blogPost, comment;


beforeEach((done) => {
joe = new User({name: 'Joe'});
blogPost = new BlogPost({title: 'JS is Great', content: 'Yep it really is'});
comment = new Comment({content: 'Congrats on great post'});

joe.blogPosts.push(blogPost);
blogPost.comments.push(comment);
comment.user = joe;

Promise.all([joe.save(), blogPost.save(), comment.save()])
.then(() => done());

});

//only 붙이면 이것만 테스트합니다
it('save a relation between a user and a blogpost', (done) => {
User.findOne({name: 'Joe'})
.populate('blogPosts')
.then((user) => {
assert(user.blogPosts[0].title = 'JS is Great');
done();
});
});

it('saves a full relatin graph', (done) => {
User.findOne({name: 'Joe'})
.populate({
//populate 안에 populate
path: 'blogPosts', //가져올거 정해주고 //그 내용안에서 populate 할 걸 정해준다.
populate: {
path: 'comments', //조회할 칼럼(?)
model: 'comment', //기준으로 가져올 데이터
populate:{
path: 'user',
model: 'user'
}
}
}).then((user) => {

assert(user.name === 'Joe');
assert(user.blogPosts[0].title === 'JS is Great');
done();



})
})
});



populate 메서드에서 path를 선언하여 조회할 칼럼을 Join처럼 가져올 수 있다.