지난 글에서 수정 할 것이라 언급했던 내용을 분류하여 간단히 정리하면 다음과 같다.

1. 마이그레이션
 History 모델 변경 - Detail_history 모델 생성
 회원 성별, 나이
 DB 제약조건

2. 커피스크립트
 자바스크립트 코드 버그
 포인트 사용 폼
 회원 검색

3. 레이아웃
 공통 레이아웃

  가장 먼저 마이그레이션 수정을 진행하였다. 수정 전 ERD는 다음과 같다.

 먼저 History 모델을 세부적으로 바꾸기 위해 다음과 같은 작업을 하였다.

 - 필드명 변경

    price → total_price

    point → point_used

    is_cash → is_credit

- 필드 추가

    point_accumulated :integer

- 필드 삭제

    note :text

    content_id :integer

- 하위 테이블 생성

    detail_histories history:references content:references price:integer note:text


  위의 작업을 db에서 직접 ddl을 통해 변경할 수 있지만 rails의 마이그레이션을 통해 루비 코드 형태로 정의된 데이터베이스 스키마를 만들 수 있다.

  먼저 마이그레이션 파일을 만든다.

$ rails g migration AddDetailsToHistory

/db/migrate 폴더에 새로운 마이그레이션 파일이 생성된다.


db/migrate/20161216164045_add_details_to_history.rb

class AddDetailsToHistory < ActiveRecord::Migration[5.0]
  def change
    add_column :histories, :point_used, :integer
    rename_column :histories, :point, :point_accumulated
    rename_column :histories, :is_cash, :is_credit
    rename_column :histories, :price, :total_price
    remove_column :histories, :note, :text
    remove_column :histories, :content_id, :integer
  end
end

  필드 추가는 add_column 메소드를, 필드명 변경은 rename_column, 필드 삭제는 remove_column 메소드를 사용한다.


  다음으로 detail_history 모델을 생성하기 위해 아래의 명령어를 입력한다.

$ rails g model detail_history history:references content:references price:integer note:text

  모델을 생성하면 데이터베이스에 해당 모델 형식을 갖는 테이블을 생성하기 위한 마이그레이션 파일이 /db/migrate에 자동으로 생성된다.


  지난 시간에 association관계 설정을 위해 모델 클래스에 belongs_to, has_many 등의 관계를 설정해 두었다. 아래와 같이 수정하였다.


app/models/content.rb

class Content < ApplicationRecord
  belongs_to :category
  has_many :detail_histories

  # 생략
end

app/models/history.rb

class History < ApplicationRecord
  belongs_to :member
  has_many :details_histories
end

app/models/detail_history.rb

class DetailHistory < ApplicationRecord
  belongs_to :history
  belongs_to :content
end

같은 방법으로 Member 모델에 성별과 연령대를 추가한다.

$ rails g migration AddGenderAndAgesToMembers gender:string ages:string

마이그레이션명 뒤에 직접 추가할 필드명과 타입을 적어 자동으로 마이그레이션 파일에 추가시킨다.


이제 migrate를 하고 적용이 되었는지 확인해보자.
$ rake db:migrate


rails erd 플러그인을 이용하여 erd를 확인해 본다.

플러그인: http://rails-erd.rubyforge.org

$ rake erd


  현재 테이블에는 not null이나 default 제약조건이 전혀 설정되어있지 않다. 모든 테이블에 필요한 제약조건을 추가하였다. member테이블의 예는 다음과 같다.

$ rails g migration AlterConstraintToMembers


db/migrate/20161216182645_alter_constraint_to_members.rb

class AlterConstraintToMembers < ActiveRecord::Migration[5.0]
  def change
    change_column_null :members, :name, false
    change_column_default :members, :ages, 'unknown'
    change_column_null :members, :gender, false
    change_column_default :members, :acc_point, 0
    change_column_default :members, :use_point, 0
    change_column_default :members, :rem_point, 0    
  end
end

  포인트의 기본값을 0으로 설정했으므로 앞선 개발과정에서 컨트롤러에서 기본값을 추가 했던 코드는 삭제해도 좋을 것이다.


  마지막으로 외래키로 연결된 데이터가 삭제 될 때 CASCADE 형태로 삭제 될 것인지, NOT NULL 형태로 유지할 것인지 조건을 설정하려 했다. 그러나 rails는 references 헬퍼로 외래키를 연결하는 듯 하지만 실제로는 외래키 제약조건을 설정하지 않는다. dbconsole을 열어서 contents 테이블을 확인해 보면 다음과 같다.

$ rails dbconsole

> .schema contents


 어디에도 외래키 제약조건은 없고, category_id와 관련된 인덱스가 생성되는 코드가 있음을 알 수 있다. 따라서 DDL로 CASCADE나 NOT NULL 제약조건을 추가 할 수 없는 듯 하다. 만약 이렇게 하고 싶다면 외래키 제약조건을 직접 걸어야 한다.



참고: http://rubykr.github.io/rails_guides/migrations.html


  references 헬퍼를 없애고 외래키 제약조건을 설정하기 보다 컨트롤러를 통해 유사한 기능을 추가할 것이다.


  위의 제약조건대로 설정하고 rails db:migrate를 하면 에러가 발생한다. 기존의 데이터들이 gender를 가지고 있지 않은데 not null로 설정되어 있기에 에러가 발생하는 듯 하다. db를 초기화 하고 다시 마이그레이션 한다.

$ rake db:reset

$ rake db:migrate


  이제 수정된 모델 형태로 뷰와 컨트롤러를 수정하자. members에는 gender와 ages가 추가된 필드를 모두 추가해 주고, history에도 변경된 필드를 추가해 주었다. 그리고 회원의 시술이력에서 큰 History객체와 여러개의 Detail_history 객체를 동시에 생성하는 코드를 작성하였다. fields_for 폼 헬퍼를 이용하여 부모와 자식 객체를 동시에 생성 할 수 있었다.

참고: https://withrails.com/2016/01/09/1187/


app/models/history.rb

class History < ApplicationRecord
  belongs_to :member
  has_many :detail_histories
  accepts_nested_attributes_for :detail_histories, reject_if: :all_blank
end

  하나의 history객체 밑에 여러 개의 detail_history객체를 가지므로 1:n 관계가 형성된다. has_many 관계를 설정하고 nested attributes로 detail_history의 필드를 가져온다. 모든 필드가 비어있으면 자식 객체를 생성하지 않도록 reject_if: :all_blank를 추가한다.


app/models/detail_history.rb

class DetailHistory < ApplicationRecord
  belongs_to :history, optional: true
  belongs_to :content
end

  현재 개발하는 rails의 버전이 5버전이므로 optional: true를 꼭 설정해 주어야 한다. 그렇지 않으면 아래 사진처럼 뷰에서 컨트롤러로 파라미터는 정확히 전달되나 rollback transaction을 수행하면서 부모와 자식객체를 동시에 생성하여 저장하지 않는다. 

app/controllers/histories_controller.rb

class HistoriesController < ApplicationController
# 생략

  private
    # 생략

    # Never trust parameters from the scary internet, only allow the white list through.
    def history_params
      params.require(:history).permit(:member_id, :date, :total_price, :point_used, :point_accumulated, :is_credit,
        detail_histories_attributes: [:content_id, :price, :note])  # fields_for 메서드를 사용하기 위해 nested attribute 추가
    end
end

  위 모델에서 nested_attributes를 선언한 것 처럼 컨트롤러에서도 뷰에서 전달되는 파라미터를 history_params 메서드를 통해 접근할 수 있도록 detail_histories_attributes를 추가해준다.


app/controllers/members_controller.rb

class MembersController < ApplicationController
  # 생략

  # GET /members/1
  # GET /members/1.json
  def show
    # 생략

    @history = History.new
    2.times do
      @history.detail_histories.build
    end

    # 생략
  end

  # 생략
end
  현재 애플리케이션에서는 history 생성폼이 members/show에 있기 때문에 해당 메서드에서 새로운 @history 액티브레코드를 생성해준다. @history.detail_histories.build 메서드를 통해 새로운 객체를 생성한다. 현재는 두개의 자식 객체를 저장할 수 있도록 설정되어있다.

app/views/members/show.html.erb
<!-- 생략 -->

<h2>시술 기록</h2>
<%= form_for @history do |f| %>
  <!-- 생략 -->

  <table>
    <thead>
      <tr>
        <th>시술종류</th>
        <th>시술별 가격</th>
        <th>비고</th>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <%= f.fields_for :detail_histories do |df| %>
      <tr>
        <td><%= df.grouped_collection_select :content_id, @categories, :contents, :name, :id, :name %></td>
        <td><%= df.number_field :price %></td>
        <td><%= df.text_area :note %></td>
        <td><!-- 추가 버튼 --></td>
      </tr>
      <% end %>
    </tbody>
  </table>

  <!-- 생략 -->
<% end %>

<!-- 생략 -->

  history 객체 생성을 위한 form_for 헬퍼 안에 fields_for 헬퍼를 통해 하위 객체를 함께 생성 할 수 있다. 컨트롤러에서 두개의 객체를 생성했으므로 여기서도 두개의 하위 객체에 대한 필드가 생성된다.



  위와 같이 동시에 저장 되는걸 확인 할 수 있다.



* Scaffold를 원하는 기능에 맞춰 수정하다 보니 특별한 중단점을 찾을 수 없어서 어느정도 기능을 만들어 놓고 리뷰 해본다.


메인 페이지 home/index.html

회원 관리 members/index.html

회원 보기 members/show.html

디자이너 관리 designers/index.html

시술목록 관리 contents/index.html

  Scaffold를 다섯개 만들긴 했으나 하나의 페이지로 기능을 합치거나 모델만 사용해서 세개의 주요 페이지만 남았다.

  앞서 요구사항을 적용하기 위해는 미리 회원정보와 디자이너 정보, 시술 목록이 저장되어 있어야 한다. Scaffold로 생성된 기본적인 CRUD에서 필요한 부분만 수정을 가했다.


app/views/member/_form.html.erb

  <!-- 생략 -->

  <div class="field">
    <%= f.label "전화번호" %>
    <%= f.telephone_field :phone, size: 20, maxlength: 11 %>
  </div>

  <div class="field">
    <%= f.label "담당 디자이너" %>
    <%= f.collection_select :designer_id, designers, :id, :name %>
  </div>

  <!-- 생략 -->


 _form.html.erb 파일은 Create와 Edit을 위한 공통 폼이다. 하지만 이 앱에서는 포인트입력을 받지 않는다. 초기 값을 입력거나 수정할 수 없다. 다만 컨트롤러에서 0으로 초기값을 설정해 주고, 회원의 이력에 따라 포인트가 자동으로 누적되게 할 것이다.

  html5 필드에 맞게 전화번호는 telephone_field로 설정하고, 휴대전화 번호는 최대 11자리 이므로 maxlength를 11로 설정한다.

  담당 디자이너는 직접 입력하지 않고 select폼으로 입력 받을 수 있게 디자이너 이름을 designers 테이블에서 가져올 수 있도록 collection_select 메서드를 사용하였다.


app/controllers/members_controller.rb

class MembersController < ApplicationController
  # 생략

  # POST /members
  # POST /members.json
  def create
    @member = Member.new(member_params)
    @member.acc_point = 0    # 이하 포인트 초기화
    @member.use_point = 0
    @member.rem_point = 0

    respond_to do |format|
      if @member.save
        format.html { redirect_to @member, notice: '회원이 추가되었습니다.' }
        format.json { render :show, status: :created, location: @member }
      else
        @designers = Designer.all
        format.html { render :new }
        format.json { render json: @member.errors, status: :unprocessable_entity }
      end
    end
  end
  
  # 생략
end

  members/index.html.erb와 show.html.erb에서는 designer 이름을 가져오는 것이 아니라 객체를 그대로 출력하므로 아래와 같이 액티브레코드의 주소를 표시한다. 이름을 출력하기 위해 컨트롤러에서 designers 테이블에서 이름을 조인한 레코드를 가져오게 하였다.

app/controllers/members_controller.rb

  # 생략

  # GET /members
  # GET /members.json
  def index
    @members = Member.joins(:designer).order('members.name').select('members.*, designers.name as designer_name')
  end

  # GET /members/1
  # GET /members/1.json
  def show
    @member = Member.joins(:designer).select('members.*, designers.name as designer_name').find(params[:id])

  # 생략

  디자이너 이름을 designer_name으로 접근할 수 있다.


app/view/members/index.html.erb

  <!-- 생략 -->

  <tbody>
    <% @members.each do |member| %>
      <tr>
        <td><%= link_to member.name, member %></td>
        <td><%= member.phone.insert(3, '-').insert(-5, '-') %></td>
        <td><%= member.designer_name %></td>
        <td><%= number_with_delimiter(member.acc_point) %></td>
        <td><%= number_with_delimiter(member.use_point) %></td>
        <td><%= number_with_delimiter(member.rem_point) %></td>
        <td><%= member.note %></td>
      </tr>
    <% end %>
  </tbody>

 <!-- 생략 -->

  디자이너 이름을 member_name으로 접근한다.

  그 외에 전화번호 형식을 표시하기 위해 문자열 처리를 하였다. 또한 포인트는 1,000 단위에 콤마를 넣기 위해 number_with_delimiter 메소드를 사용하였다.

  아래와 같이 표시된다.



  시술정보에는 시술의 큰 범주에 속하는 category 모델과 세부 범주의 content 모델이 동시에 표시된다. 카테고리와 상세정보 생성은 위와 비슷하다. category와 content를 동시에 표시하기 위해 contents_controller에서 두개의 객체를 동시에 가져왔다.


app/controllers/contents_controller.rb

  # 생략

  # GET /contents
  # GET /contents.json
  def index
    @contents = Content.all
    @categories = Category.all
  end

  # 생략

app/views/contents/index.html.erb

  <!-- 생략 -->

  <tbody>
    <% @categories.each do |category| %>
      <tr>
        <td><%= link_to category.name, category %></td>
      </tr>

      <% contents = @contents.select{|c| c.category_id == category.id} %>
      <% if contents.empty? %>
        <tr>
          <td></td>
          <td colspan="3">[ 비 어 있 음 ]</td>
        </tr>
      <% else %>
        <% contents.each do |content| %>
        <tr>
          <td></td>
          <td><%= link_to content.name, content %></td>
          <td><%= number_to_currency(content.price, unit: '원', precision: 0, format: '%n%u') %></td>
          <td><%= content.note %></td>
        </tr>
        <% end %>
      <% end %>
    <% end %>
  </tbody>

  <!-- 생략 -->

  카테고리를 탐색해서 같은 category_id 를 가진  content 인스턴스가 있으면 출력하고 아니면 [ 비 어 있 음 ] 이라는 문자를 표시한다.

  가격을 통화단위를 포함해서 출력하기 위해 number_to_currency 메서드를 활용하였다.

  이제 시술기록이다. Scaffold로는 History 컨트롤러가 관리하는 뷰에서 생성해야 하지만 회원 정보에서 모든 이력을 표시하고 생성하기 위해 member/show.html.erb 안에서 이력을 생성하고 출력한다. 


app/views/members/show.html.erb

<!-- 생략 -->

<h2>시술 기록</h2>
<%= form_for(@history) do |f| %>
  <% if @history.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@history.errors.count, "error") %> prohibited this history from being saved:</h2>

      <ul>
      <% @history.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <%= f.hidden_field :member_id, value: @member.id %>
  <%= f.hidden_field :date, value: Time.now %>

  <div class="field">
    <%= f.label '시술종류' %>
    <%= f.grouped_collection_select :content_id, @categories, :contents, :name, :id, :name %>
  </div>

  <div class="field">
    <%= f.label '가격' %>
    <%= f.number_field :price %>
  </div>

  <div class="field">
    <%= f.label '포인트' %>
    <%= f.number_field :point, readonly: true %>
  </div>

  <div class="field">
    <%= f.label '비고' %>
    <%= f.text_area :note %>
  </div>

  <div class="field">
    <%= f.label '현금결제' %>
    <%= f.check_box :is_cash %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

<table>
  <thead>
    <tr>
      <th>일자</th>
      <th>시술</th>
      <th>가격</th>
      <th>포인트</th>
      <th>비고</th>
    </tr>
  </thead>
  <tbody>
    <% @histories.each do |history| %>
      <tr>
        <td><%= history.date.strftime('%Y년 %m월 %d일') %></td>
        <td><%= history.category_name + ' - ' + history.content_name %></td>
        <td><%= number_with_delimiter(history.price) %></td>
        <td><%= number_with_delimiter(history.point) %></td>
        <td><%= number_with_delimiter(history.note) %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<!-- 생략 -->

  아래와 같은 그룹화 된 select 메서드를 사용하기 위해 grouped_collection_select 메서드를 사용하였다. 이 메서드를 사용하기 위해서는 모델간 association 관계를 정의해야한다.


app/models/category.rb

class Category < ApplicationRecord
  has_many :contents

  # 생략
end

  Category와 Content는 1:n 관계를 가지므로 'Category has many contents.'라고 말할 수 있을 것이다. 모델에서도 이와 같은 영문법처럼 1:n 관계를 정의해준다.


app/models/content.rb

class Content < ApplicationRecord
  belongs_to :category
  has_many :history

  # 생략
end

  마찬가지로 Content는 하나의 카테고리만 가지므로 'Content belongs to category.'라 볼 수 있다. 또한 Content 하나에 여러개의 History가 있을 수 있다.


app/models/history.rb

class History < ApplicationRecord
  belongs_to :member
  belongs_to :content
end

  History는 각각 Member와 Content에 n:1 관계를 가지므로 두 곳에 모두 belongs_to 설정되어있다. 

  기본적으로 Model 생성시 필드가 references 속성을 가지면 관련된 모델에 자동으로 belongs_to 설정이 된다. 다만 위와 같이 has_many나 has_one등은 직접 입력해 주여야 하는듯 하다.

  이제 아래와 같이 그룹이 생성된 select 메소드를 볼 수 있다.


  시술 기록 생성을 위한 폼은 _form.html.erb의 내용을 사용하였으나 일부 변경해서 사용하기 위해 render하지 않고 코드를 긁어서 붙여넣었다. 생성시간과 member_id는 히든 폼으로 컨트롤러에 넘겨준다.


  시술 종류를 선택하면 가격을 아래에 표시해줘야 하는데 이 과정은 자바스크립트를 통해 넣어줘야 한다. 또한 가격의 10%는 포인트로 누적되며 현금결제에 체크 되어있을 때만 포인트가 누적되게 하는 것도 스크립트로 작성하였다. erb안의 루비 코드는 서버 html 코드를 생성해서 클라이언트에게 제공하므로 클라이언트에서 능동적인 변화를 만들어 내지 않는다. 자바스크립트를 간단히 구현하기 위해 커피스크립트를 사용하였다.


app/javascripts/members.coffee

$(document).ready ->
  $('#history_content_id').change ->
    content_id = $(this).val()

    $.ajax
      url: '/contents/'+content_id
      type: 'GET'
      dataType: 'json'
      success: (result) ->
        $('#history_price').val(result['price'])
        if $('#history_is_cash').is(":checked")
          $('#history_point').val($('#history_price').val()/10)
        else
          $('#history_point').val(0)

        return
    return
  .change()

  $('#history_is_cash').click ->
    if $('#history_is_cash').is(":checked")
      $('#history_point').val($('#history_price').val()/10)
    else
      $('#history_point').val(0)
    return
  return

  jQuery로 select폼에 접근하여 내용이 변할 때 마다 ajax로 해당 시술의 가격을 받아온다. 체크박스가 선택되어있는지 여부를 확인하여 포인트 입력폼에 값을 추가시킨다.


  History가 저장될 때마다 회원의 포인트 정보가 업데이트 되어야 한다. History저장은 histories_controller에서 일어나므로 해당 코드에 업데이트 구문까지 추가한다.


app/controllers/histories_controllers.rb

  # 생략

  # POST /histories
  # POST /histories.json
  def create
    @history = History.new(history_params)
    member = Member.find(@history.member_id)

    respond_to do |format|
      if @history.save and member.update(acc_point: member.acc_point + @history.point, rem_point: member.rem_point + @history.point)
        format.html { redirect_to Member.find(@history.member_id), notice: '기록이 저장되었습니다.' }
        format.json { render :show, status: :created, location: @history }
      else
        format.html { render :new }
        format.json { render json: @history.errors, status: :unprocessable_entity }
      end
    end
  end

  # 생략

  history에 있는 member_id로 member객체를 찾아서 point를 업데이트 시켜주는 코드이다. 물론 포인트 사용시 차감되는 코드도 추가될 것이다.

  

  기능을 구현하기 위한 주요 코드는 위와 같다.

  이 외에도 유효성 검사를 위한 validate 코드를 모델에 추가하였다.


  아직 완성된 상황은 아니지만 피드백을 받기 위해 실제 이 애플리케이션을 사용할 사용자인 동생에게 중간결과를 보여줬다. 여러가지 문제점을 발견 할 수 있었다. 특히 실제 사용시 고객들이 이발만 하고 돌아가는게 아니라 같이 염색을 하는 경우가 있는데, 위 애플리케이션은 history가 하나가 아닌 두개로 저장 되기 때문에 방문 횟수를 카운트 하거나 포인트 누적에서 오류를 발생시킬 수 있다는 문제가 있었다. 또한 회원 정보에 나이와 성별 필드를 추가하는 문제, 현금결제 체크가 아니라 카드체크 버튼으로 바꿔달라는 요구를 받았다.

  이 외에도 포인트를 통한 결제금액 차감 기능 구현을 위한 모델 수정이 필요하며 자바스크립트 코드가 제대로 적용되지 않는 문제가 있으며, 포인트 관리를 위한 정규화 문제, 공통 레이아웃을 설정하는 문제등을 해결해야 할 것으로 보인다. 또한 데이터 삭제시 관련 데이터들을 삭제 시키거나 유지 시킬지 결정할 제약조건을 설정해야 하는 문제가 있다. rails의 마이그레이션은 db에 독립적으로 동작하나, db마다 상이한 제약조건은 설정하지 않으므로 직접 설정해줘야 한다.

  다음에는 이 부분을 수정하도록 하겠다.


GitHub: https://github.com/Stardust-kr/charmbitHair

  2년 차 백수인 덕에 책을 꽤 많이 읽을 수 있었다. 작년에는 어영부영 취업 준비하다 책 몇 권 읽지 못했다. 올해 상반기에는 취업 활동대신 국비 교육과정에 들어갔었기 때문에 남는 시간에는 책을 읽을 수 있었다. 특히 학원이 꽤 먼 곳에 떨어져 있었기 때문에 통학 시간동안 책을 오래 읽을 수 있었다. 후반기에는 취업활동을 하긴 했으나 계속되는 탈락 소식에 자괴감에 빠졌다. 그리고 책으로만 마음을 다잡을 수 있었다. 그 좋아하는 게임도 손에 잘 안 잡히고 거실에 누워 멍하니 있을 때마다 유일하게 하고 싶은 일은 독서밖에 없었다. 그 정도로 올해는 책을 꽤 많이 읽은 듯하다. 물론 시간이 아주 많이 여유로웠던 것 치고는 많이 못 읽었다고 해도 할 말이 없지만, 그냥 평소보다 많이 읽었다고 해두자.

 

  올해 읽은 책들은 아래와 같다.


<어떻게 살 것인가> 유시민 | 생각의길

<유시민의 글쓰기특강> 유시민 | 생각의길

<대통령의 말하기> 윤태영 | 위즈덤하우스

<대통령의 글쓰기> 강원국 | 메디치미디어

<라오스에 대체 뭐가 있는데요?> 무라카미 하루키 | 이영미 옮김 | 문학동네

<코끼리 공장의 해피엔드> 무라카미 하루키 | 김난주 옮김 | 안자이 미즈마루 그림 | 문학동네

<발렌타인데이의 무말랭이> 무라카미 하루키 | 김난주 옮김 | 안자이 미즈마루 그림 | 문학동네

<세일러복을 입은 연필> 무라카미 하루키 | 김난주 옮김 | 안자이 미즈마루 그림 | 문학동네

<쿨하고 와일드한 백일몽> 무라카미 하루키 | 김난주 옮김 | 안자이 미즈마루 그림 | 문학동네

<해뜨는 나라의 공장> 무라카미 하루키 | 김난주 옮김 | 안자이 미즈마루 그림 | 문학동네

     (이상 5권은 문학동네에서 출간한 무라카미 하루키 에세이 걸작선 세트)

<취미는 전시회 관람> 한정희 | 중앙북스

<미학 오디세이 세트> 진중권 | 휴머니스트

<진중권의 서양미술사 세트> 진중권 | 휴머니스트

<CODE: 하드웨어와 소프트웨어에 숨어 있는 언어> 찰스 펫졸드 | 김현규 옮김 | 인사이트

<군주론> 니콜로 마키아벨리 | 강정인, 김경희 옮김 | 까치

<코스모스> 칼 세이건 | 홍승수 옮김 | 사이언스북스

<순수의 시대> 이디스 워튼 | 고정아 옮김 | 열린책들

<나는 고양이로소이다> 나쓰메 소세키 | 김난주 옮김 | 열린책들

<장미의 이름> 움베르토 에코 | 이윤기 옮김 | 열린책들


  구매하지 않고 서점에서 읽은 책들

<후와후와> 무라카미 하루키 | 권남희 옮김 | 안자이 미즈마루 그림 | 비채

<무라카미 하루키의 위스키 성지여행> 무라카미 하루키 | 이윤정 옮김 | 문학사상사


  총 26권을 읽었다. 현재 읽고 있는 소설을 포함하면 12월이 끝나기 전에 한, 두 권은 더 읽을 수 있지 않을까 싶다. 다음 글에는 ‘글쓰기, 하루키 에세이, 고전과 인문 교양, 소설’을 분류로 간단한 이야기를 해볼까 한다.

+ Recent posts