Product orders between 2 users

后端 未结 3 1056
离开以前
离开以前 2021-01-06 02:29

I have three models: User, Product, Offer and a problem with the relationship between these models.

Scenario:

User 1 posts a product

相关标签:
3条回答
  • 2021-01-06 02:55

    How about

    class User < ActiveRecord::Base
      has_many :products  # All products posted by this user
      has_many :offers    # All offers created by this user
    end
    
    class Product < ActiveRecord::Base
      belongs_to :user   # This is the user who posts the product (User 1)
      has_many :offers
    end
    
    class Offer < ActiveRecord::Base
      belongs_to :product
      belongs_to :user   # This is the user who creates the offer (User 2)
    
      # Use a 'state' field with values 'nil', 'accepted', 'rejected' 
    end 
    

    For your scenario:

    # User 1 posts a product
    product = user1.products.create
    
    # User 2 can send User 1 an offer with an price e.g $ 10
    offer = user2.offers.create(:product => product)
    
    # User 1 can accept or reject the offer
    offer.state = 'rejected'
    

    You could refine this depending on your needs - e.g. if the same product could be posted by different users.

    0 讨论(0)
  • 2021-01-06 02:56

    Warning: here comes a small novel.

    Part 1: setting up the associations

    I'd recommend reading the Rails guide on associations thoroughly, bookmark it, and read it again, because this is a key thing to understand properly, and can be a bit tricky - there are lots of options once you go beyond basic associations.

    One thing to notice about your app is that your users have two roles, buyers and sellers. You're going to need to be careful with the names of your associations - Does @user.offers return the offers the user has made, or the offers the user has received? You might want to be able to put lists of both these things in the user's profile.

    The basic relationships you're describing are fairly simple:

    • A user can sell many products, so User has_many :products and Product belongs_to :user

    • A user can make many offers, so User has_many :offers and Offer belongs_to :user

    • A product may receive many offers so Product has_many :offers and Offer belongs_to :product

    That's all well and good, and you could certainly get by just doing this - in which case you can skip down to Part 2 :)

    However, as soon as you start trying to add the through relationships the waters are going to get muddy. After all,

    • Offer belongs_to :user (the buyer), but it also has a user through product (the seller)

    • User has_many :products (that they are selling), but they also have many products through offers (that they are buying - well, trying to buy).

    Aargh, confusing!

    This is the point when you need the :class_name option, which lets you name an association differently to the class it refers to, and the :source option, which lets you name associations on the 'from' model differently to the 'through' model.

    So you might then form your associations like this:

    # User
    has_many :products_selling, class_name: 'Product'
    has_many :offers_received, class_name: 'Offer',
             through: :products_selling, source: :offers
    
    has_many :offers_made, class_name: 'Offer'
    has_many :products_buying, class_name: 'Product',
             through: :offers_made, source: :product
    
    
    # Product
    belongs_to :seller, class_name: 'User', foreign_key: :user_id
    has_many :offers
    has_many :buyers, class_name: 'User', through: :offers
    
    # Offer
    belongs_to :product
    belongs_to :buyer, class_name: 'User', foreign_key: :user_id
    has_one :seller, class_name: 'User', through: :product
    

    Although if you renamed your user_id columns to seller_id in the products table, and buyer_id in the offers table, you wouldn't need those :foreign_key options.

    Part 2: accepting/rejecting offers

    There's a number of ways to tackle this. I would put a boolean field accepted on Offer and then you could have something like

    # Offer
    def accept
      self.accepted = true
      save
    end
    
    def reject
      self.accepted = false
      save
    end
    

    and you could find the outstanding offers (where accepted is null)

    scope :outstanding, where(accepted: nil)
    

    To get the accept/reject logic happening in the controller, you might consider adding new RESTful actions (the linked guide is another one worth reading thoroughly!). You should find a line like

    resources :offers
    

    in config/routes.rb, which provides the standard actions index, show, edit, etc. You can change it to

    resources :offers do
      member do
        post :accept
        post :reject
      end
    end
    

    and put something like this in your OffersController

    def accept
      offer = current_user.offers_received.find(params[:id])
      offer.accept
    end
    
    # similarly for reject
    

    Then you can issue a POST request to offers/3/accept and it will cause the offer with id 3 to be accepted. Something like this in a view should do it:

    link_to "Accept this offer", accept_offer_path(@offer), method: :post 
    

    Note that I didn't just write Offer.find(params[:id]) because then a crafty user could accept offers on the behalf of the seller. See Rails Best Practices.

    0 讨论(0)
  • 2021-01-06 03:17

    Your models are good enough, except for the relations. The confusion starts when you are trying to differentiate the owned products vs interested products(offered) and product owner vs interested users(users who placed the offer). If you can come up with a better naming convention, you can easily fix it.

    1. Better relations

    class User < ActiveRecord::Base
      attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :avatar, :screen_name
      has_many :owned_products, :class_name => "Product"
      has_many :offers 
      has_many :interested_products, :through => :offers
    end
    
    class Offer < ActiveRecord::Base
      attr_accessible :offer_price, :status, :user_id, :product_id
      belongs_to :interested_user, :class_name => "User", :foreign_key => :user_id
      belongs_to :interested_product, :class_name => "Product", :foreign_key => :product_id
    end
    
    class Product < ActiveRecord::Base
      attr_accessible :content, :price, :title, :tag_list, :productimage, :user_id
      belongs_to :owner, :foreign_key => :user_id, :class_name => "User"
      has_many :offers 
      has_many :interested_users, :through => :offers
    end
    

    With these relations I think you can get all the basic information you would be interested. For Example,

    @product = Product.find(1)
    @product.owner # would give you the user who created the product
    @product.interested_users # would give you users who placed an offer for this product
    
    @user = User.find(1)
    @user.owned_products # would give you the products created by this user
    @user.interested_products # would give you the products where the user placed an offer
    

    2. Handling accept and reject actions.

    From your description, I see there can be 2 possible state changes to an offer, "created" -> "accept" or "created" -> "reject". I suggest you to look at state_machine. State machine will add nice flavor to your model with its helper methods, which I think will be very useful in your case. So your Offer model will look something like this,

    class Offer < ActiveRecord::Base
      # attr_accessible :title, :body
      attr_accessible :offer_price, :status, :user_id, :product_id
      belongs_to :interested_user, :class_name => "User", :foreign_key => :user_id
      belongs_to :interested_product, :class_name => "Product", :foreign_key => :product_id
    
      state_machine :status, :initial => :created do
        event :accept do
          transition :created => :accepted
        end
        event :reject do
          transition :created => :reject
        end
      end
    end
    
    #cool helper methods
    @offer = Offer.new
    @offer.accepted? #returns false
    @offer.reject #rejects the offer
    @offer.rejected? #returns true
    

    I hope this gives you a better picture.

    0 讨论(0)
提交回复
热议问题