Специальная проверка Rails 3

#ruby-on-rails #ruby-on-rails-3

#ruby-on-rails #ruby-on-rails-3

Вопрос:

я столкнулся со сложной ситуацией.

 class Cart < ActiveRecord::Base
  has_many :cart_items
  accepts_nested_attributes_for :cart_items
end

class CartItem << ActiveRecord::Base
  belongs_to :cart
  belongs_to :item

  def for_friend?
    true if self.friend_email?
  end
end

Class Item << ActiveRecord::Base
  MAXIMUM_ITEMS_PER_USER = 10

  has_many :cart_items
end
  

Хорошо, это простой пример ассоциации. Каждая корзина может содержать множество элементов cart_items.
Но мы не можем продавать более 10 товаров для одного пользователя (другими словами, 1 пользователь может купить только 10 ЗЕЛЕНЫХ футболок). Это ограничение, определенное элементом::MAXIMUM_ITEMS_PER_USER.

Простейшим случаем проверки является проверка для каждой записи:

например: у нас есть объект CartItem:

 CartItem id: 1, cart_id: 1, item_id: 1, count: 1, friend_email: nil
  

и некоторый валидатор, определенный в модели CartItem:

 class CartItem < ActiveRecord::Base
  validates :count_per_user
end

def count_per_user
  self.errors.add(:base, "ONLY 10 WE CAN") if self.count > Item::MAXIMUM_ITEMS_PER_USER
end
  

Хорошо, если кто-то установит значение CartItem object count больше 10, мы выдадим сообщение об ошибке.

Но в моем случае пользователь может купить какой-нибудь предмет для друга. Таким образом, пользователь может использовать заголовки в своей корзине (разница будет только в полях COUNT и friend_email).

Это выглядит следующим образом:

 CartItem id: 1, cart_id: 1, item_id: 1, count: 1, friend_email: nil

CartItem id: 2, cart_id: 1, item_id: 1, count: 2, friend_email: mysuperfriend@gmail.com
  

Проблема в том, что нам нужно для проверки суммы «полей подсчета» двух записей 🙂

методы, которые я нахожу, заключаются в том, чтобы поместить проверку в контроллер:

 # controller method #update
def update
  @cart = Cart.find(params[:id])
  update_and_validate_count(params)
end

def update_and_validate_count(controller_params)
  @cart_items = get_and_update_cart_items_from(controller_params) # just assign new values, but doesn't save any
  summ = @cart_items.inject(0) {|result,r| result  = r.count}
  if summ > Item::MAXIMUM_ITEMS_PER_USER
    @cart.errors.add(:base, "WRONG COUNT, ONLY 10 IS POSSIBLE")
  else
    @cart.update_attributes(controller_params[:cart])
  end
end
  

Это хорошие методы для решения этой проблемы, но это выглядит плохо (
Можете ли вы дать мне несколько советов? Или, может быть, у вас есть некоторый опыт в этом случае?

Ответ №1:

Вы должны быть в состоянии выполнить эту проверку на уровне корзины, а не CartItem. Что-то вроде этого:

 def validate_count_requirements
  if (self.cart.cart_items.sum(:count) > Item::MAXIMUM_ITEMS_PER_USER)
    self.errors.add(:base, "ONLY #{Item::MAXIMUM_ITEMS_PER_USER} WE CAN")
  end
end
  

Хотя это не очень эффективно, в большинстве случаев это должно сработать. Вы даже можете использовать область, cart_items поскольку вам нужно выполнить SUM() операцию только над определенными значениями.

В качестве примечания, count это неправильное название атрибута, потому что это зарезервированное ключевое слово SQL, хотя оно будет работать, потому что ActiveRecord правильно его экранирует. Если вы забудете сделать это в любом из ваших пользовательских запросов, вам, возможно, придется обратить на это особое внимание. Хорошей неконфликтной альтернативой является quantity .

Комментарии:

1. основная проблема заключается в том, что вызов cart.cart_items на уровне модели будет загружать объекты из базы данных, а не из памяти 🙂 для Rails 3.1 с IdentityMap это не проблема, но на данный момент я использую rails 3.0.7 🙂

2. cart.cart_items элементы будут загружены, только если вы на самом деле ссылаетесь на него как на массив, но если вы вызываете sum для него, это просто генерирует запрос. ActiveRecord может быть скрытым таким образом. Посмотрите на свой журнал запросов, и вы увидите, что он загружается не так, как вы ожидаете.