title: Customizing spree's product controller create action
body: <p>I have a very specific customization question for Spree. I wish to add multi-tenancy to its core functionality. My app needs to have the following setup:</p>
  
  <ol>
  <li><p>Users who can simultaneously buy and sell products through the site (just like craigslist).</p></li>
  <li><p>Users become vendors in that they are associated as the owners of the products that they list for sale.</p></li>
  <li><p>There will be a products index page that can reference every Spree::Product. When you click on a specific product it will tell you who the seller is and you can quickly navigate to that sellers profile page and see that seller's products.</p></li>
  <li><p>Each user can manage the products they are selling: add product images, change the product description, see who has ordered their products, and of course get money from those transactions.</p></li>
  </ol>
  
  <p>Trying to implement this has been a challenge, to put it lightly. I'm a relative newcomer to rails. I have investigated and implemented (somewhat successfully) the spree <a href="https://github.com/stefansenk/spree_multi_tenant" rel="nofollow">multi-tenant</a> gem. It seemed to overcomplicate things, and it only works with Spree 2.2.</p>
  
  <p>What I think would be the best way for me to accomplish this is to use the class eval method on the product/create action. Here's some Spree documentation that led me to believe this: <a href="https://guides.spreecommerce.com/developer/logic.html" rel="nofollow">link</a></p>
  
  <p>I would like all of your help in giving me some insight into how to do this. Or, if this doesn't make sense, what would be a better way to go about doing this?</p>
  
  <p>I have done some work already: I added the necessary associations in a product model decorator and my spree user class. I have created and ran a migration to add a user_id column to the spree::products as well.</p>
  
  <p>What I need to do now is write the code to assign the current_user's id to a column on the product that they create. I am unsure of how to do this. Basically, I don't know exactly what code I need to override in order to just make this change and not break anything else.</p>
  
  <p>Thanks for your responses in advance. I am happy to clarify anything that I've left out.</p>
  
  <p>Below is the code from the spree_backend gem for the products controller:</p>
  
  <pre><code>module Spree
  module Admin
  class ProductsController &lt; ResourceController
    helper 'spree/products'
  
    before_filter :load_data, :except =&gt; :index
    create.before :create_before
    update.before :update_before
    helper_method :clone_object_url
  
    def show
      session[:return_to] ||= request.referer
      redirect_to( :action =&gt; :edit )
    end
  
    def index
      session[:return_to] = request.url
      respond_with(@collection)
    end
  
    def update
      if params[:product][:taxon_ids].present?
        params[:product][:taxon_ids] = params[:product][:taxon_ids].split(',')
      end
      if params[:product][:option_type_ids].present?
        params[:product][:option_type_ids] = params[:product][:option_type_ids].split(',')
      end
      invoke_callbacks(:update, :before)
      if @object.update_attributes(permitted_resource_params)
        invoke_callbacks(:update, :after)
        flash[:success] = flash_message_for(@object, :successfully_updated)
        respond_with(@object) do |format|
          format.html { redirect_to location_after_save }
          format.js   { render :layout =&gt; false }
        end
      else
        # Stops people submitting blank slugs, causing errors when they try to update the product again
        @product.slug = @product.slug_was if @product.slug.blank?
        invoke_callbacks(:update, :fails)
        respond_with(@object)
      end
    end
  
    def destroy
      @product = Product.friendly.find(params[:id])
      @product.destroy
  
      flash[:success] = Spree.t('notice_messages.product_deleted')
  
      respond_with(@product) do |format|
        format.html { redirect_to collection_url }
        format.js  { render_js_for_destroy }
      end
    end
  
    def clone
      @new = @product.duplicate
  
      if @new.save
        flash[:success] = Spree.t('notice_messages.product_cloned')
      else
        flash[:error] = Spree.t('notice_messages.product_not_cloned')
      end
  
      redirect_to edit_admin_product_url(@new)
    end
  
    def stock
      @variants = @product.variants
      @variants = [@product.master] if @variants.empty?
      @stock_locations = StockLocation.accessible_by(current_ability, :read)
      if @stock_locations.empty?
        flash[:error] = Spree.t(:stock_management_requires_a_stock_location)
        redirect_to admin_stock_locations_path
      end
    end
  
    protected
  
      def find_resource
        Product.with_deleted.friendly.find(params[:id])
      end
  
      def location_after_save
        spree.edit_admin_product_url(@product)
      end
  
      def load_data
        @taxons = Taxon.order(:name)
        @option_types = OptionType.order(:name)
        @tax_categories = TaxCategory.order(:name)
        @shipping_categories = ShippingCategory.order(:name)
      end
  
      def collection
        return @collection if @collection.present?
        params[:q] ||= {}
        params[:q][:deleted_at_null] ||= "1"
  
        params[:q][:s] ||= "name asc"
        @collection = super
        @collection = @collection.with_deleted if params[:q].delete(:deleted_at_null) == '0'
        # @search needs to be defined as this is passed to search_form_for
        @search = @collection.ransack(params[:q])
        @collection = @search.result.
              distinct_by_product_ids(params[:q][:s]).
              includes(product_includes).
              page(params[:page]).
              per(Spree::Config[:admin_products_per_page])
  
        @collection
      end
  
      def create_before
        return if params[:product][:prototype_id].blank?
        @prototype = Spree::Prototype.find(params[:product][:prototype_id])
      end
  
      def update_before
        # note: we only reset the product properties if we're receiving a post from the form on that tab
        return unless params[:clear_product_properties]
        params[:product] ||= {}
      end
  
      def product_includes
        [{ :variants =&gt; [:images], :master =&gt; [:images, :default_price]}]
      end
  
      def clone_object_url resource
        clone_admin_product_url resource
      end
  
      def permit_attributes
        params.require(:product).permit!
      end
  end
  </code></pre>
  
  <p>end
  end</p>
  
  <p>You'll notice that there is no Create action in this products controller. It is inheriting it from the resource controller here:</p>
  
  <pre><code>class Spree::Admin::ResourceController &lt; Spree::Admin::BaseController
  
  
  include Spree::Backend::Callbacks
  
    helper_method :new_object_url, :edit_object_url, :object_url, :collection_url
    before_filter :load_resource, :except =&gt; [:update_positions]
    rescue_from ActiveRecord::RecordNotFound, :with =&gt; :resource_not_found
  
    respond_to :html
  
    def new
      invoke_callbacks(:new_action, :before)
      respond_with(@object) do |format|
        format.html { render :layout =&gt; !request.xhr? }
        if request.xhr?
          format.js   { render :layout =&gt; false }
        end
      end
    end
  
    def edit
      respond_with(@object) do |format|
        format.html { render :layout =&gt; !request.xhr? }
        if request.xhr?
          format.js   { render :layout =&gt; false }
        end
      end
    end
  
    def update
      invoke_callbacks(:update, :before)
      if @object.update_attributes(permitted_resource_params)
        invoke_callbacks(:update, :after)
        flash[:success] = flash_message_for(@object, :successfully_updated)
        respond_with(@object) do |format|
          format.html { redirect_to location_after_save }
          format.js   { render :layout =&gt; false }
        end
      else
        invoke_callbacks(:update, :fails)
        respond_with(@object) do |format|
          format.html do
            flash.now[:error] = @object.errors.full_messages.join(", ")
            render action: 'edit'
          end
          format.js { render layout: false }
        end
      end
    end
  
    def create
      invoke_callbacks(:create, :before)
      @object.attributes = permitted_resource_params
      if @object.save
        invoke_callbacks(:create, :after)
        flash[:success] = flash_message_for(@object, :successfully_created)
        respond_with(@object) do |format|
          format.html { redirect_to location_after_save }
          format.js   { render :layout =&gt; false }
        end
      else
        invoke_callbacks(:create, :fails)
        respond_with(@object) do |format|
          format.html do
            flash.now[:error] = @object.errors.full_messages.join(", ")
            render action: 'new'
          end
          format.js { render layout: false }
        end
      end
    end
  
    def update_positions
      params[:positions].each do |id, index|
        model_class.find(id).update_attributes(:position =&gt; index)
      end
  
      respond_to do |format|
        format.js  { render :text =&gt; 'Ok' }
      end
    end
  
    def destroy
      invoke_callbacks(:destroy, :before)
      if @object.destroy
        invoke_callbacks(:destroy, :after)
        flash[:success] = flash_message_for(@object, :successfully_removed)
        respond_with(@object) do |format|
          format.html { redirect_to location_after_destroy }
          format.js   { render :partial =&gt; "spree/admin/shared/destroy" }
        end
      else
        invoke_callbacks(:destroy, :fails)
        respond_with(@object) do |format|
          format.html { redirect_to location_after_destroy }
        end
      end
    end
  
    protected
  
      class &lt;&lt; self
        attr_accessor :parent_data
  
        def belongs_to(model_name, options = {})
          @parent_data ||= {}
          @parent_data[:model_name] = model_name
          @parent_data[:model_class] = model_name.to_s.classify.constantize
          @parent_data[:find_by] = options[:find_by] || :id
        end
      end
  
      def resource_not_found
        flash[:error] = flash_message_for(model_class.new, :not_found)
        redirect_to collection_url
      end
  
      def model_class
        "Spree::#{controller_name.classify}".constantize
      end
  
      def model_name
        parent_data[:model_name].gsub('spree/', '')
      end
  
      def object_name
        controller_name.singularize
      end
  
      def load_resource
        if member_action?
          @object ||= load_resource_instance
  
          # call authorize! a third time (called twice already in Admin::BaseController)
          # this time we pass the actual instance so fine-grained abilities can control
          # access to individual records, not just entire models.
          authorize! action, @object
  
          instance_variable_set("@#{object_name}", @object)
        else
          @collection ||= collection
  
          # note: we don't call authorize here as the collection method should use
          # CanCan's accessible_by method to restrict the actual records returned
  
          instance_variable_set("@#{controller_name}", @collection)
        end
      end
  
      def load_resource_instance
        if new_actions.include?(action)
          build_resource
        elsif params[:id]
          find_resource
        end
      end
  
      def parent_data
        self.class.parent_data
      end
  
      def parent
        if parent_data.present?
          @parent ||= parent_data[:model_class].send("find_by_#{parent_data[:find_by]}", params["#{model_name}_id"])
          instance_variable_set("@#{model_name}", @parent)
        else
          nil
        end
      end
  
      def find_resource
        if parent_data.present?
          parent.send(controller_name).find(params[:id])
        else
          model_class.find(params[:id])
        end
      end
  
      def build_resource
        if parent_data.present?
          parent.send(controller_name).build
        else
          model_class.new
        end
      end
  
      def collection
        return parent.send(controller_name) if parent_data.present?
        if model_class.respond_to?(:accessible_by) &amp;&amp; !current_ability.has_block?(params[:action], model_class)
          model_class.accessible_by(current_ability, action)
        else
          model_class.where(nil)
        end
      end
  
      def location_after_destroy
        collection_url
      end
  
      def location_after_save
        collection_url
      end
  
      # URL helpers
  
      def new_object_url(options = {})
        if parent_data.present?
          spree.new_polymorphic_url([:admin, parent, model_class], options)
        else
          spree.new_polymorphic_url([:admin, model_class], options)
        end
      end
  
      def edit_object_url(object, options = {})
        if parent_data.present?
          spree.send "edit_admin_#{model_name}_#{object_name}_url", parent, object, options
        else
          spree.send "edit_admin_#{object_name}_url", object, options
        end
      end
  
      def object_url(object = nil, options = {})
        target = object ? object : @object
        if parent_data.present?
          spree.send "admin_#{model_name}_#{object_name}_url", parent, target, options
        else
          spree.send "admin_#{object_name}_url", target, options
        end
      end
  
      def collection_url(options = {})
        if parent_data.present?
          spree.polymorphic_url([:admin, parent, model_class], options)
        else
          spree.polymorphic_url([:admin, model_class], options)
        end
      end
  
      # Allow all attributes to be updatable.
      #
      # Other controllers can, should, override it to set custom logic
      def permitted_resource_params
        params.require(object_name).permit!
      end
  
      def collection_actions
        [:index]
      end
  
      def member_action?
        !collection_actions.include? action
      end
  
      def new_actions
        [:new, :create]
      end
  end
  </code></pre>