Реализация аренды магазина в Rails: как отслеживать состояние запасов со временем? - PullRequest
3 голосов
/ 16 ноября 2009

Допустим, вы внедряете приложение rails для магазина проката сноубордов.

Данный сноуборд может находиться в одном из 3 состояний:

  1. прочь на техническое обслуживание
  2. в магазине X
  3. в кредит клиенту Y

Компания должна иметь возможность просматривать историю аренды за

  • определенный сноуборд
  • конкретный клиент

История проката должна включать временные данные (например, сноуборд, арендованный Салли 0123 с 1 декабря 2009 г. по 3 декабря 2009 г.).

Как бы вы разработали свою модель? Хотели бы вы иметь таблицу сноуборда с 4 столбцами (id, state, customer, store) и копировать строки из этой таблицы вместе с отметкой времени в таблицу snowboard_history при каждом изменении состояния?

Спасибо!

(Примечание: на самом деле я не пытаюсь реализовать пункт проката; это был просто самый простой аналог, который я мог придумать.)

Ответы [ 2 ]

9 голосов
/ 16 ноября 2009

Я бы использовал пару плагинов, чтобы выполнить работу. Который будет использовать четыре модели. Сноуборд, Магазин, Пользователь и Аудит.

act_as_state_machine и Act_as_audited

AASM упрощает переходы между состояниями. В то время как аудит создает историю, которую вы хотите.

Код для Магазина и Пользователя тривиален, а act_as_audited будет обрабатывать модель аудита.

class Snowboard < ActiveRecord::Base

  include AASM
  belongs_to :store


  aasm_initial_state :unread
  acts_as_audited :only => :state

  aasm_state :maintenance
  aasm_state :available
  aasm_state :rented

  aasm_event :send_for_repairs do
    transitions :to => :maintenance, :from => [:available]
  end

  aasm_event :return_from_repairs do
    transitions :to => :available, :from => [:maintenance]
  end

  aasm_event :rent_to_customer do
   transitions :to => :rented, :from => [:available]
  end

  aasm_event :returned_by_customer do
    transitions :to => :available, :from => [:rented]
  end
end

class User < ActiveRecord::Base
  has_many :full_history, :class_name => 'Audit', :as => :user,
   :conditions => {:auditable_type => "Snowboard"}
end    

Предполагая, что ваш клиент является current_user во время действия контроллера, когда состояние меняется, это все, что вам нужно.

Чтобы получить историю сноуборда:

@snowboard.audits

Чтобы получить историю аренды клиента:

@customer.full_history

Возможно, вы захотите создать вспомогательный метод, чтобы превратить историю клиента в нечто более полезное. Может быть что-то вроде его:

 def rental_history
    history = []
    outstanding_rentals = {}
    full_history.each do |item|
      id = item.auditable_id
      if rented_at = outstanding_rentals.keys.delete(id)
        history << { 
          :snowboard_id => id, 
          :rental_start => rented_at,
          :rental_end => item.created_at
        }   
      else
        outstanding_rentals[:id] = item.created_at
      end
    end
    history << oustanding_rentals.collect{|key, value| {:snowboard_id => key,  
      :rental_start => value}
  end
end
2 голосов
/ 16 ноября 2009

Сначала я бы сгенерировал отдельные модели для Snowboard, Customer и Store.

script/generate model Snowboard name:string price:integer ...
script/generate model Customer name:string ...
script/generate model Store name:string ...

(рельсы автоматически генерируют даты id и created_at, modified_at)

Чтобы сохранить историю, я бы не копировал строки / значения из этих таблиц, за исключением случаев, когда это необходимо (например, если вы хотите отслеживать цену, которую клиент арендовал).

Вместо этого я бы создал модель SnowboardEvent (вы могли бы назвать ее SnowboardHistory, если хотите, но лично мне кажется странным делать новую историю) с подобными свойствами, которые вы описали:

  • ev_type (т. Е. 0 для ВОЗВРАТА, 1 для ОБСЛУЖИВАНИЯ, 2 для АРЕНДЫ ...)
  • snowboard_id (не ноль)
  • customer_id
  • store_id

Например,

script/generate model SnowboardEvent ev_type:integer snowboard_id:integer \
    customer_id:integer store_id:integer

Тогда я бы установил все отношения между SnowboardEvent, Snowboard, Customer и Store. Сноуборд может иметь такие функции, как current_state, current_store, реализованные как

class Snowboard < ActiveRecord::Base
  has_many :snowboard_events
  validates_presence_of :name

  def initialize(store)
    ev = SnowboardEvent.new(
         {:ev_type => RETURN,
          :store_id => store.id,
          :snowboard_id = id,
          :customer_id => nil})
    ev.save
  end

  def current_state
    ev = snowboard_events.last
    ev.ev_type          
  end

  def current_store
    ev = snowboard_events.last
    if ev.ev_type == RETURN
      return ev.store_id
    end
    nil
  end

  def rent(customer)
    last = snowboard_events.last
    if last.ev_type == RETURN
      ev = SnowboardEvent.new(
           {:ev_type => RENT,
            :snowboard_id => id,
            :customer_id => customer.id
            :store_id => nil })
      ev.save
    end
  end

  def return_to(store)
    last = snowboard_events.last
    if last.ev_type != RETURN
      # Force customer to be same as last one
      ev = SnowboardEvent.new(
           {:ev_type => RETURN,
            :snowboard_id => id,
            :customer_id => last.customer.id
            :store_id => store.id})
      ev.save
    end
  end
end

И у Клиента будет такой же has_many :snowboard_events.

Проверка сноуборда или истории клиентов - это просто вопрос циклического просмотра записей с Snowboard.snowboard_events или Customer.snowboard_events. «Временные данные» будут свойством created_at этих событий. Я не думаю, что использование Observer необходимо или связано.

ПРИМЕЧАНИЕ: приведенный выше код не проверен и ни в коем случае не идеален, но только для того, чтобы понять:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...