#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 может быть скрытым таким образом. Посмотрите на свой журнал запросов, и вы увидите, что он загружается не так, как вы ожидаете.