Bucket Place/Ruby on Rails

[Ruby on Rails] 데이터베이스 관계설정 및 장바구니 만들기

Cloud Travel 2014. 4. 30. 16:08


  들어가면서... 

 

 데이터베이스를 생성해서 다루는 것까지 알게되었다. 이제는 데이터베이스간의 연결이 필요한 시점이다. 예를들어, 앞서서 만들던 예제에서 장바구니 기능을 추가할 때의 경우이다. 이 경우에는 간단하게 다음과 같은 관계가 성립되게 될 것이다.

 

 

 Has 관계로 묶어서 표현하였지만, 말로 표현하면 다음과 같다.

  - 한 권의 책은 여러개의 카트에 포함된다.

  - 한 개의 카트는 여러권의 책을 가질 수 있다.

 Rails에서 이와 같이 관계표현을 묶어줄때 어떤 방식으로 하는지 알아보자.



  Cart 데이터베이스 생성 


 모든 일에 앞서 Cart 정보를 저장할 데이터베이스를 생성해줘야 한다. 앞에서 다루었던 scaffold 를 이용하면 되는데, Cart의 경우 요구사항에 따라 달라지겠지만, 현재에는 이름조차 없는 하나의 카트이고, 사용자 정보도 없기 때문에 단순히 Cart의 아이디 값만을 저장하는 공간이 된다.


> rails generate sacffold cart

....

> rake db:migrate

....



  장바구니 관리 


 앞서서 말했듯이 아직 User정보가 없기 때문에 세션 정보를 이용해서 카트를 구분해 줄 것이다. 이 경우에는 세션값이 변경되지 않는 이상 한명의 사용자로 인식할 수 있게 된다. 세션 정보를 받는 지점에서만 카트를 생성하거나 가져오면 되기 때문에 Application Controller(Layout 의 컨트롤러)에서 처리하기로 결정했다.


> vi app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  private
    def current_cart
      Cart.find(session[:cart_id])
    rescue ActiveRecord::RecordNotFound
      cart = Cart.create
      session[:cart_id] = cart.id
    end 
end

 소스코드를 설명하자면 다음과 같다.

  - private 함수로 current_cart를 생성하였다.

  - 현재 저장된 세션값에 cart_id 값이 설정되있는지 확인을 한다

  - cart_id 값이 세션에 저장이 되어 있지 않다면, 세로운 카트 정보를 생성하여 세션에 아이디 값을 저장을 한다.


 ## Private 함수로 지정했을 때의 효과 ##

  - 그 함수는 컨트롤러에서만 사용이 가능하게 된다.

  - 컨트롤러에서 메소드를 액션으로 사용하지 못한다.



  관계 표현하기


 이제는 실질적인 책(book)테이블과 카트(cart)테이블을 연결시키는 작업을 하겠다. 일반적으로 데이터베이스 설계에서 관계, 특히 has관계가 있는 경우에는 하나의 테이블이 별도로 존재하게 된다. 이 테이블은 id값과 관계를 맺고있는 테이블의 Key값을 외례키로 가지고 있게 된다. 위의 말을 그림으로 표현하면 다음과 같이 된다.

 (지금은 데이터베이스를 공부하는게 아니기 때문에 삼지창 이라던지 정식적 표현법과는 다르다.)


 그림을 그리면서 관계를 나타내는 테이블의 이름을 select_item으로 만들었다. 이 관계는 다음을 의미한다.

   - select_item은 book 테이블의 id값과 cart 테이블의 id값을 쌍으로 갖는다.

   - cart 테이블은 여러개의 select_item을 가지고 있다.

   - book 테이블은 여러개의 select_item을 갖는다.


 추가 사항(이것은 요구사항에 따라서 달라질 부분이다)

   - 장바구니(select_item list)에 책이 존재한다면 책은 리스트에서 삭제되지 않는다.

   - 카트가 사라지게 되면, 장바구니에 있던 책에 관련된 내용도 데이터베이스에서 사라진다. (foreign key dependency)


 자 우선은 select_item 테이블을 생성해보자.

 > rails generate scaffold select_item book_id:integer cart_id:integer

....

 > rake db:migrate

....


 다음은 각각의 테이블 클레스에서 관계를 나타내줘야 한다. 테이블은 MVC중 M(odel)에 관련되있다.


 1. 일단은 select_item 테이블 이 foreign key로 cart와 book의 id값을 가져온다는 것을 명시해주자.

> vi app/models/select_item.rb


class SelectItem < ActiveRecord::Base
  belongs_to :book
  belongs_to :cart
end

 belongs_to는 가지고 있는 값이 각각의 어떤 테이블에서 가져오는지를 명시해준다. 현재는 book과 cart테이블을 연결해주므로 양쪽에서 값을 가져온다는 것을 명시해주었다. (실질적으로는 부모-자식 관계를 나태내준다고 한다)


 2. 다음으로는 cart에 여러개의 select_item을 가질 수 있다는 것을 나타내고, dependency를 설정해주자.

 > vi app/models/cart.rb


class Cart < ActiveRecord::Base
  has_many :select_items, dependent: :destroy
end

 has_many를 이용해서 1대 多관계를 표현해주고, dependent를 이용해서 의존성 관계를 표현해주면 된다.


 3. 마지막으로 book 또한 여러개의 select_item을 가질 수 있다고 표시해주고,

     삭제되기전에 카트에 있는지 여부를 파악하는 코드를 넣어준다.

> vi app/models/book.rb


class Book < ActiveRecord::Base
  has_many :select_items

  before_destroy :ensure_not_referenced_by_any_select_item
  
  private 
  def ensure_not_referenced_by_any_select_item
    if select_items.empty?
      return true;
    else
      errors.add(:base, 'Selected items present')
      return false;
    end 
  end 
end

 before_destroy는 [Class NAME] 테이블에서 삭제가 일어 나기전에 하는 행동을 정해줄 수 있다. 현재는 book 테이블에서 삭제가 일어나기 전에 행동을 설정해 줄 수 있다. 행동으로는 ensure_not_referenced_by_any_select_item 함수를 호출 해준다. 이 함수는 select_item에 아이템이 있는지 여부를 확인하여서 참과 거짓을 넘겨주는데 참이 올경우에만 삭제가 일어나게 된다.


 위의 3개의 설정으로 위에서 나온 그림과 같은 관계가 설정이 된다.



   장바구니 기능 만들기


 여기에서 이제 앞선 과정에서 미리 만들어진 버튼 위치에 버튼을 넣어주도록 한다. 사용자가 카트를 이용하는 부분은 카탈로그에서 일어나기 때문에 이부분에 대해서 수정을 해준다.

> vi app/views/market/index.html.erb


<h1>Catalog</h1>

<% @books.each do |book| %>
<div class="item">
  <%= image_tag(book.image_url, class: 'book_cover') %>
  <div class="description">
    <h3><%= book.name %></h3>
    <p><%= book.description %></p>
    <p class="price">Price <%= book.price %> </p> 
    <%= button_to 'Add to Cart', select_items_path(book_id:book), class: 'add_cart' %>
  </div>
</div>
<% end %>

 여기서는 button_to template를 배워보자.  <%= button_to '[Name]', [link], [Attribute1]:[value], ... %> 의 형식을 같는 템플릿으로 하나의 버튼을 추가해준다. 여기서는 책의 아이디 값을 이용하여 카트에 책을 추가해주는 작업을 한다. 카트아이디가 필요없는 경우는 현재에서 이미 어플리케이션 컨트롤러에서 그 값을 알고 있기 때문이다.


 이제 관련된 select_item의 컨트롤러를 수정하면된다. 현재 수정해야 할 것은 select_item의 create기능이다.

> vi app/controller/select_items_controller.rb


  # POST /select_items
  # POST /select_items.json
  def create
    @cart = current_cart
    book = Book.find(params[:book_id])

    @select_item = @cart.select_items.build(book: book)

    respond_to do |format|
      if @select_item.save
        format.html { redirect_to @select_item.cart, notice: 'Select item was successfully created.' }
        format.json { render json: @select_item, status: :created, location: @select_item }
      else
        format.html { render action: 'new' }
        format.json { render json: @select_item.errors, status: :unprocessable_entity }
      end
    end
  end

 일단 테이블에 내용을 추가하기 위해서 현재 카트아이디 값을 가져왔다. 또한 어떤책이 참조되었는지 파라미터를 참조하여 가져왔다. 그리고 이 정보들을 이용하여 하나의 아이템을 추가하였다. 또한, 변환된 정보를 저장하면서 현재 보여줄 페이지와 넘겨줄 메세지를 선정하였다.



   뷰 설정


 장바구니 만드는 법도 이제 마지막 단계이다. 이제 장바구니가 추가되었을때 보여지는 화면을 변경하면된다. 이는 carts/show.html.erb를 수정하면 된다.


> vi app/views/cars/show.html.erb


<p id="notice"><%= notice %></p>

<h2> Your Cart </h2>

<ul>
  <% @cart.select_items.each do |item| %>
    <li><%= item.quantity %> × <%= item.book.name %></li>
  <% end %>
</ul>

사용자가 적은 메세지를 출력해주고, 카트 목록을 같이 출력해준다.


 이제 마지막으로 상위 네비게이션에 카트메뉴를 추가시켜주면 된다. 앞서서 수정했던 application layout을 한번더 수정하자.

 > vi app/views/layouts/application.html.erb


<!DOCTYPE html>
<html>
<head>
  <title>Study</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body class="<%= controller.controller_name %>">

  <div id="header">
    <%= @page_title || "Destiny Book store" %>
    <div id="navigation">
      <ul>
        <li><a herf="#">Home</a></li>
        <li><%= link_to "Catalog", market_index_path %></li>
        <li><%= link_to "Edit List", books_path %></li>
        <li><%= link_to "Cart", cart_url(session[:cart_id]) %></li>
      </ul>
    </div>
  </div>

  <div id="body">
    <%= yield %>
  </div>

</body>
</html>

 여기서 url을 사용했는데 url은 뒤에 '/' 파라미터를 취하고 path값은 '.'파라미터를 취하기 때문이다.



   마무리...


 숨가쁘게 장바구니에 대해서 만들었다. 여기에 원하는 기능들을 추가시켜 원하는 형태로 변형시켜보자. 이를 이용하면 친구목록관리, 팔로우 페이지등 무궁무진한 발전이 가능할 것이다.