简体   繁体   中英

How to restrict current_user from adding more than 3 order_items to a order per time period?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM