├── .gitignore ├── .rspec ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── app ├── controllers │ ├── concerns │ │ └── spree │ │ │ └── api │ │ │ └── v2 │ │ │ └── renderable.rb │ └── spree │ │ └── api │ │ └── v2 │ │ ├── base_controller.rb │ │ ├── children_controller.rb │ │ ├── countries_controller.rb │ │ ├── images_controller.rb │ │ ├── line_items_controller.rb │ │ ├── option_types_controller.rb │ │ ├── option_values_controller.rb │ │ ├── orders_controller.rb │ │ ├── prices_controller.rb │ │ ├── products_controller.rb │ │ ├── states_controller.rb │ │ ├── taxonomies_controller.rb │ │ ├── taxons_controller.rb │ │ └── variants_controller.rb ├── models │ └── spree │ │ ├── base_decorator.rb │ │ ├── image_decorator.rb │ │ ├── line_item_decorator.rb │ │ ├── order_decorator.rb │ │ ├── price_decorator.rb │ │ └── state_decorator.rb └── serializers │ └── spree │ ├── address_serializer.rb │ ├── base_serializer.rb │ ├── country_serializer.rb │ ├── error_serializer.rb │ ├── image_serializer.rb │ ├── line_item_serializer.rb │ ├── option_type_serializer.rb │ ├── option_value_serializer.rb │ ├── order_serializer.rb │ ├── price_serializer.rb │ ├── product_serializer.rb │ ├── role_serializer.rb │ ├── state_serializer.rb │ ├── store_serializer.rb │ ├── taxon_serializer.rb │ ├── taxonomy_serializer.rb │ ├── user_serializer.rb │ └── variant_serializer.rb ├── circle.yml ├── config ├── locales │ └── en.yml └── routes.rb ├── docs ├── .nojekyll ├── Dockerfile ├── Gemfile ├── README.md ├── Rakefile ├── config.rb ├── font-selection.json └── source │ ├── CNAME │ ├── fonts │ ├── slate.eot │ ├── slate.svg │ ├── slate.ttf │ ├── slate.woff │ └── slate.woff2 │ ├── images │ ├── favicon.ico │ └── navbar.png │ ├── includes │ ├── _countries.md │ ├── _errors.md │ ├── _filtering.md │ ├── _images.md │ ├── _line_items.md │ ├── _option_types.md │ ├── _option_values.md │ ├── _orders.md │ ├── _pagination.md │ ├── _prices.md │ ├── _products.md │ ├── _states.md │ ├── _taxonomies.md │ ├── _taxons.md │ └── _variants.md │ ├── index.md │ ├── javascripts │ ├── all.js │ ├── all_nosearch.js │ ├── app │ │ ├── _lang.js │ │ ├── _search.js │ │ └── _toc.js │ └── lib │ │ ├── _energize.js │ │ ├── _imagesloaded.min.js │ │ ├── _jquery.highlight.js │ │ ├── _jquery.tocify.js │ │ ├── _jquery_ui.js │ │ └── _lunr.js │ ├── layouts │ └── layout.erb │ └── stylesheets │ ├── _icon-font.scss │ ├── _normalize.css │ ├── _syntax.scss.erb │ ├── _variables.scss │ ├── print.css.scss │ └── screen.css.scss ├── lib ├── solidus_json_api.rb └── solidus_json_api │ ├── config.rb │ └── engine.rb ├── solidus_json_api.gemspec └── spec ├── controllers └── spree │ └── api │ └── v2 │ ├── base_controller_spec.rb │ ├── children_controller_spec.rb │ ├── countries_controller_spec.rb │ ├── images_controller_spec.rb │ ├── line_items_controller_spec.rb │ ├── option_types_controller_spec.rb │ ├── option_values_controller_spec.rb │ ├── orders_controller_spec.rb │ ├── prices_controller_spec.rb │ ├── products_controller_spec.rb │ ├── states_controller_spec.rb │ ├── taxonomies_controller_spec.rb │ ├── taxons_controller_spec.rb │ └── variants_controller_spec.rb ├── lib └── solidus_json_api │ └── config_spec.rb ├── models └── spree │ ├── base_decorator_spec.rb │ └── price_decorator_spec.rb ├── serializers └── spree │ ├── address_serializer_spec.rb │ ├── country_serializer_spec.rb │ ├── error_serializer_spec.rb │ ├── image_serializer_spec.rb │ ├── line_item_serializer_spec.rb │ ├── option_type_serializer_spec.rb │ ├── option_value_serializer_spec.rb │ ├── order_serializer_spec.rb │ ├── price_serializer_spec.rb │ ├── product_serializer_spec.rb │ ├── role_serializer_spec.rb │ ├── state_serializer_spec.rb │ ├── store_serializer_spec.rb │ ├── taxon_serializer_spec.rb │ ├── taxonomy_serializer_spec.rb │ ├── user_serializer_spec.rb │ └── variant_serializer_spec.rb ├── spec_helper.rb └── support └── shoulda_matchers.rb /.gitignore: -------------------------------------------------------------------------------- 1 | \#* 2 | *~ 3 | .#* 4 | .DS_Store 5 | .idea 6 | .project 7 | .sass-cache 8 | coverage 9 | Gemfile.lock 10 | tmp 11 | nbproject 12 | pkg 13 | *.swp 14 | spec/dummy 15 | build 16 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.3.1 4 | 5 | * Bump Active Model Serializer from rc2 to rc4. 6 | 7 | [Ben A. Morgan](https://github.com/BenMorganIO) 8 | 9 | ## 0.3.0 10 | 11 | * Support `status` and `code` for Error Objects 12 | 13 | When an error is handed back to the user, we now provide the status and the 14 | code keys. This adds another feature to the API that is supported by the 15 | JSON API specification. 16 | 17 | [Ben A. Morgan](https://github.com/BenMorganIO) 18 | 19 | * Add Friendly ID Support for `Spree::Product` 20 | 21 | When a request came in for `/api/v2/products/example-product` and there was 22 | a product in the DB with a corresponding slug, it would return a 404. The 23 | controller now calls `Spree::Product.friendly` before rendering any 24 | instance. 25 | 26 | [Ben A. Morgan](https://github.com/BenMorganIO) 27 | 28 | * Drop Spree Support 29 | 30 | After trying to get the spree support working, I’ve concluded that its just 31 | not worth it. I know there’s a lot of you out there that really want this. 32 | If you truly need to keep using Spree, please fork this repo and make the 33 | necessary changes. 34 | 35 | [Ben A. Morgan](https://github.com/BenMorganIO) 36 | 37 | * The project has been renamed from `spree_api_v2` to `solidus_json_api`. 38 | Since the Spree project is no longer maintained, then there's no reason to 39 | give Spree a second API version. This means that you need to update the 40 | following code: 41 | 42 | ```ruby 43 | ### Before ### 44 | 45 | # Gemfile 46 | gem 'spree_api_v2' 47 | 48 | # config/initializers/spree_api_v2.rb 49 | SolidusApiV2.setup do |config| 50 | config.parent_serializer = ApplicationSerializer 51 | end 52 | 53 | ### After ### 54 | 55 | # Gemfile 56 | gem 'solidus_json_api' 57 | 58 | # config/initializers/solidus_json_api.rb 59 | SolidusJsonApi.setup do |config| 60 | config.parent_serializer = ApplicationSerializer 61 | end 62 | ``` 63 | 64 | [Ben A. Morgan](https://github.com/BenMorganIO) 65 | 66 | * In Spree, you will receive an error if the stock is not sufficient when 67 | updating a line item. This is done in this project since the API requires 68 | it. However, when transition to Solidus, we discovered that this feature was 69 | constrained to the complete state. We've added a validation check before 70 | saving a line item. Please open an issue if you believe this to be an 71 | anti-pattern. 72 | 73 | [Ben A. Morgan](https://github.com/BenMorganIO) 74 | 75 | * Removed `Spree::Order#considered_risky` from the JSON response since Solidus 76 | doesn't support this anymore. 77 | 78 | [Ben A. Morgan](https://github.com/BenMorganIO) 79 | 80 | * `Spree::Variant#depth`, `Spree::Variant#width`, and `Spree::Variant#height` 81 | now return `null` instead of `""` when empty. 82 | 83 | ```json 84 | // Before 85 | { 86 | "type" : "spree_variant", 87 | "data" : { 88 | "attributes" : { 89 | "depth" : "", 90 | "width" : "", 91 | "height" : "", 92 | ... 93 | } 94 | } 95 | } 96 | 97 | // After 98 | { 99 | "type" : "spree_variant", 100 | "data" : { 101 | "attributes" : { 102 | "depth" : null, 103 | "width" : null, 104 | "height" : null, 105 | ... 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | [Ben A. Morgan](https://github.com/BenMorganIO) 112 | 113 | * `Spree::LineItem#additional_tax_total` and `Spree::LineItem#adjustment_total` 114 | now return as strings instead of floats in the api: 115 | 116 | ```json 117 | // Before 118 | { 119 | "type" : "spree_line_item", 120 | "data" : { 121 | "attributes" : { 122 | "additional_tax_total" : 0, 123 | "adjustment_total" : 0.0, 124 | ... 125 | } 126 | } 127 | } 128 | 129 | // After 130 | { 131 | "type" : "spree_line_item", 132 | "data" : { 133 | "attributes" : { 134 | "additional_tax_total" : "0", 135 | "adjustment_total" : "0.0", 136 | ... 137 | } 138 | } 139 | } 140 | ``` 141 | 142 | [Ben A. Morgan](https://github.com/BenMorganIO) 143 | 144 | * For Orders, you can now list them. A user can only view their own orders. 145 | If the user is an admin, they can view all of the orders. 146 | 147 | [Ben A. Morgan](https://github.com/BenMorganIO) 148 | 149 | * POST `/api/v2/line_items` now requires JSON API format instead of the rails 150 | format. 151 | 152 | ```shell 153 | # Before 154 | curl "https://example.com/api/v2/line_items" 155 | -X POST 156 | -d token=abc123 157 | -d line_item[order_id]=1 158 | -d line_item[variant_id]=1 159 | -d line_item[quantity]=1 160 | 161 | # After 162 | curl "https://example.com/api/v2/line_items" 163 | -X POST 164 | -d token=abc123 165 | -d data[attributes][order_id]=1 166 | -d data[attributes][variant_id]=1 167 | -d data[attributes][quantity]=1 168 | ``` 169 | 170 | [Ben A. Morgan](https://github.com/BenMorganIO) 171 | 172 | ## 0.2.2 173 | 174 | * The parent serializer for `Spree::BaseSerializer` is now configurable. 175 | In some instances, you may have an `ApplicationSerializer`. 176 | To make the `Spree::BaseSerializer` use an `ApplicationSerializer` instead 177 | of the `ActiveModel::Serializer`: 178 | 179 | ```ruby 180 | # config/initializers/spree_api_v2.rb; or 181 | # config/initializers/solidus_api_v2.rb 182 | SpreeApiV2.setup do |config| 183 | config.parent_serializer = ApplicationSerializer 184 | end 185 | ``` 186 | 187 | [Ben A. Morgan](https://github.com/BenMorganIO) 188 | 189 | ## 0.2.1 190 | 191 | * Fixes a bug where the user serializer wasn't extending from the `Spree::BaseSerializer` this is now fixed. 192 | 193 | [Ben A. Morgan](https://github.com/BenMorganIO) 194 | 195 | ## 0.2.0 196 | 197 | * Calling `super` inside fo `#index` and `#show` actions is now deprecated. 198 | Since these are methods that are meant to be called, they have been renamed to `#render_collection` and `#render_instance`. 199 | 200 | [Ben A. Morgan](https://github.com/BenMorganIO) 201 | 202 | * Added support for [Solidus](https://github.com/solidusio/solidus). 203 | 204 | [Ben A. Morgan](https://github.com/BenMorganIO) 205 | 206 | * In some instances, you would like to use the helper methods provided by the Spree V2 API, but unable to extend from the controller. 207 | The render methods have been moved to a concern. 208 | If you would like to include them in a controller without having to extend from the `Spree::Api::V2::BaseController`, then: 209 | 210 | ```ruby 211 | module Api 212 | class MyController < Api::BaseController 213 | # include the concern 214 | include Spree::Api::V2::Renderable 215 | 216 | # example usage of rendering all products without calling 217 | # +render_instance+ but providing page detail information that conforms 218 | # to the JSON API spec. 219 | def index 220 | render json: @products, meta: { page: page_details(@products) } 221 | end 222 | end 223 | end 224 | ``` 225 | 226 | [Ben A. Morgan](https://github.com/BenMorganIO) 227 | 228 | ## 0.1.0 229 | 230 | Initial Release 231 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'solidus_api', github: 'solidusio/solidus' 4 | gem 'solidus_auth_devise', github: 'solidusio/solidus_auth_devise' 5 | 6 | gemspec 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Ben A. Morgan 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name Solidus nor the names of its contributors may be used to 13 | endorse or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidus JSON API 2 | 3 | [![Circle CI](https://circleci.com/gh/wildcardlabs/solidus_json_api.svg?style=shield&circle-token=e2f6283b074ca9febcafab729d466ded2334300c)](https://circleci.com/gh/wildcardlabs/solidus_json_api) 4 | [![Coverage Status](https://coveralls.io/repos/github/wildcardlabs/solidus_json_api/badge.svg?branch=master)](https://coveralls.io/github/wildcardlabs/solidus_json_api?branch=master) 5 | 6 | A JSON API for Solidus. 7 | 8 | Documentation: [http://solidusapi.wildcardlabs.com](http://solidusapi.wildcardlabs.com) 9 | 10 | ## Installation 11 | 12 | Add solidus_json_api to your Gemfile: 13 | 14 | ```ruby 15 | gem 'solidus_json_api', github: 'wildcardlabs/solidus_json_api' 16 | ``` 17 | 18 | ## Road to 1.0 19 | 20 | *** 21 | Please note that there actually has been a _lot_ of work done. 22 | The lack of checked off boxes doesn't give this gem the respect it deserves. 23 | *** 24 | 25 | There's several things blocking 1.0. 26 | Below, you'll see a bullet list of requirements that are both specific to the app, the JSON API spec, and third parties. 27 | 28 | - [ ] Manage permissions with CanCanCan. 29 | - [ ] Add support for XML. 30 | - [x] Add support for pagination. 31 | - [x] `page` keyword support for pagination. 32 | - [x] `links` document at the root for `self`, `next`, `prev`, `last`, and `first`. 33 | - [ ] Add support for filtration. 34 | - [x] Be able to filter onto the main data object. 35 | - [ ] Be able to filter the included relationships. 36 | - [ ] Provide JSON API specific error objects. Provide support for: 37 | - [ ] `id` 38 | - [ ] `links` 39 | - [ ] `about` 40 | - [x] `status` 41 | - [x] `code` 42 | - [x] `title` 43 | - [x] `detail` 44 | - [ ] `source` 45 | - [x] `pointer` 46 | - [ ] `parameter` 47 | - [x] `meta` 48 | - [ ] Only accept data in the JSON API format. 49 | - [ ] Respond with header `Content-Type: application/vnd.api+json`. 50 | - [ ] Add support for sparse fieldsets. 51 | - [ ] Add support for sorting. 52 | 53 | | Resource | index | create | show | update | destroy | 54 | |------------------|:-------:|:-------:|:-------:|:-------:|:-------:| 55 | | Adjustment | [ ] | [ ] | [ ] | [ ] | [ ] | 56 | | Address | [ ] | [ ] | [ ] | [ ] | [ ] | 57 | | Checkout | [ ] | [ ] | [ ] | [ ] | [ ] | 58 | | Country | [x] | [ ] | [x] | [ ] | [ ] | 59 | | Image | [x] | [ ] | [x] | [ ] | [ ] | 60 | | Line Item | [ ] | [x] | [ ] | [ ] | [ ] | 61 | | Option Type | [x] | [ ] | [x] | [ ] | [ ] | 62 | | Option Value | [x] | [ ] | [x] | [ ] | [ ] | 63 | | Order | [x] | [ ] | [x] | [ ] | [ ] | 64 | | Payment | [ ] | [ ] | [ ] | [ ] | [ ] | 65 | | Price | [x] | [ ] | [x] | [ ] | [ ] | 66 | | Product | [x] | [ ] | [x] | [ ] | [ ] | 67 | | Product Property | [ ] | [ ] | [ ] | [ ] | [ ] | 68 | | Return Auth | [ ] | [ ] | [ ] | [ ] | [ ] | 69 | | Shipment | [ ] | [ ] | [ ] | [ ] | [ ] | 70 | | State | [x] | [ ] | [x] | [ ] | [ ] | 71 | | Stock Location | [ ] | [ ] | [ ] | [ ] | [ ] | 72 | | Stock Movement | [ ] | [ ] | [ ] | [ ] | [ ] | 73 | | Stock Item | [ ] | [ ] | [ ] | [ ] | [ ] | 74 | | Store | [ ] | [ ] | [ ] | [ ] | [ ] | 75 | | Store Credit | [ ] | [ ] | [ ] | [ ] | [ ] | 76 | | Tax Category | [ ] | [ ] | [ ] | [ ] | [ ] | 77 | | Tax Rate | [ ] | [ ] | [ ] | [ ] | [ ] | 78 | | Taxon | [x] | [ ] | [x] | [ ] | [ ] | 79 | | Taxonomy | [x] | [ ] | [x] | [ ] | [ ] | 80 | | User | [x] | [ ] | [x] | [ ] | [ ] | 81 | | Variant | [x] | [ ] | [x] | [ ] | [ ] | 82 | | Zone | [ ] | [ ] | [ ] | [ ] | [ ] | 83 | 84 | ## Testing 85 | 86 | First bundle your dependencies, then run `rake`. `rake` will default to building the dummy app if it does not exist, then it will run the specs. 87 | The dummy app can be regenerated by using `rake test_app`. 88 | 89 | ```shell 90 | bundle 91 | bundle exec rake 92 | ``` 93 | 94 | Copyright © 2015 Ben A. Morgan, released under the New BSD License 95 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | 3 | Bundler::GemHelper.install_tasks 4 | 5 | require 'rspec/core/rake_task' 6 | 7 | require 'generators/spree/dummy/dummy_generator' 8 | require 'generators/spree/install/install_generator' 9 | 10 | RSpec::Core::RakeTask.new 11 | 12 | task :default do 13 | if Dir['spec/dummy'].empty? 14 | Rake::Task[:test_app].invoke 15 | Dir.chdir('../../') 16 | end 17 | Rake::Task[:spec].invoke 18 | end 19 | 20 | desc 'Generates a dummy app for testing' 21 | task :test_app do 22 | ENV["RAILS_ENV"] = 'test' 23 | 24 | Spree::DummyGeneratorHelper.inject_extension_requirements = true 25 | Spree::DummyGenerator.start %w(--lib_name=solidus_json_api --quiet) 26 | 27 | Spree::InstallGenerator.class_eval do 28 | def config_spree_yml() end 29 | end 30 | 31 | Spree::InstallGenerator.start %w( 32 | --lib_name=solidus_json_api --auto-accept --migrate=false --seed=false 33 | --sample=false --quiet --user_class=Spree::LegacyUser 34 | ) 35 | 36 | puts 'Setting up dummy database...' 37 | system "bundle exec rake db:drop db:create db:migrate > #{File::NULL}" 38 | end 39 | -------------------------------------------------------------------------------- /app/controllers/concerns/spree/api/v2/renderable.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | module Renderable 5 | extend ActiveSupport::Concern 6 | 7 | protected 8 | 9 | def render_collection(collection = []) 10 | collection = collection.where(filter_params).paginate(page_params) 11 | render json: collection, include: params[:include], 12 | meta: { page: page_details(collection) } 13 | end 14 | 15 | def render_instance(object = {}) 16 | render json: object, include: params[:include] 17 | end 18 | 19 | def filter_params 20 | params.fetch(:filter, {}).permit(*filter_attributes).transform_values do |value| 21 | value.respond_to?(:each) ? value : value.split(',') 22 | end 23 | end 24 | 25 | private 26 | 27 | def render_error(resource, options = {}) 28 | response.status = options.delete(:status) || 400 29 | options[:response] = response 30 | render json: error_response(resource, options) 31 | end 32 | 33 | def error_response(resource, options = {}) 34 | Spree::ErrorSerializer.new(resource, options).as_json 35 | end 36 | 37 | def filter_attributes 38 | attributes = serializer_attributes 39 | attributes << :id unless attributes.include?(:id) 40 | attributes.map { |a| [{ a => [] }, a] }.flatten 41 | end 42 | 43 | def page_details(collection) 44 | { 45 | total_items: collection.total_count, 46 | total_pages: collection.total_pages, 47 | number: (page_params[:number] || 1).to_i, 48 | size: (page_params[:size] || Kaminari.config.default_per_page).to_i 49 | } 50 | end 51 | 52 | def page_params 53 | params.fetch(:page, {}).permit(:number, :size) 54 | end 55 | 56 | def serializer_attributes 57 | serializer = "Spree::#{controller_name.camelize.singularize}Serializer" 58 | serializer.constantize._attributes 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/base_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class BaseController < Spree::Api::BaseController 5 | include Spree::Api::V2::Renderable 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/children_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class ChildrenController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user, only: [:index, :show] 6 | 7 | def index 8 | render_collection children.includes(:products, :taxonomy, :children, :parent) 9 | end 10 | 11 | def show 12 | render_instance children.find(params[:id]) 13 | end 14 | 15 | private 16 | 17 | def children 18 | if params[:taxon_id] 19 | Spree::Taxon.find(params[:taxon_id]) 20 | end.children 21 | end 22 | 23 | def filter_attributes 24 | Spree::TaxonSerializer._attributes 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/countries_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class CountriesController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user, only: [:index, :show] 6 | 7 | def index 8 | render_collection Spree::Country.includes(:states) 9 | end 10 | 11 | def show 12 | if params[:state_id].present? 13 | render_instance Spree::State.find_country(params[:state_id]) 14 | else 15 | render_instance Spree::Country.find(params[:id]) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/images_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class ImagesController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user, only: [:index, :show] 6 | 7 | def index 8 | render_collection images.includes(:viewable) 9 | end 10 | 11 | def show 12 | render_instance images.find(params[:id]) 13 | end 14 | 15 | private 16 | 17 | def images 18 | if params[:product_id] 19 | Spree::Product.find(params[:product_id]).images 20 | elsif params[:variant_id] 21 | Spree::Variant.find(params[:variant_id]).images 22 | else 23 | Spree::Image 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/line_items_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class LineItemsController < Spree::Api::V2::BaseController 5 | rescue_from ActiveRecord::RecordNotFound, with: :render_record_not_found_error 6 | rescue_from Spree::Order::InsufficientStock, with: :render_insufficient_stock_error 7 | rescue_from RangeError, with: :render_range_error 8 | 9 | def create 10 | variant = Spree::Variant.find(line_item_params[:variant_id]) 11 | order = @current_api_user.orders.find(line_item_params[:order_id]) 12 | line_item = order.contents.add(variant, line_item_params[:quantity]) 13 | render_instance line_item 14 | end 15 | 16 | private 17 | 18 | def line_item_params 19 | params.require(:data).require(:attributes).permit(:variant_id, :order_id, :quantity) 20 | end 21 | 22 | def render_range_error 23 | render_error :quantity_too_high 24 | end 25 | 26 | def render_insufficient_stock_error 27 | render_error :product_out_of_stock 28 | end 29 | 30 | def render_record_not_found_error 31 | render_error :record_not_found 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/option_types_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class OptionTypesController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user, only: [:index, :show] 6 | 7 | def index 8 | render_collection option_types.includes(:option_values, :products) 9 | end 10 | 11 | def show 12 | if params[:option_value_id] 13 | render_instance Spree::OptionValue.find(params[:option_value_id]).option_type 14 | else 15 | render_instance option_types.find(params[:id]) 16 | end 17 | end 18 | 19 | private 20 | 21 | def option_types 22 | if params[:product_id] 23 | Spree::Product.find(params[:product_id]).option_types 24 | else 25 | Spree::OptionType 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/option_values_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class OptionValuesController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user, only: [:index, :show] 6 | 7 | def index 8 | render_collection option_values.includes(:option_type, :variants) 9 | end 10 | 11 | def show 12 | render_instance option_values.find(params[:id]) 13 | end 14 | 15 | private 16 | 17 | def option_values 18 | if params[:option_type_id] 19 | Spree::OptionType.find(params[:option_type_id]).option_values 20 | elsif params[:variant_id] 21 | Spree::Variant.find(params[:variant_id]).option_values 22 | else 23 | Spree::OptionValue 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/orders_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class OrdersController < Spree::Api::V2::BaseController 5 | def index 6 | render_collection orders 7 | end 8 | 9 | def show 10 | render_instance orders.find(params[:id]) 11 | end 12 | 13 | private 14 | 15 | def orders 16 | @current_api_user.admin? ? Spree::Order : @current_api_user.orders 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/prices_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class PricesController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user 6 | 7 | def index 8 | render_collection params[:variant_id] ? prices : prices.includes(:variant) 9 | end 10 | 11 | def show 12 | render_instance prices.find(params[:id]) 13 | end 14 | 15 | private 16 | 17 | def prices 18 | if params[:variant_id] 19 | Spree::Variant.find(params[:variant_id]).prices 20 | else 21 | Spree::Price 22 | end 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/products_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class ProductsController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user, only: [:index, :show] 6 | 7 | def index 8 | render_collection products.includes :variants, :master, :taxons, :option_types, master: :images 9 | end 10 | 11 | def show 12 | if params[:price_id] 13 | render_instance Spree::Price.find(params[:price_id]).product 14 | elsif params[:variant_id] 15 | render_instance Spree::Variant.find(params[:variant_id]).product 16 | elsif params[:image_id] 17 | render_instance Spree::Image.variants.find(params[:image_id]).viewable.product 18 | else 19 | render_instance products.friendly.find(params[:id]) 20 | end 21 | end 22 | 23 | private 24 | 25 | def products 26 | if params[:taxon_id].present? 27 | Spree::Taxon.find(params[:taxon_id]).products 28 | elsif params[:option_type_id] 29 | Spree::OptionType.find(params[:option_type_id]).products 30 | else 31 | Spree::Product 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/states_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class StatesController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user 6 | 7 | def index 8 | render_collection states 9 | end 10 | 11 | def show 12 | render_instance states.find(params[:id]) 13 | end 14 | 15 | private 16 | 17 | def states 18 | @states ||= begin 19 | if params[:country_id].present? 20 | Spree::Country.find(params[:country_id]).states.includes(:country) 21 | else 22 | Spree::State.includes(:country) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/taxonomies_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class TaxonomiesController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user, only: [:index, :show] 6 | 7 | def index 8 | render_collection Spree::Taxonomy.includes(:taxons) 9 | end 10 | 11 | def show 12 | if params[:taxon_id].present? 13 | render_instance Spree::Taxon.find(params[:taxon_id]).taxonomy 14 | else 15 | render_instance Spree::Taxonomy.find(params[:id]) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/taxons_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class TaxonsController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user, only: [:index, :show] 6 | 7 | def index 8 | render_collection taxons 9 | end 10 | 11 | def show 12 | render_instance taxon 13 | end 14 | 15 | private 16 | 17 | def taxon 18 | if params[:taxon_id].present? 19 | taxons.includes(parent: taxon_associations).find(params[:taxon_id]).parent 20 | else 21 | taxons.find(params[:id]) 22 | end 23 | end 24 | 25 | def taxons 26 | if params[:taxonomy_id].present? 27 | Spree::Taxonomy.find(params[:taxonomy_id]).taxons 28 | .includes(*taxon_associations.-([:taxonomy])) 29 | else 30 | Spree::Taxon.includes(*taxon_associations) 31 | end 32 | end 33 | 34 | def taxon_associations 35 | [:products, :taxonomy, :parent] 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/controllers/spree/api/v2/variants_controller.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | module Api 3 | module V2 4 | class VariantsController < Spree::Api::V2::BaseController 5 | skip_before_action :authenticate_user 6 | 7 | def index 8 | render_collection variants.includes(:default_price, :prices, :option_values, :product, :images) 9 | end 10 | 11 | def show 12 | if params[:price_id] 13 | render_instance Spree::Price.find(params[:price_id]).variant 14 | elsif params[:image_id] 15 | render_instance Spree::Image.variants.find(params[:image_id]).viewable 16 | else 17 | render_instance variants.find(params[:id]) 18 | end 19 | end 20 | 21 | private 22 | 23 | def variants 24 | if params[:product_id] 25 | Spree::Product.find(params[:product_id]).variants_including_master 26 | elsif params[:option_value_id] 27 | Spree::OptionValue.find(params[:option_value_id]).variants 28 | else 29 | Spree::Variant 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/models/spree/base_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::Base.class_eval do 2 | def self.paginate(page_attrs) 3 | page(page_attrs[:number]).per(page_attrs[:size]) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/spree/image_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::Image.class_eval do 2 | scope :variants, -> { where(viewable_type: 'Spree::Variant') } 3 | end 4 | -------------------------------------------------------------------------------- /app/models/spree/line_item_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::LineItem.class_eval do 2 | before_save -> { order.send :validate_line_item_availability } 3 | end 4 | -------------------------------------------------------------------------------- /app/models/spree/order_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::Order.class_eval do 2 | money_methods :promo_total 3 | end 4 | -------------------------------------------------------------------------------- /app/models/spree/price_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::Price.class_eval do 2 | delegate :product, to: :variant 3 | end 4 | -------------------------------------------------------------------------------- /app/models/spree/state_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::State.class_eval do 2 | scope :find_country, -> (id) { includes(:country).find(id).country } 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/spree/address_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class AddressSerializer < Spree::BaseSerializer 3 | attributes :first_name, :last_name, :address1, :address2, :city, :zipcode, 4 | :phone 5 | 6 | belongs_to :state 7 | belongs_to :country 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/serializers/spree/base_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class BaseSerializer < SolidusJsonApi.parent_serializer 3 | def image_links(object) 4 | { 5 | original: object.url(:original), 6 | mini: object.url(:mini), 7 | small: object.url(:small), 8 | product: object.url(:product), 9 | large: object.url(:large) 10 | } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/serializers/spree/country_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class CountrySerializer < Spree::BaseSerializer 3 | attributes :iso_name, :iso, :iso3, :name, :numcode, :states_required 4 | 5 | has_many :states 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/serializers/spree/error_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class ErrorSerializer 3 | attr_accessor :resource, :options, :response 4 | 5 | def initialize(resource, options = {}) 6 | @resource = resource 7 | @response = options.delete(:response) 8 | @options = options 9 | end 10 | 11 | def as_json 12 | { errors: error_messages }.to_json 13 | end 14 | 15 | private 16 | 17 | def resource_error_objects(messages) 18 | messages.map do |title, detail| 19 | pointer = "data/attributes/#{title.to_s.split('.').join('/')}" 20 | error_object title.to_s.gsub('.', ' '), detail.to_sentence, pointer: pointer 21 | end 22 | end 23 | 24 | def error_messages 25 | case resource 26 | when Symbol 27 | title_translation = Spree.t("api.errors.#{resource}.title") 28 | detail_translation = Spree.t("api.errors.#{resource}.detail") 29 | [error_object(title_translation, detail_translation)] 30 | when Array 31 | resource.map { |errors| resource_error_objects errors }.flatten 32 | else 33 | resource_error_objects resource.errors.messages 34 | end 35 | end 36 | 37 | def error_object(title, detail, pointer: '') 38 | error = { 39 | detail: detail.gsub("\n", ' ').strip, 40 | meta: options, 41 | title: title.to_s.titleize 42 | } 43 | 44 | error.merge!(response_attributes) if response.present? 45 | pointer.present? ? error.merge(pointer: pointer) : error 46 | end 47 | 48 | def response_attributes 49 | { 50 | status: response.status_message, 51 | code: response.code 52 | } 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/serializers/spree/image_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class ImageSerializer < Spree::BaseSerializer 3 | attributes :position, :alt, :links 4 | 5 | belongs_to :viewable 6 | 7 | def links 8 | image_links object.attachment 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/serializers/spree/line_item_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class LineItemSerializer < Spree::BaseSerializer 3 | attributes :id, :quantity, :variant_id, :order_id, :currency, :cost_price, 4 | :adjustment_total, :additional_tax_total, :price 5 | 6 | %w(amount total).each do |money_method| 7 | attributes money_method, "display_#{money_method}" 8 | end 9 | 10 | belongs_to :order 11 | belongs_to :variant 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/serializers/spree/option_type_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class OptionTypeSerializer < Spree::BaseSerializer 3 | attributes :name, :presentation, :position 4 | 5 | has_many :option_values 6 | has_many :products 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/serializers/spree/option_value_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class OptionValueSerializer < Spree::BaseSerializer 3 | attributes :name, :presentation, :position 4 | 5 | belongs_to :option_type 6 | has_many :variants 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/serializers/spree/order_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class OrderSerializer < Spree::BaseSerializer 3 | attributes :email, :number, :state, :completed_at, :shipment_state, 4 | :payment_state, :special_instructions, :currency, :channel, 5 | :item_count, :approved_at, :confirmation_delivered, :canceled_at, 6 | :payment_total, :total, :display_total 7 | 8 | %w( 9 | item adjustment shipment promo additional_tax included_tax 10 | ).each do |money_method| 11 | attributes "#{money_method}_total", "display_#{money_method}_total" 12 | end 13 | 14 | has_one :user 15 | has_one :bill_address 16 | has_one :ship_address 17 | 18 | has_many :line_items 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/serializers/spree/price_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class PriceSerializer < Spree::BaseSerializer 3 | attributes :amount, :price, :display_amount, :display_price, :currency 4 | 5 | belongs_to :variant 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/serializers/spree/product_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class ProductSerializer < Spree::BaseSerializer 3 | attributes :name, :description, :slug, :meta_description, :meta_keywords 4 | 5 | has_one :master 6 | 7 | has_many :variants 8 | has_many :taxons 9 | has_many :option_types 10 | has_many :images 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/serializers/spree/role_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class RoleSerializer < Spree::BaseSerializer 3 | attributes :name 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/serializers/spree/state_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class StateSerializer < Spree::BaseSerializer 3 | attributes :name, :abbr 4 | 5 | belongs_to :country 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/serializers/spree/store_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class StoreSerializer < Spree::BaseSerializer 3 | attributes :name, :url, :meta_description, :meta_keywords, :seo_title, 4 | :mail_from_address, :default_currency, :code, :default 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/serializers/spree/taxon_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class TaxonSerializer < Spree::BaseSerializer 3 | attributes :name, :permalink, :position, :description, :meta_title, :links, 4 | :meta_description, :meta_keywords, :depth 5 | 6 | belongs_to :taxonomy 7 | belongs_to :parent, serializer: TaxonSerializer 8 | has_many :children, serializer: TaxonSerializer 9 | 10 | def links 11 | image_links object.icon 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/serializers/spree/taxonomy_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class TaxonomySerializer < Spree::BaseSerializer 3 | attributes :name, :position 4 | 5 | has_many :taxons 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/serializers/spree/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class UserSerializer < Spree::BaseSerializer 3 | attributes :id, :email 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/serializers/spree/variant_serializer.rb: -------------------------------------------------------------------------------- 1 | module Spree 2 | class VariantSerializer < Spree::BaseSerializer 3 | attributes :sku, :weight, :height, :width, :depth, :is_master, :position, 4 | :name, :price, :display_price 5 | 6 | has_many :prices 7 | has_many :option_values 8 | has_many :images 9 | 10 | belongs_to :product 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | --- 2 | machine: 3 | environment: 4 | DB: postgres 5 | services: 6 | - postgresql 7 | ruby: 8 | version: 2.3.0 9 | 10 | test: 11 | override: 12 | - bundle exec rake 13 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | spree: 3 | api: 4 | errors: 5 | invalid_token: 6 | detail: 'Your token is invalid, please use a different one.' 7 | title: Invalid Token 8 | order_complete: 9 | detail: This order has been completed. 10 | title: Order Complete 11 | product_out_of_stock: 12 | title: Product is out of Stock 13 | detail: > 14 | This product is out of stock for the selected quantity. 15 | record_not_found: 16 | title: Record Not Found 17 | detail: > 18 | One of the records that you were looking for could not be found. 19 | Please check to see if the record exists or if you're permitted to 20 | read it. 21 | quantity_too_high: 22 | title: Quantity is too High 23 | detail: > 24 | The quantity that you have submitted is astronomically high, please 25 | tone it down a bit. 26 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Spree::Core::Engine.routes.draw do 2 | namespace :api, defaults: { format: :json } do 3 | namespace :v2 do 4 | resources :countries, only: [:index, :show] do 5 | resources :states, only: [:index, :show] 6 | end 7 | 8 | resources :images, only: [:index, :show] do 9 | resource :product, :variant, only: :show 10 | end 11 | 12 | resources :line_items, only: :create 13 | 14 | resources :option_types, only: [:index, :show] do 15 | resources :option_values, :products, only: [:index, :show] 16 | end 17 | 18 | resources :option_values, only: [:index, :show] do 19 | resource :option_type, only: :show 20 | resources :variants, only: [:index, :show] 21 | end 22 | 23 | resources :orders, only: [:index, :show] 24 | 25 | resources :prices, only: [:index, :show] do 26 | resource :variant, :product, only: :show 27 | end 28 | 29 | resources :products, only: [:index, :show] do 30 | resources :option_types, :variants, :images, only: [:index, :show] 31 | end 32 | 33 | resources :states, only: [:index, :show] do 34 | resource :country, only: :show 35 | end 36 | 37 | resources :taxonomies, only: [:index, :show] do 38 | resources :taxons, only: [:index, :show] 39 | end 40 | 41 | resources :taxons, only: [:index, :show] do 42 | resources :children, :products, only: [:index, :show] 43 | resource :parent, only: :show, controller: 'taxons' 44 | resource :taxonomy, only: :show 45 | end 46 | 47 | resources :variants, only: [:index, :show] do 48 | resources :prices, :option_values, :images, only: [:index, :show] 49 | resource :product, only: :show 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/a6f2379df1c05d6b31097462789287b096b54982/docs/.nojekyll -------------------------------------------------------------------------------- /docs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | 3 | RUN apt-get update 4 | RUN apt-get install -yq ruby ruby-dev build-essential git 5 | RUN gem install --no-ri --no-rdoc bundler 6 | ADD Gemfile /app/Gemfile 7 | ADD Gemfile.lock /app/Gemfile.lock 8 | RUN cd /app; bundle install 9 | ADD . /app 10 | EXPOSE 4567 11 | WORKDIR /app 12 | CMD ["bundle", "exec", "middleman", "server"] 13 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '2.2.3' 3 | 4 | # Middleman 5 | gem 'middleman', '~> 3.3.10' 6 | gem 'middleman-gh-pages', '~> 0.0.3' 7 | gem 'middleman-syntax', '~> 2.0.0' 8 | gem 'middleman-autoprefixer', '~> 2.4.4' 9 | 10 | gem 'rouge', '~> 1.9.0' 11 | gem 'redcarpet', '~> 3.3.2' 12 | 13 | gem 'rake', '~> 10.4.2' 14 | gem 'therubyracer', '~> 0.12.2', platforms: :ruby 15 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation for the Solidus JSON API 2 | 3 | The documentation is hand written and forked from [Slate](https://github.com/tripit/slate). 4 | Please update these docs when you make a contribution. 5 | 6 | ## Setup 7 | 8 | ```shell 9 | cd docs 10 | bundle 11 | middleman server 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/Rakefile: -------------------------------------------------------------------------------- 1 | ENV['MM_ROOT'] = File.expand_path '.' 2 | ENV['BUNDLE_GEMFILE'] = File.expand_path './Gemfile' 3 | 4 | require 'middleman-gh-pages' 5 | require 'rake/clean' 6 | 7 | CLOBBER.include('build') 8 | 9 | task default: [:build] 10 | -------------------------------------------------------------------------------- /docs/config.rb: -------------------------------------------------------------------------------- 1 | PROJECT_ROOT = File.expand_path '.' 2 | BUILD_DIR = File.expand_path './../build' 3 | 4 | # Markdown 5 | set :markdown_engine, :redcarpet 6 | set :markdown, 7 | fenced_code_blocks: true, 8 | smartypants: true, 9 | disable_indented_code_blocks: true, 10 | prettify: true, 11 | tables: true, 12 | with_toc_data: true, 13 | no_intra_emphasis: true 14 | 15 | # Assets 16 | set :css_dir, 'stylesheets' 17 | set :js_dir, 'javascripts' 18 | set :images_dir, 'images' 19 | set :fonts_dir, 'fonts' 20 | 21 | # Activate the syntax highlighter 22 | activate :syntax 23 | 24 | activate :autoprefixer do |config| 25 | config.browsers = ['last 2 version', 'Firefox ESR'] 26 | config.cascade = false 27 | config.inline = true 28 | end 29 | 30 | # Github pages require relative links 31 | activate :relative_assets 32 | set :relative_links, true 33 | 34 | # Build Configuration 35 | set :build_dir, BUILD_DIR 36 | configure :build do 37 | activate :minify_css 38 | activate :minify_javascript 39 | end 40 | -------------------------------------------------------------------------------- /docs/font-selection.json: -------------------------------------------------------------------------------- 1 | { 2 | "IcoMoonType": "selection", 3 | "icons": [ 4 | { 5 | "icon": { 6 | "paths": [ 7 | "M438.857 73.143q119.429 0 220.286 58.857t159.714 159.714 58.857 220.286-58.857 220.286-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857zM512 785.714v-108.571q0-8-5.143-13.429t-12.571-5.429h-109.714q-7.429 0-13.143 5.714t-5.714 13.143v108.571q0 7.429 5.714 13.143t13.143 5.714h109.714q7.429 0 12.571-5.429t5.143-13.429zM510.857 589.143l10.286-354.857q0-6.857-5.714-10.286-5.714-4.571-13.714-4.571h-125.714q-8 0-13.714 4.571-5.714 3.429-5.714 10.286l9.714 354.857q0 5.714 5.714 10t13.714 4.286h105.714q8 0 13.429-4.286t6-10z" 8 | ], 9 | "attrs": [], 10 | "isMulticolor": false, 11 | "tags": [ 12 | "exclamation-circle" 13 | ], 14 | "defaultCode": 61546, 15 | "grid": 14 16 | }, 17 | "attrs": [], 18 | "properties": { 19 | "id": 100, 20 | "order": 4, 21 | "prevSize": 28, 22 | "code": 58880, 23 | "name": "exclamation-sign", 24 | "ligatures": "" 25 | }, 26 | "setIdx": 0, 27 | "iconIdx": 0 28 | }, 29 | { 30 | "icon": { 31 | "paths": [ 32 | "M585.143 786.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-54.857v-292.571q0-8-5.143-13.143t-13.143-5.143h-182.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h54.857v182.857h-54.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h256q8 0 13.143-5.143t5.143-13.143zM512 274.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-109.714q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h109.714q8 0 13.143-5.143t5.143-13.143zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z" 33 | ], 34 | "attrs": [], 35 | "isMulticolor": false, 36 | "tags": [ 37 | "info-circle" 38 | ], 39 | "defaultCode": 61530, 40 | "grid": 14 41 | }, 42 | "attrs": [], 43 | "properties": { 44 | "id": 85, 45 | "order": 3, 46 | "name": "info-sign", 47 | "prevSize": 28, 48 | "code": 58882 49 | }, 50 | "setIdx": 0, 51 | "iconIdx": 2 52 | }, 53 | { 54 | "icon": { 55 | "paths": [ 56 | "M733.714 419.429q0-16-10.286-26.286l-52-51.429q-10.857-10.857-25.714-10.857t-25.714 10.857l-233.143 232.571-129.143-129.143q-10.857-10.857-25.714-10.857t-25.714 10.857l-52 51.429q-10.286 10.286-10.286 26.286 0 15.429 10.286 25.714l206.857 206.857q10.857 10.857 25.714 10.857 15.429 0 26.286-10.857l310.286-310.286q10.286-10.286 10.286-25.714zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z" 57 | ], 58 | "attrs": [], 59 | "isMulticolor": false, 60 | "tags": [ 61 | "check-circle" 62 | ], 63 | "defaultCode": 61528, 64 | "grid": 14 65 | }, 66 | "attrs": [], 67 | "properties": { 68 | "id": 83, 69 | "order": 9, 70 | "prevSize": 28, 71 | "code": 58886, 72 | "name": "ok-sign" 73 | }, 74 | "setIdx": 0, 75 | "iconIdx": 6 76 | }, 77 | { 78 | "icon": { 79 | "paths": [ 80 | "M658.286 475.429q0-105.714-75.143-180.857t-180.857-75.143-180.857 75.143-75.143 180.857 75.143 180.857 180.857 75.143 180.857-75.143 75.143-180.857zM950.857 950.857q0 29.714-21.714 51.429t-51.429 21.714q-30.857 0-51.429-21.714l-196-195.429q-102.286 70.857-228 70.857-81.714 0-156.286-31.714t-128.571-85.714-85.714-128.571-31.714-156.286 31.714-156.286 85.714-128.571 128.571-85.714 156.286-31.714 156.286 31.714 128.571 85.714 85.714 128.571 31.714 156.286q0 125.714-70.857 228l196 196q21.143 21.143 21.143 51.429z" 81 | ], 82 | "width": 951, 83 | "attrs": [], 84 | "isMulticolor": false, 85 | "tags": [ 86 | "search" 87 | ], 88 | "defaultCode": 61442, 89 | "grid": 14 90 | }, 91 | "attrs": [], 92 | "properties": { 93 | "id": 2, 94 | "order": 1, 95 | "prevSize": 28, 96 | "code": 58887, 97 | "name": "icon-search" 98 | }, 99 | "setIdx": 0, 100 | "iconIdx": 7 101 | } 102 | ], 103 | "height": 1024, 104 | "metadata": { 105 | "name": "slate", 106 | "license": "SIL OFL 1.1" 107 | }, 108 | "preferences": { 109 | "showGlyphs": true, 110 | "showQuickUse": true, 111 | "showQuickUse2": true, 112 | "showSVGs": true, 113 | "fontPref": { 114 | "prefix": "icon-", 115 | "metadata": { 116 | "fontFamily": "slate", 117 | "majorVersion": 1, 118 | "minorVersion": 0, 119 | "description": "Based on FontAwesome", 120 | "license": "SIL OFL 1.1" 121 | }, 122 | "metrics": { 123 | "emSize": 1024, 124 | "baseline": 6.25, 125 | "whitespace": 50 126 | }, 127 | "resetPoint": 58880, 128 | "showSelector": false, 129 | "selector": "class", 130 | "classSelector": ".icon", 131 | "showMetrics": false, 132 | "showMetadata": true, 133 | "showVersion": true, 134 | "ie7": false 135 | }, 136 | "imagePref": { 137 | "prefix": "icon-", 138 | "png": true, 139 | "useClassSelector": true, 140 | "color": 4473924, 141 | "bgColor": 16777215 142 | }, 143 | "historySize": 100, 144 | "showCodes": true, 145 | "gridSize": 16, 146 | "showLiga": false 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /docs/source/CNAME: -------------------------------------------------------------------------------- 1 | solidusapi.wildcardlabs.com 2 | -------------------------------------------------------------------------------- /docs/source/fonts/slate.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/a6f2379df1c05d6b31097462789287b096b54982/docs/source/fonts/slate.eot -------------------------------------------------------------------------------- /docs/source/fonts/slate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/source/fonts/slate.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/a6f2379df1c05d6b31097462789287b096b54982/docs/source/fonts/slate.ttf -------------------------------------------------------------------------------- /docs/source/fonts/slate.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/a6f2379df1c05d6b31097462789287b096b54982/docs/source/fonts/slate.woff -------------------------------------------------------------------------------- /docs/source/fonts/slate.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/a6f2379df1c05d6b31097462789287b096b54982/docs/source/fonts/slate.woff2 -------------------------------------------------------------------------------- /docs/source/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/a6f2379df1c05d6b31097462789287b096b54982/docs/source/images/favicon.ico -------------------------------------------------------------------------------- /docs/source/images/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/a6f2379df1c05d6b31097462789287b096b54982/docs/source/images/navbar.png -------------------------------------------------------------------------------- /docs/source/includes/_countries.md: -------------------------------------------------------------------------------- 1 | # Countries 2 | 3 | ## List Countries 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/countries" 7 | ``` 8 | 9 | ```json 10 | { 11 | "data": [ 12 | { 13 | "id": "1", 14 | "type": "spree_countries", 15 | "attributes": { 16 | "iso_name": "ANDORRA", 17 | "iso": "AD", 18 | "iso3": "AND", 19 | "name": "Andorra", 20 | "numcode": 20, 21 | "states_required": true 22 | }, 23 | "relationships": { 24 | "states": { 25 | "data": [ 26 | { 27 | "type": "spree_states", 28 | "id": "1" 29 | } 30 | ] 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | ``` 37 | 38 | List all of the ~200 countries in the DB. 39 | 40 | ## Show Country 41 | 42 | ```shell 43 | curl "https://example.com/api/v2/countries/1" 44 | ``` 45 | 46 | ```json 47 | { 48 | "data": { 49 | "id": "1", 50 | "type": "spree_countries", 51 | "attributes": { 52 | "iso_name": "ANDORRA", 53 | "iso": "AD", 54 | "iso3": "AND", 55 | "name": "Andorra", 56 | "numcode": 20, 57 | "states_required": true 58 | }, 59 | "relationships": { 60 | "states": { 61 | "data": [ 62 | { 63 | "type": "spree_states", 64 | "id": "1" 65 | } 66 | ] 67 | } 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | Select a country via its `id`. 74 | 75 | ## List States of a Country 76 | 77 | ```shell 78 | curl "https://example.com/api/v2/countries/1/states" 79 | ``` 80 | 81 | ```json 82 | { 83 | "data": [ 84 | { 85 | "id": "1", 86 | "type": "spree_states", 87 | "attributes": { 88 | "name": "Canillo", 89 | "abbr": "02" 90 | }, 91 | "relationships": { 92 | "country": { 93 | "data": { 94 | "type": "spree_countries", 95 | "id": "1" 96 | } 97 | } 98 | } 99 | } 100 | ] 101 | } 102 | ``` 103 | 104 | See all of the states that a country owns via the country's `id`. 105 | 106 | ## Show State of a Country 107 | 108 | ```shell 109 | curl "https://example.com/api/v2/countries/1/states/1" 110 | ``` 111 | 112 | ```json 113 | { 114 | "data": { 115 | "id": "1", 116 | "type": "spree_states", 117 | "attributes": { 118 | "name": "Canillo", 119 | "abbr": "02" 120 | }, 121 | "relationships": { 122 | "country": { 123 | "data": { 124 | "type": "spree_countries", 125 | "id": "1" 126 | } 127 | } 128 | } 129 | } 130 | } 131 | ``` 132 | 133 | View one of the states that a country owns via the country's `id` and the state's `id`. 134 | -------------------------------------------------------------------------------- /docs/source/includes/_errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | The Solidus JSON API uses the following error codes: 4 | 5 | Error Code | Meaning 6 | ---------- | ------- 7 | 400 | Bad Request -- Your request sucks 8 | 401 | Unauthorized -- Your API key is wrong 9 | 403 | Forbidden -- The kitten requested is hidden for administrators only 10 | 404 | Not Found -- The specified kitten could not be found 11 | 405 | Method Not Allowed -- You tried to access a kitten with an invalid method 12 | 406 | Not Acceptable -- You requested a format that isn't json 13 | 410 | Gone -- The kitten requested has been removed from our servers 14 | 418 | I'm a teapot 15 | 429 | Too Many Requests -- You're requesting too many kittens! Slow down! 16 | 500 | Internal Server Error -- We had a problem with our server. Try again later. 17 | 503 | Service Unavailable -- We're temporarially offline for maintanance. Please try again later. 18 | -------------------------------------------------------------------------------- /docs/source/includes/_filtering.md: -------------------------------------------------------------------------------- 1 | # Filtering 2 | 3 | ```shell 4 | curl "https://example.com/api/v2/kittens?filter[id]=1" 5 | curl "https://example.com/api/v2/kittens?filter[id]=2,3,4" 6 | curl "https://example.com/api/v2/kittens?filter[breed]=Persian,British%20Shorthair,Bengal" 7 | ``` 8 | 9 | This project supports the [JSON API's `filter` keyword](http://jsonapi.org/format/#fetching-filtering) with only the root object, not any of its relationships yet. 10 | You can filter any of the attributes that is inside of the data object. 11 | Please see the example requests to the side on how you my filter a kitten object. 12 | -------------------------------------------------------------------------------- /docs/source/includes/_images.md: -------------------------------------------------------------------------------- 1 | # Images 2 | 3 | ## List Images 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/images" 7 | ``` 8 | 9 | ```json 10 | { 11 | "data": [ 12 | { 13 | "id": "1", 14 | "type": "spree_images", 15 | "attributes": { 16 | "position": 1, 17 | "alt": null, 18 | "links": { 19 | "original": "/spree/products/1/original/ror_baseball_jersey_red.png?1442035822", 20 | "mini": "/spree/products/1/mini/ror_baseball_jersey_red.png?1442035822", 21 | "small": "/spree/products/1/small/ror_baseball_jersey_red.png?1442035822", 22 | "product": "/spree/products/1/product/ror_baseball_jersey_red.png?1442035822", 23 | "large": "/spree/products/1/large/ror_baseball_jersey_red.png?1442035822" 24 | } 25 | }, 26 | "relationships": { 27 | "viewable": { 28 | "data": { 29 | "type": "spree_variants", 30 | "id": "1" 31 | } 32 | } 33 | } 34 | } 35 | ] 36 | } 37 | ``` 38 | 39 | List all of the images stored in the database. 40 | 41 | ## Show Image 42 | 43 | ```shell 44 | curl "https://example.com/api/v2/images/1" 45 | ``` 46 | 47 | ```json 48 | { 49 | "data": { 50 | "id": "1", 51 | "type": "spree_images", 52 | "attributes": { 53 | "position": 1, 54 | "alt": null, 55 | "links": { 56 | "original": "/spree/products/1/original/ror_baseball_jersey_red.png?1442035822", 57 | "mini": "/spree/products/1/mini/ror_baseball_jersey_red.png?1442035822", 58 | "small": "/spree/products/1/small/ror_baseball_jersey_red.png?1442035822", 59 | "product": "/spree/products/1/product/ror_baseball_jersey_red.png?1442035822", 60 | "large": "/spree/products/1/large/ror_baseball_jersey_red.png?1442035822" 61 | } 62 | }, 63 | "relationships": { 64 | "viewable": { 65 | "data": { 66 | "type": "spree_variants", 67 | "id": "1" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | Fetch an image and its links via the image's `id`. 76 | 77 | ## Show Variant of an Image 78 | 79 | ```shell 80 | curl "https://example.com/api/v2/images/1/variant" 81 | ``` 82 | 83 | ```json 84 | { 85 | "data": { 86 | "id": "17", 87 | "type": "spree_variants", 88 | "attributes": { 89 | "sku": "ROR-00001", 90 | "weight": "0.0", 91 | "height": null, 92 | "width": null, 93 | "depth": null, 94 | "is_master": false, 95 | "position": 2, 96 | "name": "Ruby on Rails Baseball Jersey", 97 | "price": null, 98 | "display_price": "$0.00 CAD" 99 | }, 100 | "relationships": { 101 | "prices": { 102 | "data": [ 103 | { 104 | "type": "spree_prices", 105 | "id": "33" 106 | } 107 | ] 108 | }, 109 | "option_values": { 110 | "data": [ 111 | { 112 | "type": "spree_option_values", 113 | "id": "1" 114 | } 115 | ] 116 | }, 117 | "images": { 118 | "data": [ 119 | { 120 | "type": "spree_images", 121 | "id": "1" 122 | } 123 | ] 124 | }, 125 | "product": { 126 | "data": { 127 | "type": "spree_products", 128 | "id": "3" 129 | } 130 | } 131 | } 132 | } 133 | } 134 | ``` 135 | 136 | Fetch the variant that an image belongs to via the variant's `id`. 137 | 138 | ## Show Product of an Image 139 | 140 | ```shell 141 | curl "https://example.com/api/v2/images/1/product" 142 | ``` 143 | 144 | ```json 145 | { 146 | "data": { 147 | "id": "3", 148 | "type": "spree_products", 149 | "attributes": { 150 | "name": "Ruby on Rails Baseball Jersey", 151 | "description": "Dolorem molestias sint maxime id at rem qui exercitationem. Neque voluptas corrupti magni suscipit iusto voluptatum. Ea quibusdam dolorem inventore praesentium sed dicta eveniet et. Rerum inventore laudantium quisquam earum consequatur dignissimos.", 152 | "slug": "ruby-on-rails-baseball-jersey", 153 | "meta_description": null, 154 | "meta_keywords": null, 155 | "store_name": "Whole New Home" 156 | }, 157 | "relationships": { 158 | "master": { 159 | "data": { 160 | "type": "spree_variants", 161 | "id": "3" 162 | } 163 | }, 164 | "variants": { 165 | "data": [ 166 | { 167 | "type": "spree_variants", 168 | "id": "1" 169 | } 170 | ] 171 | }, 172 | "taxons": { 173 | "data": [ 174 | { 175 | "type": "spree_taxons", 176 | "id": "53" 177 | } 178 | ] 179 | }, 180 | "option_types": { 181 | "data": [ 182 | { 183 | "type": "spree_option_types", 184 | "id": "1" 185 | } 186 | ] 187 | }, 188 | "images": { 189 | "data": [ 190 | { 191 | "type": "spree_images", 192 | "id": "24" 193 | } 194 | ] 195 | } 196 | } 197 | } 198 | } 199 | ``` 200 | 201 | Find the product that image belongs to via the image's `id`. 202 | -------------------------------------------------------------------------------- /docs/source/includes/_line_items.md: -------------------------------------------------------------------------------- 1 | # Line Items 2 | 3 | ## Create Line Item 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/line_items" 7 | -X POST 8 | -d token=abc123 9 | -d data[attributes][order_id]=1 10 | -d data[attributes][variant_id]=1 11 | -d data[attributes][quantity]=1 12 | ``` 13 | 14 | ```json 15 | { 16 | "data": { 17 | "attributes": { 18 | "additional_tax_total": "0", 19 | "adjustment_total": "0.0", 20 | "amount": "10.0", 21 | "cost_price": "17.0", 22 | "currency": "CAD", 23 | "display_amount": "$10.00 CAD", 24 | "display_price": "$10.00 CAD", 25 | "display_total": "$10.00 CAD", 26 | "order_id": 1, 27 | "price": "10.0", 28 | "quantity": 1, 29 | "total": "10.0", 30 | "variant_id": 1 31 | }, 32 | "relationships": { 33 | "order": { 34 | "data": { 35 | "type": "spree_orders" 36 | } 37 | }, 38 | "variant": { 39 | "data": { 40 | "type": "spree_variants" 41 | } 42 | } 43 | }, 44 | "type": "spree_line_items" 45 | } 46 | } 47 | ``` 48 | 49 | This endpoint allows you add a variant to a users order by creating a line item. 50 | 51 | ### When Out of Range 52 | 53 | > Quantity is out of range. 54 | 55 | ```shell 56 | curl "https://example.com/api/v2/line_items" 57 | -X POST 58 | -d token=abc123 59 | -d data[attributes][order_id]=1 60 | -d data[attributes][variant_id]=1 61 | -d data[attributes][quantity]=100000000000 62 | ``` 63 | 64 | ```json 65 | { 66 | "errors": [ 67 | { 68 | "code": "400", 69 | "detail": "Quantity is too High", 70 | "meta": {}, 71 | "status": "Bad Request", 72 | "title": "The quantity that you have submitted is astronomically high, please tone it down a bit." 73 | } 74 | ] 75 | } 76 | ``` 77 | 78 | When requesting an insanely large amount of variants to be added to your order, this will result in an error. 79 | 80 | ### When a Variant or an Order Could Not be Found 81 | 82 | > Order could not be found. 83 | 84 | ```shell 85 | curl "https://example.com/api/v2/line_items" 86 | -X POST 87 | -d token=abc123 88 | -d data[attributes][order_id]=0 89 | -d data[attributes][variant_id]=1 90 | -d data[attributes][quantity]=1 91 | ``` 92 | 93 | ```json 94 | { 95 | "errors": [ 96 | { 97 | "code": "400", 98 | "detail": "Record Not Found", 99 | "meta": {}, 100 | "status": "Bad Request", 101 | "title": "One of the records that you were looking for could not be found. Please check to see if the record exists or if you're permitted to read it" 102 | } 103 | ] 104 | } 105 | ``` 106 | 107 | Sometimes, you'll submit a bad variant id or order id. 108 | When this happens, you'll receive an error because of it. 109 | 110 | ### When the Product is Out of Stock 111 | 112 | > Product is out of stock. 113 | 114 | ```shell 115 | curl "https://example.com/api/v2/line_items" 116 | -X POST 117 | -d token=abc123 118 | -d data[attributes][order_id]=1 119 | -d data[attributes][variant_id]=1 120 | -d data[attributes][quantity]=1 121 | ``` 122 | 123 | ```json 124 | { 125 | "errors": [ 126 | { 127 | "code": "400", 128 | "detail": "Product is out of Stock", 129 | "meta": {}, 130 | "status": "Bad Request", 131 | "title": "This product is out of stock for the selected quantity." 132 | } 133 | ] 134 | } 135 | ``` 136 | 137 | Sometimes, there's just not going to be any left in stock. 138 | This can happen for both two reasons: 139 | 140 | 1. The variant is tracking inventory and its stock items `count_on_hand`s have all reached 0. 141 | 2. The variant is not backorderable. 142 | 143 | If both of these conditions are met, then you will reveice an out of stock error. 144 | -------------------------------------------------------------------------------- /docs/source/includes/_option_types.md: -------------------------------------------------------------------------------- 1 | # Option Types 2 | 3 | ## List Option Types 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/option_types" 7 | ``` 8 | 9 | ```json 10 | { 11 | "data": [ 12 | { 13 | "id": "1", 14 | "type": "spree_option_types", 15 | "attributes": { 16 | "name": "tshirt-size", 17 | "presentation": "Size", 18 | "position": 1 19 | }, 20 | "relationships": { 21 | "option_values": { 22 | "data": [ 23 | { 24 | "type": "spree_option_values", 25 | "id": "1" 26 | } 27 | ] 28 | }, 29 | "products": { 30 | "data": [ 31 | { 32 | "type": "spree_products", 33 | "id": "1" 34 | } 35 | ] 36 | } 37 | } 38 | } 39 | ] 40 | } 41 | ``` 42 | 43 | List all of the option types in the database. 44 | 45 | ## Show Option Type 46 | 47 | ```shell 48 | curl "https://example.com/api/v2/option_types/1" 49 | ``` 50 | 51 | ```json 52 | { 53 | "data": { 54 | "id": "1", 55 | "type": "spree_option_types", 56 | "attributes": { 57 | "name": "tshirt-size", 58 | "presentation": "Size", 59 | "position": 1 60 | }, 61 | "relationships": { 62 | "option_values": { 63 | "data": [ 64 | { 65 | "type": "spree_option_values", 66 | "id": "1" 67 | } 68 | ] 69 | }, 70 | "products": { 71 | "data": [ 72 | { 73 | "type": "spree_products", 74 | "id": "1" 75 | } 76 | ] 77 | } 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | Fetch an option type in the database. 84 | 85 | ## List Option Values of a Option Type 86 | 87 | ```shell 88 | curl "https://example.com/api/v2/option_types/1/option_values" 89 | ``` 90 | 91 | ```json 92 | { 93 | "data": [ 94 | { 95 | "id": "1", 96 | "type": "spree_option_values", 97 | "attributes": { 98 | "name": "Small", 99 | "presentation": "S", 100 | "position": 1 101 | }, 102 | "relationships": { 103 | "option_type": { 104 | "data": { 105 | "type": "spree_option_types", 106 | "id": "1" 107 | } 108 | } 109 | } 110 | } 111 | ] 112 | } 113 | ``` 114 | 115 | See all of the option values that an option type owns. 116 | 117 | ## Show Option Values of a Option Type 118 | 119 | ```shell 120 | curl "https://example.com/api/v2/option_types/1/option_values/1" 121 | ``` 122 | 123 | ```json 124 | { 125 | "data": { 126 | "id": "1", 127 | "type": "spree_option_values", 128 | "attributes": { 129 | "name": "Small", 130 | "presentation": "S", 131 | "position": 1 132 | }, 133 | "relationships": { 134 | "option_type": { 135 | "data": { 136 | "type": "spree_option_types", 137 | "id": "1" 138 | } 139 | } 140 | } 141 | } 142 | } 143 | ``` 144 | 145 | Fetch an option value that an option type owns. 146 | 147 | ## List Products of a Option Type 148 | 149 | ```shell 150 | curl "https://example.com/api/v2/option_types/1/products" 151 | ``` 152 | 153 | ```json 154 | { 155 | "data": [ 156 | { 157 | "id": "1", 158 | "type": "spree_products", 159 | "attributes": { 160 | "name": "Ruby on Rails Tote", 161 | "description": "Velit nemo odio ducimus nobis non doloremque beatae sunt. Totam quia voluptatum perferendis tempore sed voluptate consequuntur. Sit id corporis autem veritatis reprehenderit.", 162 | "slug": "ruby-on-rails-tote", 163 | "meta_description": null, 164 | "meta_keywords": null, 165 | "store_name": "Whole New Home" 166 | }, 167 | "relationships": { 168 | "master": { 169 | "data": { 170 | "type": "spree_variants", 171 | "id": "1" 172 | } 173 | }, 174 | "variants": { 175 | "data": [] 176 | }, 177 | "taxons": { 178 | "data": [ 179 | { 180 | "type": "spree_taxons", 181 | "id": "1" 182 | } 183 | ] 184 | }, 185 | "option_types": { 186 | "data": [ 187 | { 188 | "type": "spree_option_types", 189 | "id": "1" 190 | } 191 | ] 192 | }, 193 | "images": { 194 | "data": [ 195 | { 196 | "type": "spree_images", 197 | "id": "1" 198 | } 199 | ] 200 | } 201 | } 202 | } 203 | ] 204 | } 205 | ``` 206 | 207 | See all of the products that an option type owns. 208 | 209 | ## Show Product of a Option Type 210 | 211 | ```shell 212 | curl "https://example.com/api/v2/option_types/1/products/1" 213 | ``` 214 | 215 | ```json 216 | { 217 | "data": { 218 | "id": "1", 219 | "type": "spree_products", 220 | "attributes": { 221 | "name": "Ruby on Rails Tote", 222 | "description": "Velit nemo odio ducimus nobis non doloremque beatae sunt. Totam quia voluptatum perferendis tempore sed voluptate consequuntur. Sit id corporis autem veritatis reprehenderit.", 223 | "slug": "ruby-on-rails-tote", 224 | "meta_description": null, 225 | "meta_keywords": null, 226 | "store_name": "Whole New Home" 227 | }, 228 | "relationships": { 229 | "master": { 230 | "data": { 231 | "type": "spree_variants", 232 | "id": "1" 233 | } 234 | }, 235 | "variants": { 236 | "data": [] 237 | }, 238 | "taxons": { 239 | "data": [ 240 | { 241 | "type": "spree_taxons", 242 | "id": "1" 243 | } 244 | ] 245 | }, 246 | "option_types": { 247 | "data": [ 248 | { 249 | "type": "spree_option_types", 250 | "id": "1" 251 | } 252 | ] 253 | }, 254 | "images": { 255 | "data": [ 256 | { 257 | "type": "spree_images", 258 | "id": "1" 259 | } 260 | ] 261 | } 262 | } 263 | } 264 | } 265 | ``` 266 | 267 | Find a product that an option type owns. 268 | -------------------------------------------------------------------------------- /docs/source/includes/_option_values.md: -------------------------------------------------------------------------------- 1 | # Option Values 2 | 3 | ## List Option Values 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/option_values" 7 | ``` 8 | 9 | ```json 10 | { 11 | "data": [ 12 | { 13 | "id": "1", 14 | "type": "spree_option_values", 15 | "attributes": { 16 | "name": "Small", 17 | "presentation": "S", 18 | "position": 1 19 | }, 20 | "relationships": { 21 | "option_type": { 22 | "data": { 23 | "type": "spree_option_types", 24 | "id": "1" 25 | } 26 | } 27 | } 28 | } 29 | ] 30 | } 31 | ``` 32 | 33 | List all of the option values. 34 | 35 | ## Show Option Value 36 | 37 | ```shell 38 | curl "https://example.com/api/v2/option_values/1" 39 | ``` 40 | 41 | ```json 42 | { 43 | "data": { 44 | "id": "1", 45 | "type": "spree_option_values", 46 | "attributes": { 47 | "name": "Small", 48 | "presentation": "S", 49 | "position": 1 50 | }, 51 | "relationships": { 52 | "option_type": { 53 | "data": { 54 | "type": "spree_option_types", 55 | "id": "1" 56 | } 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | Find an option value by its `id`. 64 | 65 | ## Show Option Type of an Option Value 66 | 67 | ```shell 68 | curl "https://example.com/api/v2/option_values/1/option_type" 69 | ``` 70 | 71 | ```json 72 | { 73 | "data": { 74 | "id": "1", 75 | "type": "spree_option_types", 76 | "attributes": { 77 | "name": "tshirt-size", 78 | "presentation": "Size", 79 | "position": 1 80 | }, 81 | "relationships": { 82 | "option_values": { 83 | "data": [ 84 | { 85 | "type": "spree_option_values", 86 | "id": "1" 87 | } 88 | ] 89 | }, 90 | "products": { 91 | "data": [ 92 | { 93 | "type": "spree_products", 94 | "id": "1" 95 | } 96 | ] 97 | } 98 | } 99 | } 100 | } 101 | ``` 102 | 103 | Find the option type of an option value by the option value's `id`. 104 | 105 | ## List Variants of an Option Value 106 | 107 | ```shell 108 | curl "https://example.com/api/v2/option_values/1/variants" 109 | ``` 110 | 111 | ```json 112 | { 113 | "data": [ 114 | { 115 | "id": "1", 116 | "type": "spree_variants", 117 | "attributes": { 118 | "sku": "ROR-00001", 119 | "weight": "0.0", 120 | "height": null, 121 | "width": null, 122 | "depth": null, 123 | "is_master": false, 124 | "position": 2, 125 | "name": "Ruby on Rails Baseball Jersey", 126 | "price": "15.99", 127 | "display_price": "$15.99 CAD" 128 | }, 129 | "relationships": { 130 | "prices": { 131 | "data": [ 132 | { 133 | "type": "spree_prices", 134 | "id": "33" 135 | } 136 | ] 137 | }, 138 | "option_values": { 139 | "data": [ 140 | { 141 | "type": "spree_option_values", 142 | "id": "1" 143 | } 144 | ] 145 | }, 146 | "images": { 147 | "data": [ 148 | { 149 | "type": "spree_images", 150 | "id": "1" 151 | } 152 | ] 153 | }, 154 | "product": { 155 | "data": { 156 | "type": "spree_products", 157 | "id": "3" 158 | } 159 | } 160 | } 161 | } 162 | ] 163 | } 164 | ``` 165 | 166 | List all of the variants that belong to an option value via the option value's `id`. 167 | 168 | ## Show Variant of an Option Value 169 | 170 | ```shell 171 | curl "https://example.com/api/v2/option_values/1/variants/1" 172 | ``` 173 | 174 | ```json 175 | { 176 | "data": { 177 | "id": "1", 178 | "type": "spree_variants", 179 | "attributes": { 180 | "sku": "ROR-00001", 181 | "weight": "0.0", 182 | "height": null, 183 | "width": null, 184 | "depth": null, 185 | "is_master": false, 186 | "position": 2, 187 | "name": "Ruby on Rails Baseball Jersey", 188 | "price": "15.99", 189 | "display_price": "$15.99 CAD" 190 | }, 191 | "relationships": { 192 | "prices": { 193 | "data": [ 194 | { 195 | "type": "spree_prices", 196 | "id": "33" 197 | } 198 | ] 199 | }, 200 | "option_values": { 201 | "data": [ 202 | { 203 | "type": "spree_option_values", 204 | "id": "1" 205 | } 206 | ] 207 | }, 208 | "images": { 209 | "data": [ 210 | { 211 | "type": "spree_images", 212 | "id": "1" 213 | } 214 | ] 215 | }, 216 | "product": { 217 | "data": { 218 | "type": "spree_products", 219 | "id": "3" 220 | } 221 | } 222 | } 223 | } 224 | } 225 | ``` 226 | 227 | Show a variant that belongs to an option value via the option value's `id`. 228 | -------------------------------------------------------------------------------- /docs/source/includes/_orders.md: -------------------------------------------------------------------------------- 1 | # Orders 2 | 3 | ## List Orders 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/orders" 7 | -d token=abc123 8 | ``` 9 | 10 | ```json 11 | { 12 | "data" : [ 13 | { 14 | "attributes" : { 15 | "additional_tax_total" : "0.0", 16 | "adjustment_total" : "0.0", 17 | "approved_at" : null, 18 | "canceled_at" : null, 19 | "channel" : "spree", 20 | "completed_at" : "2015-09-08T04:04:01.162Z", 21 | "confirmation_delivered" : false, 22 | "currency" : "CAD", 23 | "display_additional_tax_total" : "$0.00 CAD", 24 | "display_adjustment_total" : "$0.00 CAD", 25 | "display_included_tax_total" : "$0.00 CAD", 26 | "display_item_total" : "$10.00 CAD", 27 | "display_promo_total" : "$0.00 CAD", 28 | "display_shipment_total" : "$100.00 CAD", 29 | "display_total" : "$110.00 CAD", 30 | "email" : "spree@example.com", 31 | "included_tax_total" : "0.0", 32 | "item_count" : 0, 33 | "item_total" : "10.0", 34 | "number" : "#R123456789", 35 | "payment_state" : null, 36 | "payment_total" : "0.0", 37 | "promo_total" : "0.0", 38 | "shipment_state" : null, 39 | "shipment_total" : "100.0", 40 | "special_instructions" : null, 41 | "state" : "complete", 42 | "store_name" : "Example Store", 43 | "total" : "110.0" 44 | }, 45 | "relationships" : { 46 | "bill_address" : { 47 | "data" : { 48 | "type" : "spree_addresses" 49 | } 50 | }, 51 | "line_items" : { 52 | "data" : [ 53 | { 54 | "type" : "spree_line_items" 55 | } 56 | ] 57 | }, 58 | "ship_address" : { 59 | "data" : { 60 | "type" : "spree_addresses" 61 | } 62 | }, 63 | "user" : { 64 | "data" : { 65 | "type" : "spree_users" 66 | } 67 | } 68 | }, 69 | "type" : "spree_orders" 70 | } 71 | ] 72 | } 73 | ``` 74 | 75 | List all of the orders in the DB if you're an admin. 76 | If you're a standard user, then only the orders that you own are listed. 77 | 78 | ## Show Order 79 | 80 | ```shell 81 | curl "https://example.com/api/v2/orders/:id" 82 | -d token=abc123 83 | ``` 84 | 85 | ```json 86 | { 87 | "data" : { 88 | "attributes" : { 89 | "additional_tax_total" : "0.0", 90 | "adjustment_total" : "0.0", 91 | "approved_at" : null, 92 | "canceled_at" : null, 93 | "channel" : "spree", 94 | "completed_at" : "2015-09-08T04:04:01.162Z", 95 | "confirmation_delivered" : false, 96 | "currency" : "CAD", 97 | "display_additional_tax_total" : "$0.00 CAD", 98 | "display_adjustment_total" : "$0.00 CAD", 99 | "display_included_tax_total" : "$0.00 CAD", 100 | "display_item_total" : "$10.00 CAD", 101 | "display_promo_total" : "$0.00 CAD", 102 | "display_shipment_total" : "$100.00 CAD", 103 | "display_total" : "$110.00 CAD", 104 | "email" : "spree@example.com", 105 | "included_tax_total" : "0.0", 106 | "item_count" : 0, 107 | "item_total" : "10.0", 108 | "number" : "#R123456789", 109 | "payment_state" : null, 110 | "payment_total" : "0.0", 111 | "promo_total" : "0.0", 112 | "shipment_state" : null, 113 | "shipment_total" : "100.0", 114 | "special_instructions" : null, 115 | "state" : "complete", 116 | "store_name" : "Example Store", 117 | "total" : "110.0" 118 | }, 119 | "relationships" : { 120 | "bill_address" : { 121 | "data" : { 122 | "type" : "spree_addresses" 123 | } 124 | }, 125 | "line_items" : { 126 | "data" : [ 127 | { 128 | "type" : "spree_line_items" 129 | } 130 | ] 131 | }, 132 | "ship_address" : { 133 | "data" : { 134 | "type" : "spree_addresses" 135 | } 136 | }, 137 | "user" : { 138 | "data" : { 139 | "type" : "spree_users" 140 | } 141 | } 142 | }, 143 | "type" : "spree_orders" 144 | } 145 | } 146 | ``` 147 | 148 | This will get an order via the id supplied. 149 | Please note that you can ony access the orders that you own. 150 | If you are an admin, you can view any order. 151 | -------------------------------------------------------------------------------- /docs/source/includes/_pagination.md: -------------------------------------------------------------------------------- 1 | # Pagination 2 | 3 | ```shell 4 | curl "https://example.com/api/v2/kitten?page[number]2&page[size]=20" 5 | ``` 6 | 7 | This projects supports the [JSON API's `page` keyword](http://jsonapi.org/format/#fetching-pagination). 8 | To specify the page number, set a `page[number]` value. 9 | To specify the page size (the amount of records returned), set a `page[size]` value; the default is 24. 10 | If you would like the links to the `self`, `next`, `prev`, `first`, and `last` it is not yet supported. 11 | -------------------------------------------------------------------------------- /docs/source/includes/_prices.md: -------------------------------------------------------------------------------- 1 | # Prices 2 | 3 | ## List Prices 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/prices" 7 | ``` 8 | 9 | ```json 10 | { 11 | "data": [ 12 | { 13 | "id": "1", 14 | "type": "spree_prices", 15 | "attributes": { 16 | "amount": "15.99", 17 | "price": "15.99", 18 | "display_amount": "$15.99 CAD", 19 | "display_price": "$15.99 CAD", 20 | "currency": "CAD" 21 | }, 22 | "relationships": { 23 | "variant": { 24 | "data": { 25 | "type": "spree_variants", 26 | "id": "1" 27 | } 28 | } 29 | } 30 | } 31 | ] 32 | } 33 | ``` 34 | 35 | List all of the prices in the database. 36 | 37 | ## Show Price 38 | 39 | ```shell 40 | curl "https://example.com/api/v2/prices/1" 41 | ``` 42 | 43 | ```json 44 | { 45 | "data": { 46 | "id": "1", 47 | "type": "spree_prices", 48 | "attributes": { 49 | "amount": "15.99", 50 | "price": "15.99", 51 | "display_amount": "$15.99 CAD", 52 | "display_price": "$15.99 CAD", 53 | "currency": "CAD" 54 | }, 55 | "relationships": { 56 | "variant": { 57 | "data": { 58 | "type": "spree_variants", 59 | "id": "1" 60 | } 61 | } 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | Fetch a price via its `id`. 68 | 69 | ## Show Variant of a Price 70 | 71 | ```shell 72 | curl "https://example.com/api/v2/prices/1/variant" 73 | ``` 74 | 75 | ```json 76 | { 77 | "data": { 78 | "id": "1", 79 | "type": "spree_variants", 80 | "attributes": { 81 | "sku": "ROR-00011", 82 | "weight": "0.0", 83 | "height": "1.0", 84 | "width": "3.0", 85 | "depth": "2.0", 86 | "is_master": true, 87 | "position": 1, 88 | "name": "Ruby on Rails Tote", 89 | "price": "15.99", 90 | "display_price" : "$15.99 CAD" 91 | }, 92 | "relationships": { 93 | "prices": { 94 | "data": [ 95 | { 96 | "type": "spree_prices", 97 | "id": "1" 98 | } 99 | ] 100 | }, 101 | "option_values": { 102 | "data": [ 103 | { 104 | "type": "spree_option_values", 105 | "id": "1" 106 | } 107 | ] 108 | }, 109 | "images": { 110 | "data": [ 111 | { 112 | "type": "spree_images", 113 | "id": "1" 114 | } 115 | ] 116 | }, 117 | "product": { 118 | "data": { 119 | "type": "spree_products", 120 | "id": "1" 121 | } 122 | } 123 | } 124 | } 125 | } 126 | ``` 127 | 128 | Fetch the variant a price belongs to via the price's `id`. 129 | 130 | ## Show Product of a Price 131 | 132 | ```shell 133 | curl "https://example.com/api/v2/prices/1/product" 134 | ``` 135 | 136 | ```json 137 | { 138 | "data": { 139 | "id": "1", 140 | "type": "spree_products", 141 | "attributes": { 142 | "name": "Ruby on Rails Tote", 143 | "description": "Dolorem molestias sint maxime id at rem qui exercitationem. Neque voluptas corrupti magni suscipit iusto voluptatum. Ea quibusdam dolorem inventore praesentium sed dicta eveniet et. Rerum inventore laudantium quisquam earum consequatur dignissimos.", 144 | "slug": "ruby-on-rails-tote", 145 | "meta_description": null, 146 | "meta_keywords": null, 147 | "store_name": "Whole New Home" 148 | }, 149 | "relationships": { 150 | "master": { 151 | "data": { 152 | "type": "spree_variants", 153 | "id": "1" 154 | } 155 | }, 156 | "variants": { 157 | "data": [] 158 | }, 159 | "taxons": { 160 | "data": [ 161 | { 162 | "type": "spree_taxons", 163 | "id": "18" 164 | } 165 | ] 166 | }, 167 | "option_types": { 168 | "data": [ 169 | { 170 | "type": "spree_option_types", 171 | "id": "1" 172 | } 173 | ] 174 | }, 175 | "images": { 176 | "data": [ 177 | { 178 | "type": "spree_images", 179 | "id": "1" 180 | } 181 | ] 182 | } 183 | } 184 | } 185 | } 186 | ``` 187 | 188 | Fetch the product a price belongs to via the price's `id`. 189 | -------------------------------------------------------------------------------- /docs/source/includes/_states.md: -------------------------------------------------------------------------------- 1 | # States 2 | 3 | ## List States 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/states" 7 | ``` 8 | 9 | ```json 10 | { 11 | "data": [ 12 | { 13 | "id": "1", 14 | "type": "spree_states", 15 | "attributes": { 16 | "name": "Canillo", 17 | "abbr": "02" 18 | }, 19 | "relationships": { 20 | "country": { 21 | "data": { 22 | "type": "spree_countries", 23 | "id": "1" 24 | } 25 | } 26 | } 27 | } 28 | ] 29 | } 30 | ``` 31 | 32 | This will list all of the states. 33 | 34 | ## Show State 35 | 36 | ```shell 37 | curl "https://example.com/api/v2/states/1" 38 | ``` 39 | 40 | ```json 41 | { 42 | "data": { 43 | "id": "1", 44 | "type": "spree_states", 45 | "attributes": { 46 | "name": "Canillo", 47 | "abbr": "02" 48 | }, 49 | "relationships": { 50 | "country": { 51 | "data": { 52 | "type": "spree_countries", 53 | "id": "1" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | ``` 60 | 61 | Select a state via its `id`. 62 | 63 | ## Show Country of a States 64 | 65 | ```shell 66 | curl "https://example.com/api/v2/states/1/countries" 67 | ``` 68 | 69 | ```json 70 | { 71 | "data": { 72 | "id": "1", 73 | "type": "spree_countries", 74 | "attributes": { 75 | "iso_name": "ANDORRA", 76 | "iso": "AD", 77 | "iso3": "AND", 78 | "name": "Andorra", 79 | "numcode": 20, 80 | "states_required": true 81 | }, 82 | "relationships": { 83 | "states": { 84 | "data": [ 85 | { 86 | "type": "spree_states", 87 | "id": "1" 88 | } 89 | ] 90 | } 91 | } 92 | } 93 | } 94 | ``` 95 | 96 | View the country that a state belongs to via the state's `id`. 97 | -------------------------------------------------------------------------------- /docs/source/includes/_taxonomies.md: -------------------------------------------------------------------------------- 1 | # Taxonomies 2 | 3 | ## List Taxonomies 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/taxonomies" 7 | ``` 8 | 9 | ```json 10 | { 11 | "data": [ 12 | { 13 | "id": "1", 14 | "type": "spree_taxonomies", 15 | "attributes": { 16 | "name": "Category", 17 | "position": 1 18 | }, 19 | "relationships": { 20 | "taxons": { 21 | "data": [ 22 | { 23 | "type": "spree_taxons", 24 | "id": "2" 25 | }, 26 | { 27 | "type": "spree_taxons", 28 | "id": "1" 29 | } 30 | ] 31 | }, 32 | "children": { 33 | "data": [ 34 | { 35 | "type": "spree_taxons", 36 | "id": "2" 37 | } 38 | ] 39 | } 40 | } 41 | } 42 | ] 43 | } 44 | ``` 45 | 46 | List of the taxonomies in the database. 47 | 48 | ## Show Taxonomy 49 | 50 | ```shell 51 | curl "https://example.com/api/v2/taxonomies/1" 52 | ``` 53 | 54 | ```json 55 | { 56 | "data": { 57 | "id": "1", 58 | "type": "spree_taxonomies", 59 | "attributes": { 60 | "name": "Category", 61 | "position": 1 62 | }, 63 | "relationships": { 64 | "taxons": { 65 | "data": [ 66 | { 67 | "type": "spree_taxons", 68 | "id": "2" 69 | }, 70 | { 71 | "type": "spree_taxons", 72 | "id": "1" 73 | } 74 | ] 75 | }, 76 | "children": { 77 | "data": [ 78 | { 79 | "type": "spree_taxons", 80 | "id": "2" 81 | } 82 | ] 83 | } 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | Select a taxonomy via the `id`. 90 | 91 | ## List Taxons of a Taxonomy 92 | 93 | ```shell 94 | curl "https://example.com/api/v2/taxonomies/1/taxons" 95 | ``` 96 | 97 | ```json 98 | { 99 | "data": [ 100 | { 101 | "id": "2", 102 | "type": "spree_taxons", 103 | "attributes": { 104 | "name": "SLEEP", 105 | "permalink": "category/sleep", 106 | "position": 0, 107 | "description": null, 108 | "meta_title": null, 109 | "meta_description": null, 110 | "meta_keywords": null, 111 | "depth": 1, 112 | "classifications_count": null 113 | }, 114 | "relationships": { 115 | "taxonomy": { 116 | "data": { 117 | "type": "spree_taxonomies", 118 | "id": "1" 119 | } 120 | }, 121 | "parent": { 122 | "data": { 123 | "type": "spree_taxons", 124 | "id": "1" 125 | } 126 | }, 127 | "children": { 128 | "data": [ 129 | { 130 | "type": "spree_taxons", 131 | "id": "3" 132 | } 133 | ] 134 | } 135 | } 136 | }, 137 | { 138 | "id": "1", 139 | "type": "spree_taxons", 140 | "attributes": { 141 | "name": "Category", 142 | "permalink": "category", 143 | "position": 0, 144 | "description": null, 145 | "meta_title": null, 146 | "meta_description": null, 147 | "meta_keywords": null, 148 | "depth": 0, 149 | "classifications_count": 1 150 | }, 151 | "relationships": { 152 | "taxonomy": { 153 | "data": { 154 | "type": "spree_taxonomies", 155 | "id": "1" 156 | } 157 | }, 158 | "parent": { 159 | "data": null 160 | }, 161 | "children": { 162 | "data": [ 163 | { 164 | "type": "spree_taxons", 165 | "id": "2" 166 | } 167 | ] 168 | } 169 | } 170 | } 171 | ] 172 | } 173 | ``` 174 | 175 | List all of the taxons that a taxonomy has a reference to. 176 | 177 | ## Show Taxon of a Taxonomy 178 | 179 | ```shell 180 | curl "https://example.com/api/v2/taxonomies/1/taxons/1" 181 | ``` 182 | 183 | ```json 184 | { 185 | "data": { 186 | "id": "1", 187 | "type": "spree_taxons", 188 | "attributes": { 189 | "name": "Category", 190 | "permalink": "category", 191 | "position": 0, 192 | "description": null, 193 | "meta_title": null, 194 | "meta_description": null, 195 | "meta_keywords": null, 196 | "depth": 0, 197 | "classifications_count": 1 198 | }, 199 | "relationships": { 200 | "taxonomy": { 201 | "data": { 202 | "type": "spree_taxonomies", 203 | "id": "1" 204 | } 205 | }, 206 | "parent": { 207 | "data": null 208 | }, 209 | "children": { 210 | "data": [ 211 | { 212 | "type": "spree_taxons", 213 | "id": "2" 214 | } 215 | ] 216 | } 217 | } 218 | } 219 | } 220 | ``` 221 | 222 | Find a taxon that the taxonomy owns via the taxonomy's `id` and the taxon's `id`. 223 | 224 | ## List Children of a Taxonomy 225 | 226 | ```shell 227 | curl "https://example.com/api/v2/taxonomies/1/children" 228 | ``` 229 | 230 | ```json 231 | { 232 | "data": [ 233 | { 234 | "id": "2", 235 | "type": "spree_taxons", 236 | "attributes": { 237 | "name": "SLEEP", 238 | "permalink": "category/sleep", 239 | "position": 0, 240 | "description": null, 241 | "meta_title": null, 242 | "meta_description": null, 243 | "meta_keywords": null, 244 | "depth": 1, 245 | "classifications_count": null 246 | }, 247 | "relationships": { 248 | "taxonomy": { 249 | "data": { 250 | "type": "spree_taxonomies", 251 | "id": "1" 252 | } 253 | }, 254 | "parent": { 255 | "data": { 256 | "type": "spree_taxons", 257 | "id": "1" 258 | } 259 | }, 260 | "children": { 261 | "data": [ 262 | { 263 | "type": "spree_taxons", 264 | "id": "3" 265 | } 266 | ] 267 | } 268 | } 269 | } 270 | ] 271 | } 272 | ``` 273 | 274 | List all of the children of a taxonomy. 275 | This does _not_ include the root taxon that would be included when listing all of the taxons of this taxonomy. 276 | 277 | ## Show Child of a Taxonomy 278 | 279 | ```shell 280 | curl "https://example.com/api/v2/taxonomies/1/children/2" 281 | ``` 282 | 283 | ```json 284 | { 285 | "data": { 286 | "id": "2", 287 | "type": "spree_taxons", 288 | "attributes": { 289 | "name": "SLEEP", 290 | "permalink": "category/sleep", 291 | "position": 0, 292 | "description": null, 293 | "meta_title": null, 294 | "meta_description": null, 295 | "meta_keywords": null, 296 | "depth": 1, 297 | "classifications_count": null 298 | }, 299 | "relationships": { 300 | "taxonomy": { 301 | "data": { 302 | "type": "spree_taxonomies", 303 | "id": "1" 304 | } 305 | }, 306 | "parent": { 307 | "data": { 308 | "type": "spree_taxons", 309 | "id": "1" 310 | } 311 | }, 312 | "children": { 313 | "data": [ 314 | { 315 | "type": "spree_taxons", 316 | "id": "3" 317 | } 318 | ] 319 | } 320 | } 321 | } 322 | } 323 | ``` 324 | 325 | Fetch a child of the taxnomy via the taxonomy's `id` and the child's `id`. 326 | -------------------------------------------------------------------------------- /docs/source/includes/_taxons.md: -------------------------------------------------------------------------------- 1 | # Taxons 2 | 3 | ## List Taxons 4 | 5 | ```shell 6 | curl "https://example.com/api/v2/taxons" 7 | ``` 8 | 9 | ```json 10 | { 11 | "data": [ 12 | { 13 | "id": "2", 14 | "type": "spree_taxons", 15 | "attributes": { 16 | "name": "SLEEP", 17 | "permalink": "category/sleep", 18 | "position": 0, 19 | "description": null, 20 | "meta_title": null, 21 | "meta_description": null, 22 | "meta_keywords": null, 23 | "depth": 1, 24 | "classifications_count": null 25 | }, 26 | "relationships": { 27 | "taxonomy": { 28 | "data": { 29 | "type": "spree_taxonomies", 30 | "id": "1" 31 | } 32 | }, 33 | "parent": { 34 | "data": { 35 | "type": "spree_taxons", 36 | "id": "1" 37 | } 38 | }, 39 | "children": { 40 | "data": [ 41 | { 42 | "type": "spree_taxons", 43 | "id": "3" 44 | } 45 | ] 46 | } 47 | } 48 | } 49 | ] 50 | } 51 | ``` 52 | 53 | List all the taxons in the database. 54 | 55 | ## Show Taxon 56 | 57 | ```shell 58 | curl "https://example.com/api/v2/taxons/1" 59 | ``` 60 | 61 | ```json 62 | { 63 | "data": { 64 | "id": "1", 65 | "type": "spree_taxons", 66 | "attributes": { 67 | "name": "Category", 68 | "permalink": "category", 69 | "position": 0, 70 | "description": null, 71 | "meta_title": null, 72 | "meta_description": null, 73 | "meta_keywords": null, 74 | "depth": 0, 75 | "classifications_count": 1 76 | }, 77 | "relationships": { 78 | "taxonomy": { 79 | "data": { 80 | "type": "spree_taxonomies", 81 | "id": "1" 82 | } 83 | }, 84 | "parent": { 85 | "data": null 86 | }, 87 | "children": { 88 | "data": [ 89 | { 90 | "type": "spree_taxons", 91 | "id": "2" 92 | } 93 | ] 94 | } 95 | } 96 | } 97 | } 98 | ``` 99 | 100 | Locate the taxon that you're looking for via its `id`. 101 | 102 | ## Show Taxonomy of a Taxon 103 | 104 | ```shell 105 | curl "https://example.com/api/v2/taxons/1/taxonomy" 106 | ``` 107 | 108 | ```json 109 | { 110 | "data": { 111 | "id": "1", 112 | "type": "spree_taxonomies", 113 | "attributes": { 114 | "name": "Category", 115 | "position": 1 116 | }, 117 | "relationships": { 118 | "taxons": { 119 | "data": [ 120 | { 121 | "type": "spree_taxons", 122 | "id": "2" 123 | }, 124 | { 125 | "type": "spree_taxons", 126 | "id": "1" 127 | } 128 | ] 129 | }, 130 | "children": { 131 | "data": [ 132 | { 133 | "type": "spree_taxons", 134 | "id": "2" 135 | } 136 | ] 137 | } 138 | } 139 | } 140 | } 141 | ``` 142 | 143 | Display the taxonomy that a taxon belongs. 144 | 145 | ## Show Parent of a Taxon 146 | 147 | ```shell 148 | curl "https://example.com/api/v2/taxons/2/parent" 149 | ``` 150 | 151 | ```json 152 | { 153 | "data": { 154 | "id": "1", 155 | "type": "spree_taxons", 156 | "attributes": { 157 | "name": "Category", 158 | "permalink": "category", 159 | "position": 0, 160 | "description": null, 161 | "meta_title": null, 162 | "meta_description": null, 163 | "meta_keywords": null, 164 | "depth": 0, 165 | "classifications_count": 1 166 | }, 167 | "relationships": { 168 | "taxonomy": { 169 | "data": { 170 | "type": "spree_taxonomies", 171 | "id": "1" 172 | } 173 | }, 174 | "parent": { 175 | "data": null 176 | }, 177 | "children": { 178 | "data": [ 179 | { 180 | "type": "spree_taxons", 181 | "id": "2" 182 | } 183 | ] 184 | } 185 | } 186 | } 187 | } 188 | ``` 189 | 190 | Display the parent taxon that a taxon may belong to. 191 | 192 | ## List Children of a Taxon 193 | 194 | ```shell 195 | curl "https://example.com/api/v2/taxons/1/children" 196 | ``` 197 | 198 | ```json 199 | { 200 | "data": [ 201 | { 202 | "id": "2", 203 | "type": "spree_taxons", 204 | "attributes": { 205 | "name": "SLEEP", 206 | "permalink": "category/sleep", 207 | "position": 0, 208 | "description": null, 209 | "meta_title": null, 210 | "meta_description": null, 211 | "meta_keywords": null, 212 | "depth": 1, 213 | "classifications_count": null 214 | }, 215 | "relationships": { 216 | "taxonomy": { 217 | "data": { 218 | "type": "spree_taxonomies", 219 | "id": "1" 220 | } 221 | }, 222 | "parent": { 223 | "data": { 224 | "type": "spree_taxons", 225 | "id": "1" 226 | } 227 | }, 228 | "children": { 229 | "data": [ 230 | { 231 | "type": "spree_taxons", 232 | "id": "3" 233 | } 234 | ] 235 | } 236 | } 237 | } 238 | ] 239 | } 240 | ``` 241 | 242 | List all the children that a taxon has. 243 | 244 | ## Show Child of a Taxon 245 | 246 | ```shell 247 | curl "https://example.com/api/v2/taxons/1/children/2" 248 | ``` 249 | 250 | ```json 251 | { 252 | "data": { 253 | "id": "2", 254 | "type": "spree_taxons", 255 | "attributes": { 256 | "name": "SLEEP", 257 | "permalink": "category/sleep", 258 | "position": 0, 259 | "description": null, 260 | "meta_title": null, 261 | "meta_description": null, 262 | "meta_keywords": null, 263 | "depth": 1, 264 | "classifications_count": null 265 | }, 266 | "relationships": { 267 | "taxonomy": { 268 | "data": { 269 | "type": "spree_taxonomies", 270 | "id": "1" 271 | } 272 | }, 273 | "parent": { 274 | "data": { 275 | "type": "spree_taxons", 276 | "id": "1" 277 | } 278 | }, 279 | "children": { 280 | "data": [ 281 | { 282 | "type": "spree_taxons", 283 | "id": "3" 284 | } 285 | ] 286 | } 287 | } 288 | } 289 | } 290 | ``` 291 | 292 | Find a child via its `id` and its owners (taxon) `id`. 293 | 294 | ## List Products of a Taxon 295 | 296 | ```shell 297 | curl "https://example.com/api/v2/taxons/1/products" 298 | ``` 299 | 300 | ```json 301 | { 302 | "data": [ 303 | { 304 | "id": "10", 305 | "type": "spree_products", 306 | "attributes": { 307 | "name": "Spree Ringer T-Shirt", 308 | "description": "Velit nemo odio ducimus nobis non doloremque beatae sunt. Totam quia voluptatum perferendis tempore sed voluptate consequuntur. Sit id corporis autem veritatis reprehenderit.", 309 | "slug": "spree-ringer-t-shirt", 310 | "meta_description": null, 311 | "meta_keywords": null, 312 | "store_name": "Whole New Home" 313 | }, 314 | "relationships": { 315 | "master": { 316 | "data": { 317 | "type": "spree_variants", 318 | "id": "10" 319 | } 320 | }, 321 | "variants": { 322 | "data": [] 323 | }, 324 | "taxons": { 325 | "data": [ 326 | { 327 | "type": "spree_taxons", 328 | "id": "1" 329 | } 330 | ] 331 | }, 332 | "option_types": { 333 | "data": [ 334 | { 335 | "type": "spree_option_types", 336 | "id": "1" 337 | } 338 | ] 339 | }, 340 | "images": { 341 | "data": [ 342 | { 343 | "type": "spree_images", 344 | "id": "1" 345 | } 346 | ] 347 | } 348 | } 349 | } 350 | ] 351 | } 352 | ``` 353 | 354 | List all of the products that taxon owns via the taxons `id`. 355 | 356 | ## Show Product of a Taxon 357 | 358 | ```shell 359 | curl "https://example.com/api/v2/taxons/1/products/10" 360 | ``` 361 | 362 | ```json 363 | { 364 | "data": { 365 | "id": "10", 366 | "type": "spree_products", 367 | "attributes": { 368 | "name": "Spree Ringer T-Shirt", 369 | "description": "Velit nemo odio ducimus nobis non doloremque beatae sunt. Totam quia voluptatum perferendis tempore sed voluptate consequuntur. Sit id corporis autem veritatis reprehenderit.", 370 | "slug": "spree-ringer-t-shirt", 371 | "meta_description": null, 372 | "meta_keywords": null, 373 | "store_name": "Whole New Home" 374 | }, 375 | "relationships": { 376 | "master": { 377 | "data": { 378 | "type": "spree_variants", 379 | "id": "10" 380 | } 381 | }, 382 | "variants": { 383 | "data": [] 384 | }, 385 | "taxons": { 386 | "data": [ 387 | { 388 | "type": "spree_taxons", 389 | "id": "1" 390 | } 391 | ] 392 | }, 393 | "option_types": { 394 | "data": [ 395 | { 396 | "type": "spree_option_types", 397 | "id": "1" 398 | } 399 | ] 400 | }, 401 | "images": { 402 | "data": [ 403 | { 404 | "type": "spree_images", 405 | "id": "1" 406 | } 407 | ] 408 | } 409 | } 410 | } 411 | } 412 | ``` 413 | 414 | Fetch a product of taxon via the taxon's `id` and the products `id`. 415 | -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Solidus JSON API Reference 3 | 4 | language_tabs: 5 | - shell 6 | 7 | toc_footers: 8 | - Source Code 9 | - Spree API Documentation 10 | - JSON API Documentation 11 | - Documentation Powered by Slate 12 | 13 | includes: 14 | - errors 15 | - pagination 16 | - filtering 17 | - countries 18 | - images 19 | - line_items 20 | - option_types 21 | - option_values 22 | - orders 23 | - prices 24 | - products 25 | - states 26 | - taxonomies 27 | - taxons 28 | - variants 29 | 30 | search: true 31 | --- 32 | 33 | # Introduction 34 | 35 | Welcome to the Solidus JSON API! 36 | You can use our API to grab data from our database and make updates. 37 | 38 | We currently only have a language binding in Shell. 39 | You can view code examples in the dark area to the right. 40 | 41 | This example API documentation page was created with [Slate](http://github.com/tripit/slate). 42 | Feel free to edit it and use it as a base for documenting APIs of your own. 43 | 44 | This JSON API is formatted after the [JSON API spec](http://jsonapi.org). 45 | Please follow its guidelines to learn about: 46 | 47 | - Pagination 48 | - Including Relationships 49 | - Sorting 50 | - Error Objects 51 | - The Document Structure 52 | -------------------------------------------------------------------------------- /docs/source/javascripts/all.js: -------------------------------------------------------------------------------- 1 | //= require ./lib/_energize 2 | //= require ./app/_lang 3 | //= require ./app/_search 4 | //= require ./app/_toc 5 | -------------------------------------------------------------------------------- /docs/source/javascripts/all_nosearch.js: -------------------------------------------------------------------------------- 1 | //= require ./lib/_energize 2 | //= require ./app/_lang 3 | //= require ./app/_toc 4 | -------------------------------------------------------------------------------- /docs/source/javascripts/app/_lang.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2008-2013 Concur Technologies, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | License for the specific language governing permissions and limitations 14 | under the License. 15 | */ 16 | (function (global) { 17 | 'use strict'; 18 | 19 | var languages = []; 20 | 21 | global.setupLanguages = setupLanguages; 22 | global.activateLanguage = activateLanguage; 23 | 24 | function activateLanguage(language) { 25 | if (!language) return; 26 | if (language === "") return; 27 | 28 | $(".lang-selector a").removeClass('active'); 29 | $(".lang-selector a[data-language-name='" + language + "']").addClass('active'); 30 | for (var i=0; i < languages.length; i++) { 31 | $(".highlight." + languages[i]).hide(); 32 | } 33 | $(".highlight." + language).show(); 34 | 35 | global.toc.calculateHeights(); 36 | 37 | // scroll to the new location of the position 38 | if ($(window.location.hash).get(0)) { 39 | $(window.location.hash).get(0).scrollIntoView(true); 40 | } 41 | } 42 | 43 | // parseURL and stringifyURL are from https://github.com/sindresorhus/query-string 44 | // MIT licensed 45 | // https://github.com/sindresorhus/query-string/blob/7bee64c16f2da1a326579e96977b9227bf6da9e6/license 46 | function parseURL(str) { 47 | if (typeof str !== 'string') { 48 | return {}; 49 | } 50 | 51 | str = str.trim().replace(/^(\?|#|&)/, ''); 52 | 53 | if (!str) { 54 | return {}; 55 | } 56 | 57 | return str.split('&').reduce(function (ret, param) { 58 | var parts = param.replace(/\+/g, ' ').split('='); 59 | var key = parts[0]; 60 | var val = parts[1]; 61 | 62 | key = decodeURIComponent(key); 63 | // missing `=` should be `null`: 64 | // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters 65 | val = val === undefined ? null : decodeURIComponent(val); 66 | 67 | if (!ret.hasOwnProperty(key)) { 68 | ret[key] = val; 69 | } else if (Array.isArray(ret[key])) { 70 | ret[key].push(val); 71 | } else { 72 | ret[key] = [ret[key], val]; 73 | } 74 | 75 | return ret; 76 | }, {}); 77 | }; 78 | 79 | function stringifyURL(obj) { 80 | return obj ? Object.keys(obj).sort().map(function (key) { 81 | var val = obj[key]; 82 | 83 | if (Array.isArray(val)) { 84 | return val.sort().map(function (val2) { 85 | return encodeURIComponent(key) + '=' + encodeURIComponent(val2); 86 | }).join('&'); 87 | } 88 | 89 | return encodeURIComponent(key) + '=' + encodeURIComponent(val); 90 | }).join('&') : ''; 91 | }; 92 | 93 | // gets the language set in the query string 94 | function getLanguageFromQueryString() { 95 | if (location.search.length >= 1) { 96 | var language = parseURL(location.search).language 97 | if (language) { 98 | return language; 99 | } else if (jQuery.inArray(location.search.substr(1), languages) != -1) { 100 | return location.search.substr(1); 101 | } 102 | } 103 | 104 | return false; 105 | } 106 | 107 | // returns a new query string with the new language in it 108 | function generateNewQueryString(language) { 109 | var url = parseURL(location.search); 110 | if (url.language) { 111 | url.language = language; 112 | return stringifyURL(url); 113 | } 114 | return language; 115 | } 116 | 117 | // if a button is clicked, add the state to the history 118 | function pushURL(language) { 119 | if (!history) { return; } 120 | var hash = window.location.hash; 121 | if (hash) { 122 | hash = hash.replace(/^#+/, ''); 123 | } 124 | history.pushState({}, '', '?' + generateNewQueryString(language) + '#' + hash); 125 | 126 | // save language as next default 127 | localStorage.setItem("language", language); 128 | } 129 | 130 | function setupLanguages(l) { 131 | var defaultLanguage = localStorage.getItem("language"); 132 | 133 | languages = l; 134 | 135 | var presetLanguage = getLanguageFromQueryString(); 136 | if (presetLanguage) { 137 | // the language is in the URL, so use that language! 138 | activateLanguage(presetLanguage); 139 | 140 | localStorage.setItem("language", presetLanguage); 141 | } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) { 142 | // the language was the last selected one saved in localstorage, so use that language! 143 | activateLanguage(defaultLanguage); 144 | } else { 145 | // no language selected, so use the default 146 | activateLanguage(languages[0]); 147 | } 148 | } 149 | 150 | // if we click on a language tab, activate that language 151 | $(function() { 152 | $(".lang-selector a").on("click", function() { 153 | var language = $(this).data("language-name"); 154 | pushURL(language); 155 | activateLanguage(language); 156 | return false; 157 | }); 158 | window.onpopstate = function() { 159 | activateLanguage(getLanguageFromQueryString()); 160 | }; 161 | }); 162 | })(window); 163 | -------------------------------------------------------------------------------- /docs/source/javascripts/app/_search.js: -------------------------------------------------------------------------------- 1 | //= require ../lib/_lunr 2 | //= require ../lib/_jquery.highlight 3 | (function () { 4 | 'use strict'; 5 | 6 | var content, searchResults; 7 | var highlightOpts = { element: 'span', className: 'search-highlight' }; 8 | 9 | var index = new lunr.Index(); 10 | 11 | index.ref('id'); 12 | index.field('title', { boost: 10 }); 13 | index.field('body'); 14 | index.pipeline.add(lunr.trimmer, lunr.stopWordFilter); 15 | 16 | $(populate); 17 | $(bind); 18 | 19 | function populate() { 20 | $('h1, h2').each(function() { 21 | var title = $(this); 22 | var body = title.nextUntil('h1, h2'); 23 | index.add({ 24 | id: title.prop('id'), 25 | title: title.text(), 26 | body: body.text() 27 | }); 28 | }); 29 | } 30 | 31 | function bind() { 32 | content = $('.content'); 33 | searchResults = $('.search-results'); 34 | 35 | $('#input-search').on('keyup', search); 36 | } 37 | 38 | function search(event) { 39 | unhighlight(); 40 | searchResults.addClass('visible'); 41 | 42 | // ESC clears the field 43 | if (event.keyCode === 27) this.value = ''; 44 | 45 | if (this.value) { 46 | var results = index.search(this.value).filter(function(r) { 47 | return r.score > 0.0001; 48 | }); 49 | 50 | if (results.length) { 51 | searchResults.empty(); 52 | $.each(results, function (index, result) { 53 | var elem = document.getElementById(result.ref); 54 | searchResults.append("
  • " + $(elem).text() + "
  • "); 55 | }); 56 | highlight.call(this); 57 | } else { 58 | searchResults.html('
  • '); 59 | $('.search-results li').text('No Results Found for "' + this.value + '"'); 60 | } 61 | } else { 62 | unhighlight(); 63 | searchResults.removeClass('visible'); 64 | } 65 | } 66 | 67 | function highlight() { 68 | if (this.value) content.highlight(this.value, highlightOpts); 69 | } 70 | 71 | function unhighlight() { 72 | content.unhighlight(highlightOpts); 73 | } 74 | })(); 75 | -------------------------------------------------------------------------------- /docs/source/javascripts/app/_toc.js: -------------------------------------------------------------------------------- 1 | //= require ../lib/_jquery_ui 2 | //= require ../lib/_jquery.tocify 3 | //= require ../lib/_imagesloaded.min 4 | (function (global) { 5 | 'use strict'; 6 | 7 | var closeToc = function() { 8 | $(".tocify-wrapper").removeClass('open'); 9 | $("#nav-button").removeClass('open'); 10 | }; 11 | 12 | var makeToc = function() { 13 | global.toc = $("#toc").tocify({ 14 | selectors: 'h1, h2', 15 | extendPage: false, 16 | theme: 'none', 17 | smoothScroll: false, 18 | showEffectSpeed: 0, 19 | hideEffectSpeed: 180, 20 | ignoreSelector: '.toc-ignore', 21 | highlightOffset: 60, 22 | scrollTo: -1, 23 | scrollHistory: true, 24 | hashGenerator: function (text, element) { 25 | return element.prop('id'); 26 | } 27 | }).data('toc-tocify'); 28 | 29 | $("#nav-button").click(function() { 30 | $(".tocify-wrapper").toggleClass('open'); 31 | $("#nav-button").toggleClass('open'); 32 | return false; 33 | }); 34 | 35 | $(".page-wrapper").click(closeToc); 36 | $(".tocify-item").click(closeToc); 37 | }; 38 | 39 | // Hack to make already open sections to start opened, 40 | // instead of displaying an ugly animation 41 | function animate() { 42 | setTimeout(function() { 43 | toc.setOption('showEffectSpeed', 180); 44 | }, 50); 45 | } 46 | 47 | $(function() { 48 | makeToc(); 49 | animate(); 50 | $('.content').imagesLoaded( function() { 51 | global.toc.calculateHeights(); 52 | }); 53 | }); 54 | })(window); 55 | 56 | -------------------------------------------------------------------------------- /docs/source/javascripts/lib/_energize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * energize.js v0.1.0 3 | * 4 | * Speeds up click events on mobile devices. 5 | * https://github.com/davidcalhoun/energize.js 6 | */ 7 | 8 | (function() { // Sandbox 9 | /** 10 | * Don't add to non-touch devices, which don't need to be sped up 11 | */ 12 | if(!('ontouchstart' in window)) return; 13 | 14 | var lastClick = {}, 15 | isThresholdReached, touchstart, touchmove, touchend, 16 | click, closest; 17 | 18 | /** 19 | * isThresholdReached 20 | * 21 | * Compare touchstart with touchend xy coordinates, 22 | * and only fire simulated click event if the coordinates 23 | * are nearby. (don't want clicking to be confused with a swipe) 24 | */ 25 | isThresholdReached = function(startXY, xy) { 26 | return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5; 27 | }; 28 | 29 | /** 30 | * touchstart 31 | * 32 | * Save xy coordinates when the user starts touching the screen 33 | */ 34 | touchstart = function(e) { 35 | this.startXY = [e.touches[0].clientX, e.touches[0].clientY]; 36 | this.threshold = false; 37 | }; 38 | 39 | /** 40 | * touchmove 41 | * 42 | * Check if the user is scrolling past the threshold. 43 | * Have to check here because touchend will not always fire 44 | * on some tested devices (Kindle Fire?) 45 | */ 46 | touchmove = function(e) { 47 | // NOOP if the threshold has already been reached 48 | if(this.threshold) return false; 49 | 50 | this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]); 51 | }; 52 | 53 | /** 54 | * touchend 55 | * 56 | * If the user didn't scroll past the threshold between 57 | * touchstart and touchend, fire a simulated click. 58 | * 59 | * (This will fire before a native click) 60 | */ 61 | touchend = function(e) { 62 | // Don't fire a click if the user scrolled past the threshold 63 | if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) { 64 | return; 65 | } 66 | 67 | /** 68 | * Create and fire a click event on the target element 69 | * https://developer.mozilla.org/en/DOM/event.initMouseEvent 70 | */ 71 | var touch = e.changedTouches[0], 72 | evt = document.createEvent('MouseEvents'); 73 | evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); 74 | evt.simulated = true; // distinguish from a normal (nonsimulated) click 75 | e.target.dispatchEvent(evt); 76 | }; 77 | 78 | /** 79 | * click 80 | * 81 | * Because we've already fired a click event in touchend, 82 | * we need to listed for all native click events here 83 | * and suppress them as necessary. 84 | */ 85 | click = function(e) { 86 | /** 87 | * Prevent ghost clicks by only allowing clicks we created 88 | * in the click event we fired (look for e.simulated) 89 | */ 90 | var time = Date.now(), 91 | timeDiff = time - lastClick.time, 92 | x = e.clientX, 93 | y = e.clientY, 94 | xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)], 95 | target = closest(e.target, 'A') || e.target, // needed for standalone apps 96 | nodeName = target.nodeName, 97 | isLink = nodeName === 'A', 98 | standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href"); 99 | 100 | lastClick.time = time; 101 | lastClick.x = x; 102 | lastClick.y = y; 103 | 104 | /** 105 | * Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire), 106 | * so we have to add more logic to determine the time of the last click. Not perfect... 107 | * 108 | * Older, simpler check: if((!e.simulated) || standAlone) 109 | */ 110 | if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) { 111 | e.preventDefault(); 112 | e.stopPropagation(); 113 | if(!standAlone) return false; 114 | } 115 | 116 | /** 117 | * Special logic for standalone web apps 118 | * See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window 119 | */ 120 | if(standAlone) { 121 | window.location = target.getAttribute("href"); 122 | } 123 | 124 | /** 125 | * Add an energize-focus class to the targeted link (mimics :focus behavior) 126 | * TODO: test and/or remove? Does this work? 127 | */ 128 | if(!target || !target.classList) return; 129 | target.classList.add("energize-focus"); 130 | window.setTimeout(function(){ 131 | target.classList.remove("energize-focus"); 132 | }, 150); 133 | }; 134 | 135 | /** 136 | * closest 137 | * @param {HTMLElement} node current node to start searching from. 138 | * @param {string} tagName the (uppercase) name of the tag you're looking for. 139 | * 140 | * Find the closest ancestor tag of a given node. 141 | * 142 | * Starts at node and goes up the DOM tree looking for a 143 | * matching nodeName, continuing until hitting document.body 144 | */ 145 | closest = function(node, tagName){ 146 | var curNode = node; 147 | 148 | while(curNode !== document.body) { // go up the dom until we find the tag we're after 149 | if(!curNode || curNode.nodeName === tagName) { return curNode; } // found 150 | curNode = curNode.parentNode; // not found, so keep going up 151 | } 152 | 153 | return null; // not found 154 | }; 155 | 156 | /** 157 | * Add all delegated event listeners 158 | * 159 | * All the events we care about bubble up to document, 160 | * so we can take advantage of event delegation. 161 | * 162 | * Note: no need to wait for DOMContentLoaded here 163 | */ 164 | document.addEventListener('touchstart', touchstart, false); 165 | document.addEventListener('touchmove', touchmove, false); 166 | document.addEventListener('touchend', touchend, false); 167 | document.addEventListener('click', click, true); // TODO: why does this use capture? 168 | 169 | })(); -------------------------------------------------------------------------------- /docs/source/javascripts/lib/_imagesloaded.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * imagesLoaded PACKAGED v3.1.8 3 | * JavaScript is all like "You images are done yet or what?" 4 | * MIT License 5 | */ 6 | 7 | (function(){function e(){}function t(e,t){for(var n=e.length;n--;)if(e[n].listener===t)return n;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var i=e.prototype,r=this,o=r.EventEmitter;i.getListeners=function(e){var t,n,i=this._getEvents();if("object"==typeof e){t={};for(n in i)i.hasOwnProperty(n)&&e.test(n)&&(t[n]=i[n])}else t=i[e]||(i[e]=[]);return t},i.flattenListeners=function(e){var t,n=[];for(t=0;e.length>t;t+=1)n.push(e[t].listener);return n},i.getListenersAsObject=function(e){var t,n=this.getListeners(e);return n instanceof Array&&(t={},t[e]=n),t||n},i.addListener=function(e,n){var i,r=this.getListenersAsObject(e),o="object"==typeof n;for(i in r)r.hasOwnProperty(i)&&-1===t(r[i],n)&&r[i].push(o?n:{listener:n,once:!1});return this},i.on=n("addListener"),i.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},i.once=n("addOnceListener"),i.defineEvent=function(e){return this.getListeners(e),this},i.defineEvents=function(e){for(var t=0;e.length>t;t+=1)this.defineEvent(e[t]);return this},i.removeListener=function(e,n){var i,r,o=this.getListenersAsObject(e);for(r in o)o.hasOwnProperty(r)&&(i=t(o[r],n),-1!==i&&o[r].splice(i,1));return this},i.off=n("removeListener"),i.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},i.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},i.manipulateListeners=function(e,t,n){var i,r,o=e?this.removeListener:this.addListener,s=e?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(i=n.length;i--;)o.call(this,t,n[i]);else for(i in t)t.hasOwnProperty(i)&&(r=t[i])&&("function"==typeof r?o.call(this,i,r):s.call(this,i,r));return this},i.removeEvent=function(e){var t,n=typeof e,i=this._getEvents();if("string"===n)delete i[e];else if("object"===n)for(t in i)i.hasOwnProperty(t)&&e.test(t)&&delete i[t];else delete this._events;return this},i.removeAllListeners=n("removeEvent"),i.emitEvent=function(e,t){var n,i,r,o,s=this.getListenersAsObject(e);for(r in s)if(s.hasOwnProperty(r))for(i=s[r].length;i--;)n=s[r][i],n.once===!0&&this.removeListener(e,n.listener),o=n.listener.apply(this,t||[]),o===this._getOnceReturnValue()&&this.removeListener(e,n.listener);return this},i.trigger=n("emitEvent"),i.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},i.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},i._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},i._getEvents=function(){return this._events||(this._events={})},e.noConflict=function(){return r.EventEmitter=o,e},"function"==typeof define&&define.amd?define("eventEmitter/EventEmitter",[],function(){return e}):"object"==typeof module&&module.exports?module.exports=e:this.EventEmitter=e}).call(this),function(e){function t(t){var n=e.event;return n.target=n.target||n.srcElement||t,n}var n=document.documentElement,i=function(){};n.addEventListener?i=function(e,t,n){e.addEventListener(t,n,!1)}:n.attachEvent&&(i=function(e,n,i){e[n+i]=i.handleEvent?function(){var n=t(e);i.handleEvent.call(i,n)}:function(){var n=t(e);i.call(e,n)},e.attachEvent("on"+n,e[n+i])});var r=function(){};n.removeEventListener?r=function(e,t,n){e.removeEventListener(t,n,!1)}:n.detachEvent&&(r=function(e,t,n){e.detachEvent("on"+t,e[t+n]);try{delete e[t+n]}catch(i){e[t+n]=void 0}});var o={bind:i,unbind:r};"function"==typeof define&&define.amd?define("eventie/eventie",o):e.eventie=o}(this),function(e,t){"function"==typeof define&&define.amd?define(["eventEmitter/EventEmitter","eventie/eventie"],function(n,i){return t(e,n,i)}):"object"==typeof exports?module.exports=t(e,require("wolfy87-eventemitter"),require("eventie")):e.imagesLoaded=t(e,e.EventEmitter,e.eventie)}(window,function(e,t,n){function i(e,t){for(var n in t)e[n]=t[n];return e}function r(e){return"[object Array]"===d.call(e)}function o(e){var t=[];if(r(e))t=e;else if("number"==typeof e.length)for(var n=0,i=e.length;i>n;n++)t.push(e[n]);else t.push(e);return t}function s(e,t,n){if(!(this instanceof s))return new s(e,t);"string"==typeof e&&(e=document.querySelectorAll(e)),this.elements=o(e),this.options=i({},this.options),"function"==typeof t?n=t:i(this.options,t),n&&this.on("always",n),this.getImages(),a&&(this.jqDeferred=new a.Deferred);var r=this;setTimeout(function(){r.check()})}function f(e){this.img=e}function c(e){this.src=e,v[e]=this}var a=e.jQuery,u=e.console,h=u!==void 0,d=Object.prototype.toString;s.prototype=new t,s.prototype.options={},s.prototype.getImages=function(){this.images=[];for(var e=0,t=this.elements.length;t>e;e++){var n=this.elements[e];"IMG"===n.nodeName&&this.addImage(n);var i=n.nodeType;if(i&&(1===i||9===i||11===i))for(var r=n.querySelectorAll("img"),o=0,s=r.length;s>o;o++){var f=r[o];this.addImage(f)}}},s.prototype.addImage=function(e){var t=new f(e);this.images.push(t)},s.prototype.check=function(){function e(e,r){return t.options.debug&&h&&u.log("confirm",e,r),t.progress(e),n++,n===i&&t.complete(),!0}var t=this,n=0,i=this.images.length;if(this.hasAnyBroken=!1,!i)return this.complete(),void 0;for(var r=0;i>r;r++){var o=this.images[r];o.on("confirm",e),o.check()}},s.prototype.progress=function(e){this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded;var t=this;setTimeout(function(){t.emit("progress",t,e),t.jqDeferred&&t.jqDeferred.notify&&t.jqDeferred.notify(t,e)})},s.prototype.complete=function(){var e=this.hasAnyBroken?"fail":"done";this.isComplete=!0;var t=this;setTimeout(function(){if(t.emit(e,t),t.emit("always",t),t.jqDeferred){var n=t.hasAnyBroken?"reject":"resolve";t.jqDeferred[n](t)}})},a&&(a.fn.imagesLoaded=function(e,t){var n=new s(this,e,t);return n.jqDeferred.promise(a(this))}),f.prototype=new t,f.prototype.check=function(){var e=v[this.img.src]||new c(this.img.src);if(e.isConfirmed)return this.confirm(e.isLoaded,"cached was confirmed"),void 0;if(this.img.complete&&void 0!==this.img.naturalWidth)return this.confirm(0!==this.img.naturalWidth,"naturalWidth"),void 0;var t=this;e.on("confirm",function(e,n){return t.confirm(e.isLoaded,n),!0}),e.check()},f.prototype.confirm=function(e,t){this.isLoaded=e,this.emit("confirm",this,t)};var v={};return c.prototype=new t,c.prototype.check=function(){if(!this.isChecked){var e=new Image;n.bind(e,"load",this),n.bind(e,"error",this),e.src=this.src,this.isChecked=!0}},c.prototype.handleEvent=function(e){var t="on"+e.type;this[t]&&this[t](e)},c.prototype.onload=function(e){this.confirm(!0,"onload"),this.unbindProxyEvents(e)},c.prototype.onerror=function(e){this.confirm(!1,"onerror"),this.unbindProxyEvents(e)},c.prototype.confirm=function(e,t){this.isConfirmed=!0,this.isLoaded=e,this.emit("confirm",this,t)},c.prototype.unbindProxyEvents=function(e){n.unbind(e.target,"load",this),n.unbind(e.target,"error",this)},s}); -------------------------------------------------------------------------------- /docs/source/javascripts/lib/_jquery.highlight.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Highlight plugin 3 | * 4 | * Based on highlight v3 by Johann Burkard 5 | * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html 6 | * 7 | * Code a little bit refactored and cleaned (in my humble opinion). 8 | * Most important changes: 9 | * - has an option to highlight only entire words (wordsOnly - false by default), 10 | * - has an option to be case sensitive (caseSensitive - false by default) 11 | * - highlight element tag and class names can be specified in options 12 | * 13 | * Usage: 14 | * // wrap every occurrance of text 'lorem' in content 15 | * // with (default options) 16 | * $('#content').highlight('lorem'); 17 | * 18 | * // search for and highlight more terms at once 19 | * // so you can save some time on traversing DOM 20 | * $('#content').highlight(['lorem', 'ipsum']); 21 | * $('#content').highlight('lorem ipsum'); 22 | * 23 | * // search only for entire word 'lorem' 24 | * $('#content').highlight('lorem', { wordsOnly: true }); 25 | * 26 | * // don't ignore case during search of term 'lorem' 27 | * $('#content').highlight('lorem', { caseSensitive: true }); 28 | * 29 | * // wrap every occurrance of term 'ipsum' in content 30 | * // with 31 | * $('#content').highlight('ipsum', { element: 'em', className: 'important' }); 32 | * 33 | * // remove default highlight 34 | * $('#content').unhighlight(); 35 | * 36 | * // remove custom highlight 37 | * $('#content').unhighlight({ element: 'em', className: 'important' }); 38 | * 39 | * 40 | * Copyright (c) 2009 Bartek Szopka 41 | * 42 | * Licensed under MIT license. 43 | * 44 | */ 45 | 46 | jQuery.extend({ 47 | highlight: function (node, re, nodeName, className) { 48 | if (node.nodeType === 3) { 49 | var match = node.data.match(re); 50 | if (match) { 51 | var highlight = document.createElement(nodeName || 'span'); 52 | highlight.className = className || 'highlight'; 53 | var wordNode = node.splitText(match.index); 54 | wordNode.splitText(match[0].length); 55 | var wordClone = wordNode.cloneNode(true); 56 | highlight.appendChild(wordClone); 57 | wordNode.parentNode.replaceChild(highlight, wordNode); 58 | return 1; //skip added node in parent 59 | } 60 | } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children 61 | !/(script|style)/i.test(node.tagName) && // ignore script and style nodes 62 | !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted 63 | for (var i = 0; i < node.childNodes.length; i++) { 64 | i += jQuery.highlight(node.childNodes[i], re, nodeName, className); 65 | } 66 | } 67 | return 0; 68 | } 69 | }); 70 | 71 | jQuery.fn.unhighlight = function (options) { 72 | var settings = { className: 'highlight', element: 'span' }; 73 | jQuery.extend(settings, options); 74 | 75 | return this.find(settings.element + "." + settings.className).each(function () { 76 | var parent = this.parentNode; 77 | parent.replaceChild(this.firstChild, this); 78 | parent.normalize(); 79 | }).end(); 80 | }; 81 | 82 | jQuery.fn.highlight = function (words, options) { 83 | var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false }; 84 | jQuery.extend(settings, options); 85 | 86 | if (words.constructor === String) { 87 | words = [words]; 88 | } 89 | words = jQuery.grep(words, function(word, i){ 90 | return word != ''; 91 | }); 92 | words = jQuery.map(words, function(word, i) { 93 | return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 94 | }); 95 | if (words.length == 0) { return this; }; 96 | 97 | var flag = settings.caseSensitive ? "" : "i"; 98 | var pattern = "(" + words.join("|") + ")"; 99 | if (settings.wordsOnly) { 100 | pattern = "\\b" + pattern + "\\b"; 101 | } 102 | var re = new RegExp(pattern, flag); 103 | 104 | return this.each(function () { 105 | jQuery.highlight(this, re, settings.element, settings.className); 106 | }); 107 | }; 108 | 109 | -------------------------------------------------------------------------------- /docs/source/layouts/layout.erb: -------------------------------------------------------------------------------- 1 | <%# 2 | Copyright 2008-2013 Concur Technologies, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | License for the specific language governing permissions and limitations 14 | under the License. 15 | %> 16 | <% language_tabs = current_page.data.language_tabs %> 17 | 18 | 19 | 20 | 21 | 22 | 23 | <%= current_page.data.title || "API Documentation" %> 24 | 25 | 26 | 27 | <%= stylesheet_link_tag :screen, media: :screen %> 28 | <%= stylesheet_link_tag :print, media: :print %> 29 | 30 | <% if current_page.data.search %> 31 | <%= javascript_include_tag "all" %> 32 | <% else %> 33 | <%= javascript_include_tag "all_nosearch" %> 34 | <% end %> 35 | 36 | <% if language_tabs %> 37 | 42 | <% end %> 43 | 44 | 45 | 46 | 47 | 48 | NAV 49 | <%= image_tag('navbar.png') %> 50 | 51 | 52 |
    53 | 54 | 55 | 59 | 60 | <% if language_tabs %> 61 |
    62 | <% language_tabs.each do |lang| %> 63 | <% if lang.is_a? Hash %> 64 | <%= lang.values.first %> 65 | <% else %> 66 | <%= lang %> 67 | <% end %> 68 | <% end %> 69 |
    70 | <% end %> 71 | <% if current_page.data.search %> 72 | 75 |
      76 | <% end %> 77 |
      78 |
      79 | <% if current_page.data.toc_footers %> 80 | 85 | <% end %> 86 |
      87 |
      88 |
      89 |
      90 | <%= yield %> 91 | <% current_page.data.includes && current_page.data.includes.each do |include| %> 92 | <%= partial "includes/#{include}" %> 93 | <% end %> 94 |
      95 |
      96 | <% if language_tabs %> 97 |
      98 | <% language_tabs.each do |lang| %> 99 | <% if lang.is_a? Hash %> 100 | <%= lang.values.first %> 101 | <% else %> 102 | <%= lang %> 103 | <% end %> 104 | <% end %> 105 |
      106 | <% end %> 107 |
      108 |
      109 | 110 | 111 | -------------------------------------------------------------------------------- /docs/source/stylesheets/_icon-font.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'slate'; 3 | src:font-url('slate.eot?-syv14m'); 4 | src:font-url('slate.eot?#iefix-syv14m') format('embedded-opentype'), 5 | font-url('slate.woff2?-syv14m') format('woff2'), 6 | font-url('slate.woff?-syv14m') format('woff'), 7 | font-url('slate.ttf?-syv14m') format('truetype'), 8 | font-url('slate.svg?-syv14m#slate') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | %icon { 14 | font-family: 'slate'; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | } 22 | 23 | %icon-exclamation-sign { 24 | @extend %icon; 25 | content: "\e600"; 26 | } 27 | %icon-info-sign { 28 | @extend %icon; 29 | content: "\e602"; 30 | } 31 | %icon-ok-sign { 32 | @extend %icon; 33 | content: "\e606"; 34 | } 35 | %icon-search { 36 | @extend %icon; 37 | content: "\e607"; 38 | } 39 | -------------------------------------------------------------------------------- /docs/source/stylesheets/_syntax.scss.erb: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2008-2013 Concur Technologies, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | License for the specific language governing permissions and limitations 14 | under the License. 15 | */ 16 | 17 | @import 'variables'; 18 | 19 | <%= Rouge::Themes::Base16::Monokai.render(:scope => '.highlight') %> 20 | 21 | .highlight .c, .highlight .cm, .highlight .c1, .highlight .cs { 22 | color: #909090; 23 | } 24 | 25 | .highlight, .highlight .w { 26 | background-color: $code-bg; 27 | } -------------------------------------------------------------------------------- /docs/source/stylesheets/_variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2008-2013 Concur Technologies, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | License for the specific language governing permissions and limitations 14 | under the License. 15 | */ 16 | 17 | 18 | //////////////////////////////////////////////////////////////////////////////// 19 | // CUSTOMIZE SLATE 20 | //////////////////////////////////////////////////////////////////////////////// 21 | // Use these settings to help adjust the appearance of Slate 22 | 23 | 24 | // BACKGROUND COLORS 25 | //////////////////// 26 | $nav-bg: #393939; 27 | $examples-bg: #393939; 28 | $code-bg: #292929; 29 | $code-annotation-bg: #1c1c1c; 30 | $nav-subitem-bg: #262626; 31 | $nav-active-bg: #2467af; 32 | $lang-select-border: #000; 33 | $lang-select-bg: #222; 34 | $lang-select-active-bg: $examples-bg; // feel free to change this to blue or something 35 | $lang-select-pressed-bg: #111; // color of language tab bg when mouse is pressed 36 | $main-bg: #eaf2f6; 37 | $aside-notice-bg: #8fbcd4; 38 | $aside-warning-bg: #c97a7e; 39 | $aside-success-bg: #6ac174; 40 | $search-notice-bg: #c97a7e; 41 | 42 | 43 | // TEXT COLORS 44 | //////////////////// 45 | $main-text: #333; // main content text color 46 | $nav-text: #fff; 47 | $nav-active-text: #fff; 48 | $lang-select-text: #fff; // color of unselected language tab text 49 | $lang-select-active-text: #fff; // color of selected language tab text 50 | $lang-select-pressed-text: #fff; // color of language tab text when mouse is pressed 51 | 52 | 53 | // SIZES 54 | //////////////////// 55 | $nav-width: 250px; // width of the navbar 56 | $examples-width: 50%; // portion of the screen taken up by code examples 57 | $logo-margin: 20px; // margin between nav items and logo, ignored if search is active 58 | $main-padding: 28px; // padding to left and right of content & examples 59 | $nav-padding: 15px; // padding to left and right of navbar 60 | $nav-v-padding: 10px; // padding used vertically around search boxes and results 61 | $nav-indent: 10px; // extra padding for ToC subitems 62 | $code-annotation-padding: 13px; // padding inside code annotations 63 | $h1-margin-bottom: 21px; // padding under the largest header tags 64 | $tablet-width: 930px; // min width before reverting to tablet size 65 | $phone-width: $tablet-width - $nav-width; // min width before reverting to mobile size 66 | 67 | 68 | // FONTS 69 | //////////////////// 70 | %default-font { 71 | font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei","微软雅黑", STXihei, "华文细黑", sans-serif; 72 | font-size: 13px; 73 | } 74 | 75 | %header-font { 76 | @extend %default-font; 77 | font-weight: bold; 78 | } 79 | 80 | %code-font { 81 | font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif; 82 | font-size: 12px; 83 | line-height: 1.5; 84 | } 85 | 86 | 87 | // OTHER 88 | //////////////////// 89 | $nav-active-shadow: #000; 90 | $nav-footer-border-color: #666; 91 | $nav-embossed-border-top: #000; 92 | $nav-embossed-border-bottom: #939393; 93 | $main-embossed-text-shadow: 0px 1px 0px #fff; 94 | $search-box-border-color: #666; 95 | 96 | 97 | //////////////////////////////////////////////////////////////////////////////// 98 | // INTERNAL 99 | //////////////////////////////////////////////////////////////////////////////// 100 | // These settings are probably best left alone. 101 | 102 | %break-words { 103 | word-break: break-all; 104 | 105 | /* Non standard for webkit */ 106 | word-break: break-word; 107 | 108 | hyphens: auto; 109 | } 110 | -------------------------------------------------------------------------------- /docs/source/stylesheets/print.css.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | @import 'normalize'; 3 | @import 'compass'; 4 | @import 'variables'; 5 | @import 'icon-font'; 6 | 7 | /* 8 | Copyright 2008-2013 Concur Technologies, Inc. 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); you may 11 | not use this file except in compliance with the License. You may obtain 12 | a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 | License for the specific language governing permissions and limitations 20 | under the License. 21 | */ 22 | 23 | $print-color: #999; 24 | $print-color-light: #ccc; 25 | $print-font-size: 12px; 26 | 27 | body { 28 | @extend %default-font; 29 | } 30 | 31 | .tocify, .toc-footer, .lang-selector, .search, #nav-button { 32 | display: none; 33 | } 34 | 35 | .tocify-wrapper>img { 36 | margin: 0 auto; 37 | display: block; 38 | } 39 | 40 | .content { 41 | font-size: 12px; 42 | 43 | pre, code { 44 | @extend %code-font; 45 | @extend %break-words; 46 | border: 1px solid $print-color; 47 | border-radius: 5px; 48 | font-size: 0.8em; 49 | } 50 | 51 | pre { 52 | padding: 1.3em; 53 | } 54 | 55 | code { 56 | padding: 0.2em; 57 | } 58 | 59 | table { 60 | border: 1px solid $print-color; 61 | tr { 62 | border-bottom: 1px solid $print-color; 63 | } 64 | td,th { 65 | padding: 0.7em; 66 | } 67 | } 68 | 69 | p { 70 | line-height: 1.5; 71 | } 72 | 73 | a { 74 | text-decoration: none; 75 | color: #000; 76 | } 77 | 78 | h1 { 79 | @extend %header-font; 80 | font-size: 2.5em; 81 | padding-top: 0.5em; 82 | padding-bottom: 0.5em; 83 | margin-top: 1em; 84 | margin-bottom: $h1-margin-bottom; 85 | border: 2px solid $print-color-light; 86 | border-width: 2px 0; 87 | text-align: center; 88 | } 89 | 90 | h2 { 91 | @extend %header-font; 92 | font-size: 1.8em; 93 | margin-top: 2em; 94 | border-top: 2px solid $print-color-light; 95 | padding-top: 0.8em; 96 | } 97 | 98 | h1+h2, h1+div+h2 { 99 | border-top: none; 100 | padding-top: 0; 101 | margin-top: 0; 102 | } 103 | 104 | h3, h4 { 105 | @extend %header-font; 106 | font-size: 0.8em; 107 | margin-top: 1.5em; 108 | margin-bottom: 0.8em; 109 | text-transform: uppercase; 110 | } 111 | 112 | h5, h6 { 113 | text-transform: uppercase; 114 | } 115 | 116 | aside { 117 | padding: 1em; 118 | border: 1px solid $print-color-light; 119 | border-radius: 5px; 120 | margin-top: 1.5em; 121 | margin-bottom: 1.5em; 122 | line-height: 1.6; 123 | } 124 | 125 | aside:before { 126 | vertical-align: middle; 127 | padding-right: 0.5em; 128 | font-size: 14px; 129 | } 130 | 131 | aside.notice:before { 132 | @extend %icon-info-sign; 133 | } 134 | 135 | aside.warning:before { 136 | @extend %icon-exclamation-sign; 137 | } 138 | 139 | aside.success:before { 140 | @extend %icon-ok-sign; 141 | } 142 | } -------------------------------------------------------------------------------- /lib/solidus_json_api.rb: -------------------------------------------------------------------------------- 1 | require 'solidus_core' 2 | require 'active_model_serializers' 3 | 4 | require 'solidus_json_api/config' 5 | require 'solidus_json_api/engine' 6 | -------------------------------------------------------------------------------- /lib/solidus_json_api/config.rb: -------------------------------------------------------------------------------- 1 | module SolidusJsonApi 2 | mattr_accessor :parent_serializer 3 | @@parent_serializer = ActiveModel::Serializer 4 | 5 | def self.setup 6 | yield self 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/solidus_json_api/engine.rb: -------------------------------------------------------------------------------- 1 | module SolidusJsonApi 2 | class Engine < Rails::Engine 3 | require 'spree/core' 4 | 5 | isolate_namespace Spree 6 | engine_name 'solidus_json_api' 7 | 8 | # use rspec for tests 9 | config.generators do |g| 10 | g.test_framework :rspec 11 | end 12 | 13 | def self.activate 14 | Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c| 15 | Rails.configuration.cache_classes ? require(c) : load(c) 16 | end 17 | end 18 | 19 | config.to_prepare &method(:activate).to_proc 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /solidus_json_api.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | Gem::Specification.new do |s| 3 | s.platform = Gem::Platform::RUBY 4 | s.name = 'solidus_json_api' 5 | s.version = '0.3.1' 6 | s.summary = 'A JSON API for Solidus.' 7 | s.description = 'Adds an assortment of new api endpoints that are JSON API compatible.' 8 | s.required_ruby_version = '>= 2.0.0' 9 | 10 | s.author = 'Ben A. Morgan' 11 | s.email = 'ben@benmorgan.io' 12 | s.homepage = 'http://solidusapi.wildcardlabs.com' 13 | 14 | s.files = `git ls-files`.split("\n") 15 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 16 | s.require_path = 'lib' 17 | s.requirements << 'none' 18 | 19 | s.add_dependency 'solidus_api', '~> 1.0' 20 | s.add_dependency 'active_model_serializers', '= 0.10.0.rc4' 21 | 22 | s.add_development_dependency 'coffee-rails' 23 | s.add_development_dependency 'coveralls' 24 | s.add_development_dependency 'database_cleaner' 25 | s.add_development_dependency 'factory_girl', '~> 4.5' 26 | s.add_development_dependency 'ffaker' 27 | s.add_development_dependency 'rspec-rails', '~> 3.3' 28 | s.add_development_dependency 'rspec-its' 29 | s.add_development_dependency 'sass-rails', '~> 5.0.0.beta1' 30 | s.add_development_dependency 'simplecov' 31 | s.add_development_dependency 'sqlite3' 32 | s.add_development_dependency 'pg' 33 | s.add_development_dependency 'pry' 34 | s.add_development_dependency 'json_spec', '~> 1.1.4' 35 | s.add_development_dependency 'shoulda-matchers', '~> 3.0.0' 36 | end 37 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/base_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::BaseController do 2 | controller do 3 | skip_before_action :authenticate_user 4 | 5 | def index 6 | render_collection Spree::Order 7 | end 8 | end 9 | 10 | describe '#filter_params' do 11 | before do 12 | allow(controller).to receive(:serializer_attributes) { Spree::OrderSerializer._attributes } 13 | end 14 | 15 | let!(:orders) do 16 | [create(:order), create(:order), create(:order, frontend_viewable: false)] 17 | end 18 | 19 | it 'can filter by a serializer\'s attribute' do 20 | first_order = orders.first 21 | get :index, filter: { id: first_order.id } 22 | order_ids = parse_json(response.body)['data'].map do |order| 23 | order['id'] 24 | end 25 | expect(order_ids).to match [first_order.id.to_s] 26 | end 27 | 28 | it 'can filter multiple attributes' do 29 | first_order, second_order, _third_order = orders 30 | get :index, filter: { id: [first_order.id, second_order.id] } 31 | order_ids = parse_json(response.body)['data'].map do |order| 32 | order['id'].to_i 33 | end 34 | 35 | expect(order_ids).to match [first_order.id, second_order.id] 36 | end 37 | 38 | it 'will allow filtering of non-serializer attributes' do 39 | get :index, filter: { frontend_viewable: false } 40 | order_ids = parse_json(response.body)['data'].map do |order| 41 | order['id'].to_i 42 | end 43 | expect(order_ids).to match orders.map(&:id) 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/children_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::ChildrenController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let(:taxon) { create :taxon } 5 | let!(:child) { create :taxon, parent: taxon, taxonomy: taxon.taxonomy } 6 | 7 | describe '#index' do 8 | context 'by taxon' do 9 | it 'can list all the children' do 10 | get :index, taxon_id: taxon.id 11 | taxon_ids = parse_json(response.body)['data'].map do |taxon| 12 | taxon['id'] 13 | end 14 | expect(taxon_ids).to include child.id.to_s 15 | end 16 | end 17 | end 18 | 19 | describe '#show' do 20 | context 'by taxon' do 21 | it 'will fetch the selected child' do 22 | get :show, taxon_id: taxon.id, id: child.id 23 | child_id = parse_json(response.body)['data']['id'] 24 | expect(child_id).to eql child.id.to_s 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/countries_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::CountriesController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:country) { create :country } 5 | let(:state) { create :state, country: country } 6 | 7 | describe '#index' do 8 | it 'will list all countries' do 9 | get :index 10 | expect(JSON.parse(response.body)['data'].length).to eql 1 11 | end 12 | end 13 | 14 | describe '#show' do 15 | it 'will list the selected country' do 16 | get :show, id: country.id 17 | expect(JSON.parse(response.body)['data']['id']).to eql country.id.to_s 18 | end 19 | 20 | it 'will the country via a state' do 21 | get :show, state_id: state.id 22 | expect(JSON.parse(response.body)['data']['id']).to eql country.id.to_s 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/images_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::ImagesController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:image) { create :image } 5 | 6 | let(:product) { create :product } 7 | let(:new_product) { create :product } 8 | 9 | let(:variant) { create :variant, images: [image] } 10 | let(:new_variant) { create :variant, images: [create(:image)] } 11 | 12 | describe '#index' do 13 | it 'will list the images' do 14 | get :index 15 | image_ids = parse_json(response.body)['data'].map do |image| 16 | image['id'] 17 | end 18 | expect(image_ids).to include image.id.to_s 19 | end 20 | 21 | context 'by product id' do 22 | it 'will list its images' do 23 | product.images << image 24 | get :index, product_id: product.id 25 | image_ids = parse_json(response.body)['data'].map do |image| 26 | image['id'] 27 | end 28 | expect(image_ids).to include image.id.to_s 29 | end 30 | 31 | it 'will not list another products images' do 32 | get :index, product_id: new_product.id 33 | data = parse_json(response.body)['data'] 34 | expect(data).to be_empty 35 | end 36 | end 37 | 38 | context 'by variant id' do 39 | it 'will list its images' do 40 | get :index, variant_id: variant.id 41 | image_ids = parse_json(response.body)['data'].map do |image| 42 | image['id'] 43 | end 44 | expect(image_ids).to include image.id.to_s 45 | end 46 | 47 | it 'will not list another variants images' do 48 | get :index, variant_id: new_variant.id 49 | image_ids = parse_json(response.body)['data'].map do |image| 50 | image['id'] 51 | end 52 | expect(image_ids).to_not include image.id.to_s 53 | end 54 | end 55 | end 56 | 57 | describe '#show' do 58 | it 'will show the image' do 59 | get :show, id: image.id 60 | image_id = parse_json(response.body)['data']['id'] 61 | expect(image_id).to eql image.id.to_s 62 | end 63 | 64 | context 'by product id' do 65 | it 'will show its image' do 66 | product.images << image 67 | get :show, id: image.id, product_id: product.id 68 | image_id = parse_json(response.body)['data']['id'] 69 | expect(image_id).to eql image.id.to_s 70 | end 71 | 72 | it 'will not show another products image' do 73 | get :show, id: image.id, product_id: new_product.id 74 | expect(response).to have_http_status 404 75 | end 76 | end 77 | 78 | context 'by variant id' do 79 | it 'will show its image' do 80 | get :show, id: image.id, variant_id: variant.id 81 | image_id = parse_json(response.body)['data']['id'] 82 | expect(image_id).to eql image.id.to_s 83 | end 84 | 85 | it 'will not show another products image' do 86 | get :show, id: image.id, variant_id: new_variant.id 87 | expect(response).to have_http_status 404 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/line_items_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::LineItemsController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let(:order) { create :order } 5 | let(:user) { order.user } 6 | let(:variant) { create :variant } 7 | 8 | before { user.generate_spree_api_key! } 9 | 10 | describe '#create' do 11 | let(:line_item_data) do 12 | { 13 | attributes: { 14 | order_id: order.id, 15 | variant_id: variant.id, 16 | quantity: 1 17 | } 18 | } 19 | end 20 | 21 | context 'on success' do 22 | it 'will respond with line item' do 23 | post :create, token: user.spree_api_key, data: line_item_data 24 | expect(JSON.parse(response.body)['data']['type']).to eql 'spree_line_items' 25 | end 26 | end 27 | 28 | context 'when out of range' do 29 | before do 30 | line_item_data[:attributes][:quantity] = 123_123_123_123 31 | post :create, token: user.spree_api_key, data: line_item_data 32 | end 33 | 34 | it 'will respond with quantity too high error' do 35 | expect(response.body).to be_json_eql <<-JSON 36 | { 37 | "errors" : [ 38 | { 39 | "code": "400", 40 | "detail" : #{Spree.t('api.errors.quantity_too_high.detail').gsub("\n", '').to_json}, 41 | "meta" : {}, 42 | "status": "Bad Request", 43 | "title" : #{Spree.t('api.errors.quantity_too_high.title').titleize.to_json} 44 | } 45 | ] 46 | } 47 | JSON 48 | end 49 | end 50 | 51 | context 'when record not found' do 52 | before do 53 | line_item_data[:attributes][:order_id] = 0 54 | post :create, token: user.spree_api_key, data: line_item_data 55 | end 56 | 57 | it 'will respond with record not found error' do 58 | expect(response.body).to be_json_eql <<-JSON 59 | { 60 | "errors" : [ 61 | { 62 | "code": "400", 63 | "detail" : #{Spree.t('api.errors.record_not_found.detail').gsub("\n", '').to_json}, 64 | "meta" : {}, 65 | "status": "Bad Request", 66 | "title" : #{Spree.t('api.errors.record_not_found.title').to_json} 67 | } 68 | ] 69 | } 70 | JSON 71 | end 72 | end 73 | 74 | context 'when record is invalid' do 75 | let(:stock_item) { variant.stock_items.first } 76 | 77 | before do 78 | quantity = stock_item.count_on_hand + 1 79 | stock_item.update(backorderable: false) 80 | line_item_data[:attributes][:quantity] = quantity 81 | post :create, token: user.spree_api_key, data: line_item_data 82 | end 83 | 84 | it 'will respond with product out of stock' do 85 | expect(response.body).to be_json_eql <<-JSON 86 | { 87 | "errors" : [ 88 | { 89 | "code": "400", 90 | "detail" : "#{Spree.t('api.errors.product_out_of_stock.detail').gsub("\n", '')}", 91 | "meta" : {}, 92 | "status": "Bad Request", 93 | "title" : #{Spree.t('api.errors.product_out_of_stock.title').titleize.to_json} 94 | } 95 | ] 96 | } 97 | JSON 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/option_types_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::OptionTypesController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:option_type) { create :option_type } 5 | let(:product) { create :product, option_types: [option_type] } 6 | let(:new_product) { create :product } 7 | 8 | describe '#index' do 9 | it 'will list all option types' do 10 | get :index 11 | option_type_ids = parse_json(response.body)['data'].map do |option_type| 12 | option_type['id'] 13 | end 14 | expect(option_type_ids).to include option_type.id.to_s 15 | end 16 | 17 | context 'by product' do 18 | it 'will list option types' do 19 | get :index, product_id: product.id 20 | option_type_ids = parse_json(response.body)['data'].map do |option_type| 21 | option_type['id'] 22 | end 23 | expect(option_type_ids).to include option_type.id.to_s 24 | end 25 | 26 | it 'will not list option types belonging to a different product' do 27 | get :index, product_id: new_product.id 28 | data = parse_json(response.body)['data'] 29 | expect(data).to be_empty 30 | end 31 | end 32 | end 33 | 34 | describe '#show' do 35 | it 'will find selected option_type by id' do 36 | get :show, id: option_type.id 37 | option_type_id = parse_json(response.body)['data']['id'] 38 | expect(option_type_id).to eql option_type.id.to_s 39 | end 40 | 41 | context 'by product' do 42 | it 'will list option types' do 43 | get :show, id: option_type.id, product_id: product.id 44 | option_type_id = parse_json(response.body)['data']['id'] 45 | expect(option_type_id).to eql option_type.id.to_s 46 | end 47 | 48 | it 'will not list option types belonging to a different product' do 49 | get :show, id: option_type.id, product_id: new_product.id 50 | expect(response).to have_http_status 404 51 | end 52 | end 53 | 54 | context 'by option value' do 55 | let(:option_value) { create :option_value, option_type: option_type } 56 | let(:new_option_value) { create :option_value } 57 | 58 | it 'will list option types' do 59 | get :show, option_value_id: option_value.id 60 | option_type_id = parse_json(response.body)['data']['id'] 61 | expect(option_type_id).to eql option_type.id.to_s 62 | end 63 | 64 | it 'will not list option types belonging to a different option value' do 65 | get :show, option_value_id: new_option_value.id 66 | option_type_id = parse_json(response.body)['data']['id'] 67 | expect(option_type_id).to_not eql option_type.id.to_s 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/option_values_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::OptionValuesController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:option_value) { create :option_value } 5 | let(:new_option_value) { create :option_value } 6 | let(:variant) { create :variant, option_values: [option_value] } 7 | let(:new_variant) { create :variant } 8 | 9 | describe '#index' do 10 | it 'will list all option values' do 11 | get :index 12 | option_value_ids = parse_json(response.body)['data'].map do |option_value| 13 | option_value['id'] 14 | end 15 | expect(option_value_ids).to include option_value.id.to_s 16 | end 17 | 18 | context 'by option type' do 19 | it 'will list all option values' do 20 | get :index, option_type_id: option_value.option_type.id 21 | option_value_ids = parse_json(response.body)['data'].map do |option_value| 22 | option_value['id'] 23 | end 24 | expect(option_value_ids).to include option_value.id.to_s 25 | end 26 | 27 | it 'will not list option value belonging to a different option type' do 28 | get :index, option_type_id: new_option_value.option_type.id 29 | option_value_ids = parse_json(response.body)['data'].map do |option_value| 30 | option_value['id'] 31 | end 32 | expect(option_value_ids).to_not include option_value.id.to_s 33 | end 34 | end 35 | 36 | context 'by variant' do 37 | it 'will list all option values' do 38 | get :index, variant_id: variant.id 39 | option_value_ids = parse_json(response.body)['data'].map do |option_value| 40 | option_value['id'] 41 | end 42 | expect(option_value_ids).to include option_value.id.to_s 43 | end 44 | 45 | it 'will not list option value belonging to a different variant' do 46 | get :index, variant_id: new_variant.id 47 | option_value_ids = parse_json(response.body)['data'].map do |option_value| 48 | option_value['id'] 49 | end 50 | expect(option_value_ids).to_not include option_value.id.to_s 51 | end 52 | end 53 | end 54 | 55 | describe '#show' do 56 | it 'will find selected option value by id' do 57 | get :show, id: option_value.id 58 | option_value_id = parse_json(response.body)['data']['id'] 59 | expect(option_value_id).to eql option_value.id.to_s 60 | end 61 | 62 | context 'by option type' do 63 | it 'will find option value by option type' do 64 | get :show, option_type_id: option_value.option_type.id, id: option_value.id 65 | option_value_id = parse_json(response.body)['data']['id'] 66 | expect(option_value_id).to eql option_value.id.to_s 67 | end 68 | 69 | it 'will not find option value belonging to a different option type' do 70 | get :show, option_type_id: new_option_value.option_type.id, id: option_value.id 71 | expect(response).to have_http_status 404 72 | end 73 | end 74 | 75 | context 'by variant' do 76 | it 'will find option value by option type' do 77 | get :show, variant_id: variant.id, id: option_value.id 78 | option_value_id = parse_json(response.body)['data']['id'] 79 | expect(option_value_id).to eql option_value.id.to_s 80 | end 81 | 82 | it 'will not find option value belonging to a different option type' do 83 | get :show, variant_id: new_variant.id, id: option_value.id 84 | expect(response).to have_http_status 404 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/orders_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::OrdersController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let(:order) { create :order } 5 | let(:user) { order.user } 6 | let(:admin) { create :admin_user } 7 | 8 | before { user.generate_spree_api_key! } 9 | 10 | describe '#index' do 11 | context 'when admin' do 12 | before { admin.generate_spree_api_key! } 13 | 14 | it 'will list all orders' do 15 | get :index, token: admin.spree_api_key 16 | ids = JSON.parse(response.body)['data'].map { |d| d['id'].to_i } 17 | expect(ids).to include order.id 18 | end 19 | end 20 | 21 | context 'when user' do 22 | let!(:new_order) { create :order } 23 | 24 | before { get :index, token: user.spree_api_key } 25 | 26 | it 'will not list another users order' do 27 | ids = JSON.parse(response.body)['data'].map { |d| d['id'].to_i } 28 | expect(ids).to_not include new_order.id 29 | end 30 | 31 | it 'will list the current users orders' do 32 | ids = JSON.parse(response.body)['data'].map { |d| d['id'].to_i } 33 | expect(ids).to include order.id 34 | end 35 | end 36 | end 37 | 38 | describe '#show' do 39 | context 'when admin' do 40 | before { admin.generate_spree_api_key! } 41 | 42 | it 'can respond with another users order' do 43 | get :show, token: admin.spree_api_key, id: order.id 44 | expect(JSON.parse(response.body)['data']['type']).to eql 'spree_orders' 45 | end 46 | end 47 | 48 | context 'when user' do 49 | it 'will not respond with another users order' do 50 | new_order = create :order 51 | get :show, token: user.spree_api_key, id: new_order.id 52 | expect(response).to have_http_status 404 53 | end 54 | 55 | it 'will respond with the current users order' do 56 | get :show, token: user.spree_api_key, id: order.id 57 | expect(JSON.parse(response.body)['data']['id'].to_i).to eql order.id 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/prices_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::PricesController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:price) { create :price } 5 | let(:new_variant) { create :variant } 6 | 7 | describe '#index' do 8 | it 'will list all prices' do 9 | get :index 10 | price_ids = parse_json(response.body)['data'].map do |price| 11 | price['id'] 12 | end 13 | expect(price_ids).to include price.id.to_s 14 | end 15 | 16 | context 'by variant id' do 17 | it 'will list its prices' do 18 | get :index, variant_id: price.variant.id 19 | price_ids = parse_json(response.body)['data'].map do |price| 20 | price['id'] 21 | end 22 | expect(price_ids).to include price.id.to_s 23 | end 24 | 25 | it 'will not list another variants prices' do 26 | get :index, variant_id: new_variant.id 27 | price_ids = parse_json(response.body)['data'].map do |price| 28 | price['id'] 29 | end 30 | expect(price_ids).to_not include price.id.to_s 31 | end 32 | end 33 | end 34 | 35 | describe '#show' do 36 | it 'will show the price' do 37 | get :show, id: price.id 38 | price_id = parse_json(response.body)['data']['id'] 39 | expect(price_id).to eql price.id.to_s 40 | end 41 | 42 | context 'by variant id' do 43 | it 'will show its prices' do 44 | get :show, id: price.id, variant_id: price.variant.id 45 | price_id = parse_json(response.body)['data']['id'] 46 | expect(price_id).to eql price.id.to_s 47 | end 48 | 49 | it 'will not show another variants price' do 50 | get :show, id: price.id, variant_id: new_variant.id 51 | expect(response).to have_http_status 404 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/products_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::ProductsController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:product) { create :product } 5 | let(:taxon) { create :taxon, products: [product] } 6 | let(:new_taxon) { create :taxon } 7 | 8 | describe '#index' do 9 | it 'will respond with products' do 10 | get :index 11 | expect(JSON.parse(response.body)['data'].size).to eql 1 12 | end 13 | 14 | context 'by taxon id' do 15 | it 'will respond with the taxons products' do 16 | get :index, taxon_id: taxon.id 17 | product_ids = parse_json(response.body)['data'].map do |products| 18 | products['id'] 19 | end 20 | expect(product_ids).to include product.id.to_s 21 | end 22 | 23 | it 'will not respond with a product that is not associated to the taxon' do 24 | get :index, taxon_id: new_taxon.id 25 | data = parse_json(response.body)['data'] 26 | expect(data).to be_empty 27 | end 28 | end 29 | end 30 | 31 | describe '#show' do 32 | it 'will render the selected product' do 33 | get :show, id: product.id 34 | expect(parse_json(response.body)['data']['id']).to eql product.id.to_s 35 | end 36 | 37 | it 'can find a product by slug' do 38 | get :show, id: product.slug 39 | product_id = parse_json(response.body)['data']['id'] 40 | expect(product_id).to eql product.id.to_s 41 | end 42 | 43 | context 'by taxon id' do 44 | it 'will respond with the taxons products' do 45 | get :show, taxon_id: taxon.id, id: product.id 46 | product_id = parse_json(response.body)['data']['id'] 47 | expect(product_id).to eql product.id.to_s 48 | end 49 | 50 | it 'will not respond with a product that is not associated to the taxon' do 51 | get :show, taxon_id: new_taxon.id, id: product.id 52 | expect(response).to have_http_status 404 53 | end 54 | end 55 | 56 | context 'by option type' do 57 | let(:option_type) { create :option_type, products: [product] } 58 | let(:new_option_type) { create :option_type } 59 | 60 | it 'will find product' do 61 | get :show, option_type_id: option_type.id, id: product.id 62 | product_id = parse_json(response.body)['data']['id'] 63 | expect(product_id).to eql product.id.to_s 64 | end 65 | 66 | it 'will not find a product belong to a different option type' do 67 | get :show, option_type_id: new_option_type.id, id: product.id 68 | expect(response).to have_http_status 404 69 | end 70 | end 71 | 72 | context 'by variant id' do 73 | it 'will show its product' do 74 | get :show, variant_id: product.master.id 75 | product_id = parse_json(response.body)['data']['id'] 76 | expect(product_id).to eql product.id.to_s 77 | end 78 | end 79 | 80 | context 'by price id' do 81 | it 'will show its product' do 82 | get :show, price_id: product.master.prices.first.id 83 | product_id = parse_json(response.body)['data']['id'] 84 | expect(product_id).to eql product.id.to_s 85 | end 86 | end 87 | 88 | context 'by image id' do 89 | let(:image) { create :image } 90 | 91 | it 'will show its product' do 92 | product.images << image 93 | get :show, image_id: image.id 94 | product_id = parse_json(response.body)['data']['id'] 95 | expect(product_id).to eql product.id.to_s 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/states_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::StatesController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:state) { create :state } 5 | 6 | describe '#index' do 7 | it 'can list all states' do 8 | get :index 9 | expect(JSON.parse(response.body)['data'].length).to eql 1 10 | end 11 | 12 | context 'by country id' do 13 | it 'will list selected states by country' do 14 | get :index, country_id: state.country.id 15 | expect(JSON.parse(response.body)['data'].length).to eql 1 16 | end 17 | 18 | it 'will not include states belonging to other countries' do 19 | new_state = create :state 20 | get :index, country_id: state.country.id 21 | state_ids = JSON.parse(response.body)['data'].map do |states| 22 | states['id'] 23 | end 24 | expect(state_ids).to_not include new_state 25 | end 26 | end 27 | end 28 | 29 | describe '#show' do 30 | it 'will find selected state by id' do 31 | get :show, id: state.id 32 | state_id = parse_json(response.body)['data']['id'] 33 | expect(state_id).to eql state.id.to_s 34 | end 35 | 36 | it 'can find a state by a country' do 37 | get :show, country_id: state.country.id, id: state.id 38 | state_id = parse_json(response.body)['data']['id'] 39 | expect(state_id).to eql state.id.to_s 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/taxonomies_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::TaxonomiesController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:taxonomy) { create :taxonomy } 5 | 6 | describe '#index' do 7 | it 'will list taxonomies' do 8 | get :index 9 | taxonomy_ids = parse_json(response.body)['data'].map do |taxonomy| 10 | taxonomy['id'] 11 | end 12 | expect(taxonomy_ids).to include taxonomy.id.to_s 13 | end 14 | end 15 | 16 | describe '#show' do 17 | let(:taxon) { taxonomy.root } 18 | 19 | it 'will show taxonomy' do 20 | get :show, id: taxonomy.id 21 | taxonomy_id = parse_json(response.body)['data']['id'] 22 | expect(taxonomy_id).to eql taxonomy.id.to_s 23 | end 24 | 25 | it 'can show taxonomy by taxon_id' do 26 | get :show, taxon_id: taxon.id 27 | taxonomy_id = parse_json(response.body)['data']['id'] 28 | expect(taxonomy_id).to eql taxonomy.id.to_s 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/taxons_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::TaxonsController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:taxon) { create :taxon } 5 | 6 | describe '#index' do 7 | it 'will list the taxons' do 8 | get :index 9 | taxon_ids = parse_json(response.body)['data'].map do |taxon| 10 | taxon['id'] 11 | end 12 | expect(taxon_ids).to include taxon.id.to_s 13 | end 14 | 15 | it 'can list the taxons filtered by taxonomy_id' do 16 | get :index, taxonomy_id: taxon.taxonomy.id 17 | taxon_ids = parse_json(response.body)['data'].map do |taxon| 18 | taxon['id'] 19 | end 20 | expect(taxon_ids).to include taxon.id.to_s 21 | end 22 | end 23 | 24 | describe '#show' do 25 | it 'will show the selected taxon' do 26 | get :show, id: taxon.id 27 | taxon_id = parse_json(response.body)['data']['id'] 28 | expect(taxon_id).to eql taxon.id.to_s 29 | end 30 | 31 | context 'by parent' do 32 | it 'can show a taxons parent' do 33 | child = create :taxon, parent: taxon 34 | get :show, taxon_id: child.id 35 | taxon_id = parse_json(response.body)['data']['id'] 36 | expect(taxon_id).to eql taxon.id.to_s 37 | end 38 | end 39 | 40 | context 'by taxonomy_id' do 41 | it 'will fetch the selected taxon' do 42 | get :show, taxonomy_id: taxon.taxonomy.id, id: taxon.id 43 | taxon_id = parse_json(response.body)['data']['id'] 44 | expect(taxon_id).to eql taxon.id.to_s 45 | end 46 | 47 | it 'when taxon does not belong to taxonomy' do 48 | new_taxonomy = create :taxonomy 49 | get :show, taxonomy_id: new_taxonomy.id, id: taxon.id 50 | expect(response).to have_http_status 404 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/controllers/spree/api/v2/variants_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Api::V2::VariantsController do 2 | routes { Spree::Core::Engine.routes } 3 | 4 | let!(:variant) { create :variant } 5 | let(:new_option_value) { create :option_value } 6 | 7 | describe '#index' do 8 | it 'will list all variants' do 9 | get :index 10 | variant_ids = parse_json(response.body)['data'].map do |variant| 11 | variant['id'] 12 | end 13 | expect(variant_ids).to include variant.id.to_s 14 | end 15 | 16 | context 'by product id' do 17 | let(:new_product) { create :product } 18 | 19 | it 'will list its variants' do 20 | get :index, product_id: variant.product.id 21 | variant_ids = parse_json(response.body)['data'].map do |variant| 22 | variant['id'] 23 | end 24 | expect(variant_ids).to include variant.id.to_s 25 | end 26 | 27 | it 'will not list another products variants' do 28 | get :index, product_id: new_product.id 29 | variant_ids = parse_json(response.body)['data'].map do |variant| 30 | variant['id'] 31 | end 32 | expect(variant_ids).to_not include variant.id.to_s 33 | end 34 | end 35 | 36 | context 'by option value' do 37 | it 'will list its variants' do 38 | get :index, option_value_id: variant.option_values.first.id 39 | variant_ids = parse_json(response.body)['data'].map do |variant| 40 | variant['id'] 41 | end 42 | expect(variant_ids).to include variant.id.to_s 43 | end 44 | 45 | it 'will not list another products variant' do 46 | get :index, option_value_id: new_option_value.id 47 | data = parse_json(response.body)['data'] 48 | expect(data).to be_empty 49 | end 50 | end 51 | end 52 | 53 | describe '#show' do 54 | it 'will show the variant' do 55 | get :show, id: variant.id 56 | variant_id = parse_json(response.body)['data']['id'] 57 | expect(variant_id).to eql variant.id.to_s 58 | end 59 | 60 | context 'by product id' do 61 | let(:new_product) { create :product } 62 | 63 | it 'will show its variant' do 64 | get :show, id: variant.id, product_id: variant.product.id 65 | variant_id = parse_json(response.body)['data']['id'] 66 | expect(variant_id).to eql variant.id.to_s 67 | end 68 | 69 | it 'will not show another products variant' do 70 | get :show, id: variant.id, product_id: new_product.id 71 | expect(response).to have_http_status 404 72 | end 73 | end 74 | 75 | context 'by option_value id' do 76 | it 'will show its variant' do 77 | get :show, id: variant.id, option_value_id: variant.option_values.first.id 78 | variant_id = parse_json(response.body)['data']['id'] 79 | expect(variant_id).to eql variant.id.to_s 80 | end 81 | 82 | it 'will not show another option_values variant' do 83 | get :show, id: variant.id, option_value_id: new_option_value.id 84 | expect(response).to have_http_status 404 85 | end 86 | end 87 | 88 | context 'by price id' do 89 | it 'will show its variant' do 90 | get :show, price_id: variant.prices.first.id 91 | variant_id = parse_json(response.body)['data']['id'] 92 | expect(variant_id).to eql variant.id.to_s 93 | end 94 | end 95 | 96 | context 'by image id' do 97 | let(:image) { create :image, viewable: variant } 98 | 99 | it 'will show its variant' do 100 | get :show, image_id: image.id 101 | variant_id = parse_json(response.body)['data']['id'] 102 | expect(variant_id).to eql variant.id.to_s 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/lib/solidus_json_api/config_spec.rb: -------------------------------------------------------------------------------- 1 | describe SolidusJsonApi do 2 | describe '.setup' do 3 | it 'can set the parent serializer' do 4 | class DescendantOfAMS < ActiveModel::Serializer; end 5 | 6 | described_class.setup { |c| c.parent_serializer = DescendantOfAMS } 7 | expect(described_class.parent_serializer).to eql DescendantOfAMS 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/models/spree/base_decorator_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Base do 2 | it '.paginate' do 3 | # The pagination is concerned onto abstract models. There's not really a way 4 | # to be able to ensure that the model is paginated, so instead of ensuring 5 | # that the result is paginated (tested in the Spree::Api::BaseController), 6 | # this just makes sure it can respond to the +.paginate+ method. 7 | expect(described_class).to respond_to(:paginate).with(1).argument 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/models/spree/price_decorator_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::Price do 2 | it { is_expected.to delegate_method(:product).to :variant } 3 | end 4 | -------------------------------------------------------------------------------- /spec/serializers/spree/address_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::AddressSerializer do 2 | let(:address) { create :address } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new address) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "address1" : "#{address.address1}", 11 | "address2" : "#{address.address2}", 12 | "city" : "#{address.city}", 13 | "first_name" : "#{address.first_name}", 14 | "last_name" : "#{address.last_name}", 15 | "phone" : "#{address.phone}", 16 | "zipcode" : "#{address.zipcode}" 17 | }, 18 | "relationships" : { 19 | "country" : { 20 | "data" : { 21 | "type" : "spree_countries" 22 | } 23 | }, 24 | "state" : { 25 | "data" : { 26 | "type" : "spree_states" 27 | } 28 | } 29 | }, 30 | "type" : "spree_addresses" 31 | } 32 | } 33 | JSON 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/serializers/spree/country_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::CountrySerializer do 2 | let(:country) { create :country } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new country) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "iso" : "#{country.iso}", 11 | "iso3" : "#{country.iso3}", 12 | "iso_name" : "#{country.iso_name}", 13 | "name" : "#{country.name}", 14 | "numcode" : #{country.numcode}, 15 | "states_required" : #{country.states_required} 16 | }, 17 | "relationships" : { 18 | "states" : { 19 | "data" : [] 20 | } 21 | }, 22 | "type" : "spree_countries" 23 | } 24 | } 25 | JSON 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/serializers/spree/error_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::ErrorSerializer do 2 | let(:address_errors) do 3 | [ 4 | { 5 | "ship_address.firstname": ["can't be blank"], 6 | "ship_address.lastname": ["can't be blank"] 7 | }, 8 | { 9 | "bill_address.firstname": ["can't be blank"], 10 | "bill_address.lastname": ["can't be blank"] 11 | } 12 | ] 13 | end 14 | 15 | subject { described_class } 16 | 17 | describe '#as_json' do 18 | before do 19 | @store = build(:store, code: nil, mail_from_address: nil) 20 | @store.save 21 | end 22 | 23 | it 'will format json to json api spec' do 24 | expect(described_class.new(@store).as_json).to be_json_eql <<-JSON 25 | { 26 | "errors": [ 27 | { 28 | "title": "Code", 29 | "meta": {}, 30 | "pointer": "data/attributes/code", 31 | "detail": "can't be blank" 32 | }, 33 | { 34 | "title": "Mail From Address", 35 | "meta": {}, 36 | "pointer": "data/attributes/mail_from_address", 37 | "detail": "can't be blank" 38 | } 39 | ] 40 | } 41 | JSON 42 | end 43 | 44 | it 'will format options as meta' do 45 | expect(described_class.new(@store, example: :content).as_json).to be_json_eql <<-JSON 46 | { 47 | "errors": [ 48 | { 49 | "detail": "can't be blank", 50 | "meta": { 51 | "example": "content" 52 | }, 53 | "pointer": "data/attributes/code", 54 | "title": "Code" 55 | }, 56 | { 57 | "detail": "can't be blank", 58 | "meta": { 59 | "example": "content" 60 | }, 61 | "pointer": "data/attributes/mail_from_address", 62 | "title": "Mail From Address" 63 | } 64 | ] 65 | } 66 | JSON 67 | end 68 | 69 | it 'will format a symbol' do 70 | expect(described_class.new(:invalid_token).as_json).to be_json_eql <<-JSON 71 | { 72 | "errors": [ 73 | { 74 | "detail": "#{Spree.t('api.errors.invalid_token.detail')}", 75 | "meta": {}, 76 | "title": "#{Spree.t('api.errors.invalid_token.title')}" 77 | } 78 | ] 79 | } 80 | JSON 81 | end 82 | 83 | it 'will format an array of errors' do 84 | expect(described_class.new(address_errors).as_json).to be_json_eql <<-JSON 85 | { 86 | "errors" : [ 87 | { 88 | "detail" : "can't be blank", 89 | "meta" : {}, 90 | "pointer" : "data/attributes/ship_address/firstname", 91 | "title" : "Ship Address Firstname" 92 | }, 93 | { 94 | "detail" : "can't be blank", 95 | "meta" : {}, 96 | "pointer" : "data/attributes/ship_address/lastname", 97 | "title" : "Ship Address Lastname" 98 | }, 99 | { 100 | "detail" : "can't be blank", 101 | "meta" : {}, 102 | "pointer" : "data/attributes/bill_address/firstname", 103 | "title" : "Bill Address Firstname" 104 | }, 105 | { 106 | "detail" : "can't be blank", 107 | "meta" : {}, 108 | "pointer" : "data/attributes/bill_address/lastname", 109 | "title" : "Bill Address Lastname" 110 | } 111 | ] 112 | } 113 | JSON 114 | end 115 | 116 | context 'can set a response when it is present' do 117 | let(:response) do 118 | Response ||= Struct.new(:status_message, :code) 119 | Response.new('OK', 200) 120 | end 121 | 122 | subject { described_class.new(:test, response: response).as_json } 123 | 124 | it do 125 | should be_json_eql <<-JSON 126 | { 127 | "errors" : [ 128 | { 129 | "code" : 200, 130 | "detail" : #{Spree.t('api.errors.test.detail').to_json}, 131 | "meta" : {}, 132 | "status" : "OK", 133 | "title" : #{Spree.t('api.errors.test.title').titleize.to_json} 134 | } 135 | ] 136 | } 137 | JSON 138 | end 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /spec/serializers/spree/image_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::ImageSerializer do 2 | let(:image) { create :image } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new image) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "alt" : null, 11 | "links" : { 12 | "large" : "#{image.attachment.url(:large)}", 13 | "mini" : "#{image.attachment.url(:mini)}", 14 | "original" : "#{image.attachment.url(:original)}", 15 | "product" : "#{image.attachment.url(:product)}", 16 | "small" : "#{image.attachment.url(:small)}" 17 | }, 18 | "position" : 1 19 | }, 20 | "relationships" : { 21 | "viewable" : { 22 | "data" : null 23 | } 24 | }, 25 | "type" : "spree_images" 26 | } 27 | } 28 | JSON 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/serializers/spree/line_item_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::LineItemSerializer do 2 | let(:line_item) { create :line_item } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new line_item) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "additional_tax_total" : "0.0", 11 | "adjustment_total" : "0.0", 12 | "amount" : "10.0", 13 | "cost_price" : "17.0", 14 | "currency" : "USD", 15 | "display_amount" : "$10.00", 16 | "display_total" : "$10.00", 17 | "order_id" : #{line_item.order_id}, 18 | "price" : "10.0", 19 | "quantity" : 1, 20 | "total" : "10.0", 21 | "variant_id" : #{line_item.variant_id} 22 | }, 23 | "relationships" : { 24 | "order" : { 25 | "data" : { 26 | "type" : "spree_orders" 27 | } 28 | }, 29 | "variant" : { 30 | "data" : { 31 | "type" : "spree_variants" 32 | } 33 | } 34 | }, 35 | "type" : "spree_line_items" 36 | } 37 | } 38 | JSON 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/serializers/spree/option_type_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::OptionTypeSerializer do 2 | let(:option_type) { create :option_type } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new option_type) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "name" : "#{option_type.name}", 11 | "position" : #{option_type.position}, 12 | "presentation" : "#{option_type.presentation}" 13 | }, 14 | "relationships" : { 15 | "option_values" : { 16 | "data" : [] 17 | }, 18 | "products" : { 19 | "data" : [] 20 | } 21 | }, 22 | "type" : "spree_option_types" 23 | } 24 | } 25 | JSON 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/serializers/spree/option_value_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::OptionValueSerializer do 2 | let(:option_value) { create :option_value } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new option_value) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "name" : "#{option_value.name}", 11 | "position" : #{option_value.position}, 12 | "presentation" : "#{option_value.presentation}" 13 | }, 14 | "relationships" : { 15 | "option_type" : { 16 | "data" : { 17 | "type" : "spree_option_types" 18 | } 19 | }, 20 | "variants" : { 21 | "data" : [] 22 | } 23 | }, 24 | "type" : "spree_option_values" 25 | } 26 | } 27 | JSON 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/serializers/spree/order_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::OrderSerializer do 2 | let(:order) { create :completed_order_with_totals } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new order) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "additional_tax_total" : "0.0", 11 | "adjustment_total" : "0.0", 12 | "approved_at" : null, 13 | "canceled_at" : null, 14 | "channel" : "spree", 15 | "completed_at" : "#{order.completed_at.iso8601(3)}", 16 | "confirmation_delivered" : false, 17 | "currency" : "USD", 18 | "display_additional_tax_total" : "$0.00", 19 | "display_adjustment_total" : "$0.00", 20 | "display_included_tax_total" : "$0.00", 21 | "display_item_total" : "$10.00", 22 | "display_promo_total" : "$0.00", 23 | "display_shipment_total" : "$100.00", 24 | "display_total" : "$110.00", 25 | "email" : "#{order.email}", 26 | "included_tax_total" : "0.0", 27 | "item_count" : 1, 28 | "item_total" : "10.0", 29 | "number" : "#{order.number}", 30 | "payment_state" : null, 31 | "payment_total" : "0.0", 32 | "promo_total" : "0.0", 33 | "shipment_state" : null, 34 | "shipment_total" : "100.0", 35 | "special_instructions" : null, 36 | "state" : "complete", 37 | "total" : "110.0" 38 | }, 39 | "relationships" : { 40 | "bill_address" : { 41 | "data" : { 42 | "type" : "spree_addresses" 43 | } 44 | }, 45 | "line_items" : { 46 | "data" : [ 47 | { 48 | "type" : "spree_line_items" 49 | } 50 | ] 51 | }, 52 | "ship_address" : { 53 | "data" : { 54 | "type" : "spree_addresses" 55 | } 56 | }, 57 | "user" : { 58 | "data" : { 59 | "type" : "spree_users" 60 | } 61 | } 62 | }, 63 | "type" : "spree_orders" 64 | } 65 | } 66 | JSON 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/serializers/spree/price_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::PriceSerializer do 2 | let(:price) { create :price } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new price) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "amount" : "19.99", 11 | "currency" : "USD", 12 | "display_amount" : "$19.99", 13 | "display_price" : "$19.99", 14 | "price" : "19.99" 15 | }, 16 | "relationships" : { 17 | "variant" : { 18 | "data" : { 19 | "type" : "spree_variants" 20 | } 21 | } 22 | }, 23 | "type" : "spree_prices" 24 | } 25 | } 26 | JSON 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/serializers/spree/product_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::ProductSerializer do 2 | let(:image) { create :image } 3 | let(:product) { create :product } 4 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new product) } 5 | 6 | before { product.images << image } 7 | 8 | its(:to_json) do 9 | is_expected.to be_json_eql <<-JSON 10 | { 11 | "data" : { 12 | "attributes" : { 13 | "description" : #{product.description.to_json}, 14 | "meta_description" : null, 15 | "meta_keywords" : null, 16 | "name" : "#{product.name}", 17 | "slug" : "#{product.slug}" 18 | }, 19 | "relationships" : { 20 | "images" : { 21 | "data" : [ 22 | { 23 | "type" : "spree_images" 24 | } 25 | ] 26 | }, 27 | "master" : { 28 | "data" : { 29 | "type" : "spree_variants" 30 | } 31 | }, 32 | "option_types" : { 33 | "data" : [] 34 | }, 35 | "taxons" : { 36 | "data" : [] 37 | }, 38 | "variants" : { 39 | "data" : [] 40 | } 41 | }, 42 | "type" : "spree_products" 43 | } 44 | } 45 | JSON 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/serializers/spree/role_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::RoleSerializer do 2 | let(:role) { create :role } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new role) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "name" : "#{role.name}" 11 | }, 12 | "type" : "spree_roles" 13 | } 14 | } 15 | JSON 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/serializers/spree/state_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::StateSerializer do 2 | let(:state) { create :state } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new state) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "abbr" : "#{state.abbr}", 11 | "name" : "#{state.name}" 12 | }, 13 | "relationships" : { 14 | "country" : { 15 | "data" : { 16 | "type" : "spree_countries" 17 | } 18 | } 19 | }, 20 | "type" : "spree_states" 21 | } 22 | } 23 | JSON 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/serializers/spree/store_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::StoreSerializer do 2 | let(:store) { create :store } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new store) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "code" : "#{store.code}", 11 | "default" : #{store.default}, 12 | "default_currency" : #{store.default_currency || 'null'}, 13 | "mail_from_address" : "#{store.mail_from_address}", 14 | "meta_description" : null, 15 | "meta_keywords" : null, 16 | "name" : "#{store.name}", 17 | "seo_title" : null, 18 | "url" : "#{store.url}" 19 | }, 20 | "type" : "spree_stores" 21 | } 22 | } 23 | JSON 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/serializers/spree/taxon_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::TaxonSerializer do 2 | let(:taxon) { create :taxon } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new taxon) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "depth" : 0, 11 | "description" : null, 12 | "links": { 13 | "large" : "/assets/default_taxon.png", 14 | "mini" : "/assets/default_taxon.png", 15 | "original" : "/assets/default_taxon.png", 16 | "product" : "/assets/default_taxon.png", 17 | "small" : "/assets/default_taxon.png" 18 | }, 19 | "meta_description" : null, 20 | "meta_keywords" : null, 21 | "meta_title" : null, 22 | "name" : "#{taxon.name}", 23 | "permalink" : "#{taxon.permalink}", 24 | "position" : 0 25 | }, 26 | "relationships" : { 27 | "children" : { 28 | "data" : [] 29 | }, 30 | "parent" : { 31 | "data" : null 32 | }, 33 | "taxonomy" : { 34 | "data" : { 35 | "type" : "spree_taxonomies" 36 | } 37 | } 38 | }, 39 | "type" : "spree_taxons" 40 | } 41 | } 42 | JSON 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/serializers/spree/taxonomy_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::TaxonomySerializer do 2 | let(:taxonomy) { create :taxonomy } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new taxonomy) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "name" : "#{taxonomy.name}", 11 | "position" : #{taxonomy.position} 12 | }, 13 | "relationships" : { 14 | "taxons" : { 15 | "data" : [ 16 | { 17 | "type" : "spree_taxons" 18 | } 19 | ] 20 | } 21 | }, 22 | "type" : "spree_taxonomies" 23 | } 24 | } 25 | JSON 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/serializers/spree/user_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::UserSerializer do 2 | let(:user) { create :user } 3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new user) } 4 | 5 | its(:to_json) do 6 | is_expected.to be_json_eql <<-JSON 7 | { 8 | "data" : { 9 | "attributes" : { 10 | "email" : "#{user.email}" 11 | }, 12 | "type" : "spree_users" 13 | } 14 | } 15 | JSON 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/serializers/spree/variant_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | describe Spree::VariantSerializer do 2 | let(:image) { create :image } 3 | let(:variant) { create :variant, images: [image] } 4 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new variant) } 5 | 6 | its(:to_json) do 7 | is_expected.to be_json_eql <<-JSON 8 | { 9 | "data" : { 10 | "attributes" : { 11 | "depth" : #{variant.depth.to_json}, 12 | "display_price" : "#{variant.display_price}", 13 | "height" : #{variant.height.to_json}, 14 | "is_master" : #{variant.is_master}, 15 | "name" : "#{variant.name}", 16 | "position" : #{variant.position}, 17 | "price" : "#{variant.price}", 18 | "sku" : "#{variant.sku}", 19 | "weight" : "#{variant.weight}", 20 | "width" : #{variant.width.to_json} 21 | }, 22 | "relationships" : { 23 | "images" : { 24 | "data" : [ 25 | { 26 | "type" : "spree_images" 27 | } 28 | ] 29 | }, 30 | "option_values" : { 31 | "data" : [ 32 | { 33 | "type" : "spree_option_values" 34 | } 35 | ] 36 | }, 37 | "prices" : { 38 | "data" : [ 39 | { 40 | "type" : "spree_prices" 41 | } 42 | ] 43 | }, 44 | "product" : { 45 | "data" : { 46 | "type" : "spree_products" 47 | } 48 | } 49 | }, 50 | "type" : "spree_variants" 51 | } 52 | } 53 | JSON 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | 3 | if ENV['CI'] 4 | require 'coveralls' 5 | Coveralls.wear!('rails') 6 | end 7 | 8 | SimpleCov.start do 9 | add_filter 'spec/dummy' 10 | add_group 'Controllers', 'app/controllers' 11 | add_group 'Models', 'app/models' 12 | add_group 'Serializers', 'app/serializers' 13 | add_group 'Libraries', 'lib' 14 | end 15 | 16 | ENV['RAILS_ENV'] = 'test' 17 | 18 | require File.expand_path('../dummy/config/environment.rb', __FILE__) 19 | 20 | require 'rspec/rails' 21 | require 'rspec/its' 22 | require 'database_cleaner' 23 | require 'ffaker' 24 | require 'json_spec' 25 | require 'shoulda/matchers' 26 | 27 | Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f } 28 | 29 | require 'spree/testing_support/factories' 30 | 31 | RSpec.configure do |config| 32 | config.include FactoryGirl::Syntax::Methods 33 | config.include JsonSpec::Helpers 34 | 35 | config.infer_spec_type_from_file_location! 36 | config.mock_with :rspec 37 | config.color = true 38 | config.use_transactional_fixtures = false 39 | config.fail_fast = ENV['FAIL_FAST'] || false 40 | config.order = "random" 41 | 42 | config.before :suite do 43 | ActiveModel::Serializer.config.adapter = :json_api 44 | 45 | DatabaseCleaner.strategy = :transaction 46 | DatabaseCleaner.clean_with :truncation 47 | end 48 | 49 | config.before :each do 50 | DatabaseCleaner.strategy = RSpec.current_example.metadata[:js] ? :truncation : :transaction 51 | DatabaseCleaner.start 52 | end 53 | 54 | config.after :each do 55 | DatabaseCleaner.clean 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/support/shoulda_matchers.rb: -------------------------------------------------------------------------------- 1 | Shoulda::Matchers.configure do |config| 2 | config.integrate do |with| 3 | with.test_framework :rspec 4 | with.library :rails 5 | end 6 | end 7 | --------------------------------------------------------------------------------