I'm building a store in Rails that has a specific sales model. I need to allow a user to add only 3 items to his order per 30 days. The 30 days counter should start upon adding the first order_item. Once 30 days expires, user would be able to add 3 orders. If 30 days didn't pass and for an example, user adds two order_items he would still be allowed to add one more order_item within 30 days. So as well if user tries to add more then 3 items to show an error message and disregard saving of the order_items to current_user's order.
I have products, orders, order_items, users. I guess that I should add something to user model but I'm not sure what.
order_items_controller.rb
def create
@order = current_order
@order_item = @order.order_items.new(order_item_params)
@order.user_id = current_user.id
@order.save
session[:order_id] = @order.id
respond_to do |format|
format.js { flash[:notice] = "ORDER HAS BEEN CREATED." }
end
end
private
def order_item_params
params.require(:order_item).permit(:quantity, :product_id, :user_id)
end
end
user.rb
class User < ActiveRecord::Base
has_many :identities, dependent: :destroy
has_many :order
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :omniauthable, :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable
end
order_item.rb
class OrderItem < ActiveRecord::Base
belongs_to :product
belongs_to :order
validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
validate :product_present
validate :order_present
before_save :finalize
def unit_price
if persisted?
self[:unit_price]
else
product.price
end
end
def total_price
unit_price * quantity
end
private
def product_present
if product.nil?
errors.add(:product, "is not valid or is not active.")
end
end
def order_present
if order.nil?
errors.add(:order, "is not a valid order.")
end
end
def finalize
self[:unit_price] = unit_price
self[:total_price] = quantity * self[:unit_price]
end
end
order.rb
class Order < ActiveRecord::Base
belongs_to :order_status
has_many :order_items
before_create :set_order_status
before_save :update_subtotal
def subtotal
order_items.collect { |oi| oi.valid? ? (oi.quantity * oi.unit_price) : 0 }.sum
end
private
def set_order_status
self.order_status_id = 1
end
def update_subtotal
self[:subtotal] = subtotal
end
end
carts_controller.rb
class CartsController < ApplicationController
def show
@order_items = current_order.order_items
end
routes.rb
resources :order_items, only: [:create, :update, :destroy, :new]
form.html.erb
<%= form_for OrderItem.new, html: {class: "add-to-cart"}, remote: true do |f| %>
<div class="input-group">
<%= f.hidden_field :quantity, value: 1, min: 1 %>
<div class="input-group-btn">
<%= f.hidden_field :product_id, value: product.id %>
<%= f.submit "Add to Cart", data: { confirm: 'Are you sure that you want to order this item for current month?'}, class: "btn btn-default black-background white" %>
</div>
</div>
<% end %>
</div>
I would add a begin_date
and a order_counter
to user model. Every time you add an order, look if the begin_date
is more than 30 days ago, then set the begin_date
to the actual date. If the begin_date is less than 30 days ago, increase the counter. And if the counter ist already 3 refuse the order.
You can add the columns to the user table by the command line argument
rails generate migration AddOrderCounterToUser
This will create a class in db/migrations:
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :users, :begin_date, :date
add_column :users, :order_counter, :integer
end
end
Add the additional attributes in your UserController
to permit them in user_params
.
Then change the create method in your OrderItemController
def create
now = Date.today
success = false
if current_user.begin_date && ((now - 30) < current_user.begin_date)
if current_user.order_counter >= 3
# deal with the case that order should not be created,
# for example redirect.
else
current_user.order_counter += 1
current_user.save
success = true
end
else
current_user.order_counter = 1
current_user.begin_date = now
current_user.save
success = true
end
if success
@order = current_order
@order_item = @order.order_items.new(order_item_params)
@order.user_id = current_user.id
@order.save
session[:order_id] = @order.id
respond_to do |format|
format.js { flash[:notice] = "ORDER HAS BEEN CREATED." }
end
else
respond_to do |format|
format.js { flash[:notice] = "CREATION NOT POSSIBLE." }
end
end
end
You can also put the checking code in a method in the user model, that would be cleaner.
Generally, when you don't want to create an element in rails under certain circumstances, you should choose to handle the situation via validators.
You could take a nesting approaches here: Nest your OrderItem
routes under Order
(you can find further information about nesting in the Rails Guides about Nested Routing )
You should start by adding a new database column first_item_added_at
to you Order
model
rails generate migration AddFirstItemAddedAtToOrder
class AddFirstItemAddedAtToOrder < ActiveRecord::Migration
def change
add_column :orders, :first_item_added_at, :date
end
end
When nesting, you would create a new OrderItem
via the route
POST /orders/:id/order_items
Then, you have to add a validator to your OrderItem
model
class OrderItem < ActiveRecord::Base
validate :only_3_items_in_30_days
private
def only_3_items_in_30_days
now = Date.new
days_since_first = now - order.first_item_added_at
if order.order_items.count > 2 && days_since_first < 30
errors.add(:base, 'only 3 items in 30 days are allowed')
end
true # this is to make sure the validation chain is not broken in case the check fails
end
end
Now your controller only needs to create a new item and save it
def create
@item = OrderItem.new(item_params)
if @item.save
render <whatever_you_want_to_render>
else
# @item will contain the errors set in the model's validator
render <error_reaction>
end
end
private
def item_params
params.require(:order_item).permit(
:attribute_1,
:attribute_2,
:order_id # << this one is very important
)
end
If you don't wish to nest OrderItem
, than the model still remains the same, but your controller would look like:
def create
@item = OrderItem.new(order_item_params)
session[:order_id] = current_order.id
if @item.save
respond_to do |format|
format.js { flash[:notice] = "ORDER HAS BEEN CREATED." }
end
else
render <handling for error>
end
end
private
def order_item_params
base_params = params.require(:order_item)
.permit(:quantity, :product_id, :user_id)
base_params.merge(order: current_order)
end
Please note, that I added current_order.id
to your order_item_params
method.
EDIT: replaced order_id: current_order.id
by order: current_order
to provide the relation to the new OrderItem
before it is actually saved
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.