Hot questions for Using Spree in model

Question:

I have added a custom field in my spree_orders table (let's call it custom_attribute).

I have added Spree::PermittedAttributes.checkout_attributes << [:custom_attribute] to my spree.rb initializer.

In my checkout process I have a custom form with the following code (html formatting has been removed):

<%= form_for @order do |alt_form| %>
      <%= alt_form.label :custom_attribute, "Custom Attribute" %><span class="required">*</span><br />
      <%= alt_form.text_field :custom_attribute, :class => 'form-control required', maxlength: 11 %>
<% end %>

This form successfully submits the field in the post request (full dump below) to http://localhost:3000/checkout/update/address as order[custom_attribute] xyz, however, the information is not saved to the model.

_method=patch
_method=patch
authenticity_token=Y+ATRotWKfI57f+b0/YGwIw9Bg6mADHBDmeEOHYzLPnB6Vbydya4ITDTopcX65EG+TiL7bwyJKQPpBU9bQTaUg==
authenticity_token=Y+ATRotWKfI57f+b0/YGwIw9Bg6mADHBDmeEOHYzLPnB6Vbydya4ITDTopcX65EG+TiL7bwyJKQPpBU9bQTaUg==
commit=Save and Continue
order[bill_address_attributes][address1]=123 Test
order[bill_address_attributes][address2]=
order[bill_address_attributes][city]=Test
order[bill_address_attributes][country_id]=232
order[bill_address_attributes][firstname]=Test
order[bill_address_attributes][id]=3
order[bill_address_attributes][lastname]=Test
order[bill_address_attributes][phone]=555555555
order[bill_address_attributes][state_id]=3535
order[bill_address_attributes][zipcode]=30024
order[email]=spree@example.com
order[custom_attribute]=2414
order[state_lock_version]=32
utf8=✓
utf8=✓

I've inserted @order.inspect on the following (payment) page to can see at that point that @order.custom_attribute is still nil.

Does anyone have any idea about what I need to do in order to get the custom_attribute value sent in the post request saved to the model with the other attributes sent?

-------------------edit-------------------

Default spree permitted attributes are defined here https://github.com/spree/spree/blob/3-0-stable/core/lib/spree/core/controller_helpers/strong_parameters.rb and are added on by the strong_paramaters helper here (don't have the rep to post a third link):

module Spree
  module Core
    module ControllerHelpers
      module StrongParameters
        def permitted_attributes
          Spree::PermittedAttributes
        end

        delegate *Spree::PermittedAttributes::ATTRIBUTES,
                 to: :permitted_attributes,
                 prefix: :permitted

        def permitted_payment_attributes
          permitted_attributes.payment_attributes + [
            source_attributes: permitted_source_attributes
          ]
        end

        def permitted_checkout_attributes
          permitted_attributes.checkout_attributes + [
            bill_address_attributes: permitted_address_attributes,
            ship_address_attributes: permitted_address_attributes,
            payments_attributes: permitted_payment_attributes,
            shipments_attributes: permitted_shipment_attributes
          ]
        end

        def permitted_order_attributes
          permitted_checkout_attributes + [
            line_items_attributes: permitted_line_item_attributes
          ]
        end

        def permitted_product_attributes
          permitted_attributes.product_attributes + [
            product_properties_attributes: permitted_product_properties_attributes
          ]
        end
      end
    end
  end
end

which can be at found spree/core/lib/spree/core/controller_helpers/strong_parameters.rb in the spree github repo.

-------------------final edit------------------- If anyone finds this in the future and is trying to troubleshoot a similar issue, my code above is actually correct; I had (stupidly) placed it in an if Rails.env.production? block.


Answer:

I will give you an example, maybe you can translate it into your code.


OPTIONAL

Imagine that I have a custom action, called "custom" on my users controller, defined this way in my routes:

resources :users do
  collection do
    get 'custom'
    post 'custom'
  end
end

This way I can call it by using custom_users_path.

Next, I want a form that submits to that function, to do that you need to specify an additional parameter in your form_for called :url, in this example I call it using custom_users_path, once I submit the form, It will run my custom action.

form_for would look like this:

<%= form_for :user, :url  => custom_users_path do |f| %>
  <%= f.text_field :random %>
  <%= f.submit "Submit" %>
<% end %>


Then, I want to be able to access some :random parameter in my users controller. Let's suppose that I have a text_field which I want store the value on my :random parameter (see above). First, you need to permit that parameter to be accessible in your controller, in this example, in users controller. This way:

params.require(:user).permit(YOUR PARAMETER HERE, {:random => []})

So, every time I submit the form, I can access the :submit parameter value, by doing this params["controller-name"]["parameter-name"], translated into this example, would look like:

params["user"]["random"]

You can then convert it into string using to_s if you want.

Output (Supposing that I wrote "444" on my text_field):

444

I hope this helps you.

Question:

I created new models in my app which is using spree. I have a new model called lets say Something and I want to spree Product to have an one to many relationship with Something. So I added a folder in apps directory called spree and model called product.rb and the code inside it is

module Spree
  class Product < Spree::Base
    extend FriendlyId

    has_many :somethings
  end
end

If I run my console, my products have access to something but all other default spree product methods are not. I think I overriden them. What is the mistake I am doing? I wanna open the spree products class and add my new associations.


Answer:

You gotta monkeypatch that thing!

#{Rails.root}/lib/extensions/spree/product.rb

Spree::Product.class_eval do |variable|
  has_many :somethings , class_name: Something ,:foreign_key => "something_id" 
end

/#{Rails.root}/config/application.rb

config.autoload_paths += %W(#{config.root}/lib/extensions)

Question:

I have an image slider at home page(Spree). I want to populate the slider with images from database. For that i have to create an image model, and associate it with home model. I found home controller and views, but I couldn't find home model. Where can i find it? and is this the right way of doing it, or is there any other optimized method?


Answer:

Ok, I solved it as, define a model for images inside Spree module

module Spree

 class SliderImage < Spree::Base
  has_attached_file :image, :styles => { :large => "980 x 280>",:medium => "400x100>", :thumb => "100x50>" }, :default_url => "no-photo.png"
  validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
 end

end

Make a tab for SliderImage in admin panel, where admin can deal with the slider images. Then, define its instance variable in home controller

module Spree
 class HomeController < Spree::StoreController
  respond_to :html

   def index
    @slider_images = Spree::SliderImage.all
   end
 end
end 

Now you can dynamically load your images in slider.

   #../app/views/spree/home/index.html.erb
   .......

      <% @slider_images.each do |image_slider| %>
        <div class="item active">
            <%= image_tag(image_slider.image(:large), alt: image_slider.title, class: "img-responsive", style: "width: 100%;") %>
        </div>
     <% end %>

   ........

Question:

I want to add Service category, same like Spree::Product, for that I have to define some associations, as below

class Service < ActiveRecord::Base
      has_many :images, -> { order(:position) }, as: :viewable, class_name: "Spree::Image", dependent: :destroy

      has_many :taxons, class_name: "Spree::Taxon", dependent: :destroy

      validates :name, presence: true,
                  length: { minimum: 5 }

end

Now, first, is this the right method to define such category or should i use some other convention to define Service, and for :taxons association, should I define migration to add service_id column in spree_taxons table?


Answer:

There is a matter of design there, Spree uses a model to join Taxons and Products, you should create it and name it services_taxon, the migration should look something like this:

class CreateServiceTaxon < ActiveRecord::Migration
  def change
    create_table :service_taxon do |t|
      t.integer :service_id
      t.integer :taxon_id
    end
  end
end

And on the Service model you should add:

class ServiceTaxon < ActiveRecord::Base
  belongs_to :service, :class_name => 'Service', :foreign_key => 'service_id'
  belongs_to :taxon, :class_name => 'Spree::Taxon', :foreign_key => 'taxon_id'
end

Another thing i should point out is that if you need some functionality that is already created by the spree team on the product model, you should really consider using theirs, or at least try to extend the product model.

Question:

I am using Spree to build an e-commerce application.I have created one model Offer(Spree::Offer) . I have created one role seller,and I am trying to give seller access to view,update and manage this model(Offer) in the admin panel using the following code

if user.respond_to?(:has_spree_role?) and user.has_spree_role?('supplier') > can :manage,Offer

But when I login as seller,I am unable to see Offers Tab in the Admin Panel But,when I login as admin ,I am able to see Offers Tab.Admin has following access

can :manage,:all

I am new to rails, Should I do anything prior, to get the desired result


Answer:

You need to define the ability like this as the offer model is under the spree module then and then you can access the Spree::Offer recourse.

if user.respond_to?(:has_spree_role?) and user.has_spree_role?('supplier') 
  can :manage, Spree::Offer
end

Question:

I was in the process of implementing a slide show for my spree store, when I came across a gem that does it. In part of the ReadMe file, I noticed it calls a model class method directly from the view: https://github.com/priviterag/spree_slider#or-roll-your-own

Here's the code:

<% if Spree::Slide.published.count > 0 %>
  <section id="slideshow">
    <ul class="slide">
      <% Spree::Slide.published.order('position ASC').each do |s| %>
        <li>
          <h1><%= s.slide_name %></h1>
          <%= link_to image_tag(s.slide_image.url), url_for(s.slide_link) %>
        </li>
      <% end %>
    </ul>
    <a title="<%= t(:previous) %>" class="slider-prev"><%= t(:previous)%></a>
    <a title="<%= t(:next) %>" class="slider-next"><%= t(:next) %></a>
  </section>
<% end %>

I was doing the same with my own slider, but had flagged the model call as something to move into the relevant controllers (there'd be a few) later. However, really, it seems like it's most self-contained, and easiest to understand and maintain, when the model is called directly from the view like this.

I'd always been lead to believe a View accessing a Model class directly gave good programers nightmares. But in this context, is fetching data directly from the view OK? Why or why not?


Answer:

I would not, mostly because your view is making a pile of assumptions that are going to be a pain to change later. Your view is assuming that it's using:

  • an ActiveRecord class,
  • with certain table columns,
  • with other objects it's directly calling the properties of.

Here's my take on a view:

<% if slides.count > 0 %>
  <section id="slideshow">
    <ul class="slide">
      <% slides.each do |s| %>
        <li>
          <h1><%= s.name %></h1>
          <%= link_to image_tag(s.image_url), url_for(s.link) %>
        </li>
      <% end %>
    </ul>
    <a title="<%= t(:previous) %>" class="slider-prev"><%= t(:previous)%></a>
    <a title="<%= t(:next) %>" class="slider-next"><%= t(:next) %></a>
  </section>
<% end %>

And with a presenter:

# eg. app/presenters/slide_presenter.rb
class SlidePresenter
  extend Forwardable
  def initialize(slide)
    @slide = slide
  end

  def_delegator :@slide, :name, :slide_name
  def_delegator :@slide, :link, :slide_link

  def image_url
    @slide.slide_image.url
  end
end

And the controller action itself:

slides = Spree::Slide.published
                     .order('position ASC')
                     .map {|s| SlidePresenter.new(s) }
render 'theview', locals: { slides: slides }

The benefits of using a Presenter instead of directly using the model in the view is that it restricts how the model is used. Instead of calling any of the hundreds of methods on the ActiveRecord model (with all the variations in parameters), you know exactly how it's being used because you've only defined four methods on the presenter.

(Hiding the model behaviour behind the presenter also gives you an interface that doesn't change: that is, changing the model means changing the guts of the presenter itself, but not all the views that use the presenter.)

I'm not a big fan of the direct ActiveRecord business in the controller in my example above, but you can choose to move it into something like a FetchOrderedSlides service class or helper method, further restricting the known uses of the hundreds of methods on Slides to a handful in very specific parts of your codebase.

Question:

Is it possible to have model translation for Spree 3.2 (through spree_globalize) in rails 5? I followed the instructions but when I run bundle update, I get this error:

Bundler could not find compatible versions for gem "spree_i18n":
  In Gemfile:
    spree_i18n

    spree_globalize was resolved to 3.1.0.beta, which depends on
      spree_i18n (~> 3.1.0.beta)

This is the relevant part of my gemfile

gem 'spree', '~> 3.2.0.rc1'
gem 'spree_auth_devise', '~> 3.2.0.beta'
gem 'spree_gateway', '~> 3.2.0.beta'
gem 'spree_i18n', github: 'spree-contrib/spree_i18n' 
gem 'spree_globalize', github: 'spree-contrib/spree_globalize'

Answer:

The problem you're encountering now is a common one with versioning of Spree add-ons. They tend to be pinned to one version of Spree, so when you upgrade you often end up having to fork the add-ons, modify the dependency requirements, test, maybe fix, then use it, upstream it. It's a bit of a hassle but it ends up working so that there's good support of add-ons compatible with every version of Spree.

In the world of Spree there's a consistent format to versioning. Spree and add-ons tend to have branches matching the version of Spree. For example, 3-1-stable for Spree and all add-ons. Unless you require something from Spree 3.2, I recommend sticking with 3.1 since it's been out for months and most of the commonly-used add-ons have been updated and tested for it. Regardless of the version you go with, make your add-ons use the same version as Spree!. Life will be a lot easier that way. (Sidenote: I tend to hang at least one minor version back to wait for add-ons to be updated, bugs to be sussed-out before upgrading)

Here's what I do in my Gemfile to enforce consistency. You'll notice I'm request versions 3.1 of the add-ons and pointing to their 3-1-stable branches.

# Spree
spree_version = '3.1'
spree_branch = "#{spree_version.sub(/\./, '-')}-stable"

gem 'spree',                  "~> #{spree_version}", github: 'spree/spree', branch: spree_branch
gem 'spree_gateway',          "~> #{spree_version}", github: 'WebGents/spree_gateway', branch: "#{spree_branch}-quickpay-storage"
gem 'spree_auth_devise',      "~> #{spree_version}", branch: spree_branch
gem 'spree_static_content',   "~> #{spree_version}", github: 'spree-contrib/spree_static_content', branch: spree_branch
gem 'spree_sale_pricing',     "~> #{spree_version}", github: 'WebGents/spree-sale-pricing', branch: spree_branch
gem 'spree_i18n',             "~> #{spree_version}", github: 'spree-contrib/spree_i18n', branch: spree_branch
gem 'spree_variant_options',  "~> #{spree_version}", github: 'WebGents/spree_variant_options', branch: spree_branch

If you stick with version 3.1 you should find that the add-ons you want to use are all compatible and ready to go.

Question:

I'm creating a view that list all vendors using Spree Multi Vendor extension for Spree Commerce.

My index.html.erb template :

<% @vendors.each do |vendor| %>
    <h2>
      <%= vendor.name %>
    </h2>
<% end %>

My stores_controller.rb controller :

module Spree
    class StoresController < Spree::StoreController  
      def index
        @vendors = Spree::Vendor
      end
    end
end

The error that I'm getting :

NoMethodError in Spree::Stores#index

Showing /myapp/app/views/spree/stores/index.html.erb where line #36 raised:

undefined method `each' for # < Class:0x00007fe3f5570a40 >

Line #36 : <% @vendors.each do |vendor| %>

How can I correctly instantiate Vendor Model inside my controller so I can use it's methods and access it's attributes in the views of my store ?


Answer:

Spree::Vendor is just a model class. You can think about it like any other model - User, Product, etc.

In this case, you should call Spree::Vendor.all or Spree::Vendor.active to fetch records.

Question:

I'm trying to create a new model in spree extension. I generated a model and it is in /spree_extension/app/models/my_class.rb:

module Spree
  class MyClass < Spree::Base
    belongs_to :product
  end
end

But when I start my application, there is no Spree::MyClass, I get this error:

NameError: uninitialized constant Spree::MyClass

I tried moving my_class.rb to "spree" directory, but nothing helps.


Answer:

Most probably, you need to put your class into:

/spree_extension/app/models/spree/my_class.rb

As rails is always expecting to find classes inside file with the same name, inside folder that has the module name.

Question:

I'm trying to setup spree together with devise (not 'spree_auth_devise' gem but standalone devise). I've followed http://guides.spreecommerce.com/developer/authentication.html guide and everything is working well until in a shop I click "Add To Cart" which brings following error:

NoMethodError in Spree::OrdersController#edit
undefined method `orders' for #<User:0x007f6bb8782730>

I can fix that error by adding to User model:

def orders
   spree_orders
end

but I assume that's not the way it should be fixed.

Can anyone tell me the right way of setting it up so that I don't get that error?


Answer:

Adding the orders association would be the right way to correct the error:

has_many :orders, foreign_key: :user_id

If you look at LegacyUser in the Spree project that will show a lot of the barebones that your user model needs to provide to be able to function properly with Spree.

https://github.com/spree/spree/blob/master/core/app/models/spree/legacy_user.rb

Question:

I'm trying to create a route that link to the store page of each Vendor using Spree Commerce. The page should list the details (name, about us, etc), and the products of the given vendor.

https://example.com/stores/vendor-one

My routes.rb configuration :

Spree::Core::Engine.routes.draw do
    resources :stores
end

My stores_controller controller :

module Spree
    class StoresController < Spree::StoreController  
      require 'vendor'
      def show
        @vendor = current_spree_vendor
      end     
    end
end

My show.html.erb template :

<h2><%= @vendor.name %></h2>
<p><%= @vendor.about_us %></p>

The error that I'm getting :

NoMethodError in Spree::Stores#single

Showing /myapp/app/views/spree/stores/show.html.erb where line #2 raised:

undefined method `name' for nil:NilClass

Line # 2 : <h2><%= @vendor.name %></h2>

How can I load the given Vendor by getting its name from the url ? I'm new to Rails so any help will be highly appreciated !


Answer:

The way to debug errors like this is by working backwards from the error. The error is occurring in the template because @vendor is nil. Why? The controller has a #show method that is setting @vendor, but it is setting @vendor to the result of current_spree_vendor, so it must be that current_spree_vendor returned nil. So now you need to examine current_spree_vendor and figure out why it is sometimes returning nil.

You will soon learn how to debug this sort of error, and you'll need to because these kinds of errors are too common, and too specific, to be something that the internet can help you with. Even when the internet can help, it's too slow and waiting for answers on it will keep you from getting the job done.

Question:

I am new to spree. I created a new model in spree and was having issue in accessing it. I found this link with similar question but it did'nt reached to any satisfying answer. How to access a new spree model

The model that I ceated

module Spree
    class Spree::ClinicDetail < ActiveRecord::Base
        belongs_to :user, class_name: 'Spree::User', foreign_key: :spree_user_id
    end
end

On accessing it through console I was getting the error

LoadError: Unable to autoload constant Spree::Clinic_Detail

Please answer to my query. It would be a great help to me. Thankyou. Sorry if I am wrong in anyway and correct me please.


Answer:

As per the discussion and the error:

LoadError: Unable to autoload constant Spree::Clinic_Detail

I get, you are trying to access it through Spree::Clinic_Detail.find(1) while the model you defined it Spree::ClinicDetail (without an underscore).

So, you need to access it as below:

Spree::ClinicDetail.find(1)

Question:

In spree sign_up the app/models/spree/user.rb has

def set_login
  # for now force login to be same as email, eventually we will make this configurable, etc.
  self.login ||= self.email if self.email
end

How can I override set login and do something like this:

def set_login
  # for now force login to be same as email, eventually we will make this configurable, etc.
  self.login ||= self.phone if self.phone
end

Thanks in advance


Answer:

I did this in app/models/spree/user_decorator.rb

 Spree::User.class_eval do
    Spree::PermittedAttributes.user_attributes << :phone


 def set_login
   # for now force login to be same as email, eventually we will make this configurable, etc.
   self.login ||= self.phone if self.phone
 end
end

Question:

I want to add a new attribute "usertype" to User. I followed the answer in the following link:

Extending the Spree::Product model/class

I created a new migration and also created a file "spree/user_decorator.rb", which has the codes:

Spree::User.class_eval
  attr_accesssible :usertype
end

But when I run "rails s", it says I have "syntax error, unexpected keyword_end, expecting end-of-input (SyntaxError)" in this file.

What's wrong with the three lines of codes?

PS: I also tried

Spree::User.class_eval do
  attr_accesssible :usertype
end

Then it says the method attr_accesssible does not exist.


Answer:

Your first code sample is syntactically incorrect. Your second code sample is the correct syntax.

attr_accessible is no longer used in Rails 4, which is used by Spree 2.1 and later. You should look in to adding the attribute to Spree's PermittedAttributes instead.

Question:

I am new to ruby on rails and spree e-commerce too. what i want is to see all the models that manipulates this e-commerce framework poduct.rb, order.rb ... to understand relationships between all of them. But I did not find them


Answer:

You can start by generating your domain model:

This will generate a complete domain model of the project you are working on by looking at the relationships between the models.

Hope this helps.