Slide 1

Slide 1 text

Introducing 1.0 $ gem install rails -v '~> 3.1.1' $ gem install spree $ rails new my_store $ spree install my_store 1 Saturday, February 18, 12

Slide 2

Slide 2 text

Jeff Squires CTO at Rails Dog, LLC @jeffsquires github.com/jsqu99 2 Saturday, February 18, 12

Slide 3

Slide 3 text

Agenda • 5-Minute Intro to Spree • Install,Deploy,Modify,Extend,Discuss Spree 1.0 3 Saturday, February 18, 12

Slide 4

Slide 4 text

What is Spree? 4 Saturday, February 18, 12

Slide 5

Slide 5 text

Features • Provide common features only • Avoid bloat • Make everything else possible 5 Saturday, February 18, 12

Slide 6

Slide 6 text

6 Saturday, February 18, 12

Slide 7

Slide 7 text

products users variants carts orders shipping payments returns inventory checkout coupons promotions properties taxonomies reports api adjustments back-ordering internationalization sales VAT theming pluggable search extensions 6 Saturday, February 18, 12

Slide 8

Slide 8 text

• Minimal dependencies Design Philosophy 7 Saturday, February 18, 12

Slide 9

Slide 9 text

8 Saturday, February 18, 12

Slide 10

Slide 10 text

Rails 3.1 Devise Kaminari ERB Asset Pipeline RSpec Deface Paperclip ActiveMerchant Faker MetaSearch ActiveRecord State Machine JQuery FactoryGirl Javascript Engines 8 Saturday, February 18, 12

Slide 11

Slide 11 text

Let’s see 1.0 9 Saturday, February 18, 12

Slide 12

Slide 12 text

10 Saturday, February 18, 12

Slide 13

Slide 13 text

11 Saturday, February 18, 12

Slide 14

Slide 14 text

12 Saturday, February 18, 12

Slide 15

Slide 15 text

13 Saturday, February 18, 12

Slide 16

Slide 16 text

14 Saturday, February 18, 12

Slide 17

Slide 17 text

15 Saturday, February 18, 12

Slide 18

Slide 18 text

16 Saturday, February 18, 12

Slide 19

Slide 19 text

17 Saturday, February 18, 12

Slide 20

Slide 20 text

18 Saturday, February 18, 12

Slide 21

Slide 21 text

19 Saturday, February 18, 12

Slide 22

Slide 22 text

20 Saturday, February 18, 12

Slide 23

Slide 23 text

Full Featured Admin 21 Saturday, February 18, 12

Slide 24

Slide 24 text

22 Saturday, February 18, 12

Slide 25

Slide 25 text

23 Saturday, February 18, 12

Slide 26

Slide 26 text

24 Saturday, February 18, 12

Slide 27

Slide 27 text

25 Saturday, February 18, 12

Slide 28

Slide 28 text

26 Saturday, February 18, 12

Slide 29

Slide 29 text

27 Saturday, February 18, 12

Slide 30

Slide 30 text

28 Saturday, February 18, 12

Slide 31

Slide 31 text

29 Saturday, February 18, 12

Slide 32

Slide 32 text

30 Saturday, February 18, 12

Slide 33

Slide 33 text

$ gem install rails -v '~> 3.1.1' $ gem install spree $ rails new my_store $ spree install my_store $ cd my_store $ bundle exec rails server Installation 31 Saturday, February 18, 12

Slide 34

Slide 34 text

$ gem install rails -v '~> 3.1.1' $ gem install spree $ rails new my_store $ spree install my_store $ cd my_store $ bundle exec rails server Installation Under the Hood 32 Saturday, February 18, 12

Slide 35

Slide 35 text

Installation Gemfile gem 'spree', '~> 1.0.0' 33 Saturday, February 18, 12

Slide 36

Slide 36 text

Installation routes mount Spree::Core::Engine, :at => '/' my_store/config/routes.rb 34 Saturday, February 18, 12

Slide 37

Slide 37 text

Installation assets $ rails new my_store $ spree install my_store 35 Saturday, February 18, 12

Slide 38

Slide 38 text

Installation assets //= require store/spree_core //= require store/spree_auth //= require store/spree_api //= require store/spree_promo //= require_tree . my_store/app/assets/javascripts/store/all.js 36 Saturday, February 18, 12

Slide 39

Slide 39 text

//= require jquery //= require jquery_ujs //= require jquery.validate/jquery.validate.min //= require jquery.formalize.min //= require store/checkout //= require store/product //= require store/cart //= require store/helpers Installation assets spree/core/app/assets/javascripts/store/spree_core.js //= require store/spree_core //= require store/spree_auth //= require store/spree_api //= require store/spree_promo //= require_tree . 37 Saturday, February 18, 12

Slide 40

Slide 40 text

Installation default layouts spree/core/app/views/spree/layouts 38 Saturday, February 18, 12

Slide 41

Slide 41 text

Installation migrations 39 Saturday, February 18, 12

Slide 42

Slide 42 text

I need more! 40 Saturday, February 18, 12

Slide 43

Slide 43 text

Extensions 41 Saturday, February 18, 12

Slide 44

Slide 44 text

20+ Official Extensions 42 Saturday, February 18, 12

Slide 45

Slide 45 text

180 Third Party Extensions 43 Saturday, February 18, 12

Slide 46

Slide 46 text

spree_social 44 Saturday, February 18, 12

Slide 47

Slide 47 text

spree_active_shipping 45 Saturday, February 18, 12

Slide 48

Slide 48 text

spree_i18n 30+ Languages 46 Saturday, February 18, 12

Slide 49

Slide 49 text

spree_paypal_express 47 Saturday, February 18, 12

Slide 50

Slide 50 text

48 Saturday, February 18, 12

Slide 51

Slide 51 text

spree_store_credits spree_affiliate spree_solr spree_gift_card spree_print_invoice spree_sphinx spree_related_products spree_comments spree_blog spree_volume_pricing spree_fulfillment spree_wishlist spree_wholesale spree_multi_domain spree_google_checkout spree_recently_viewed spree_pages spree_email_to_friend spree_digital spree_redirects spree_mp3player spree_editor spree_product_translations spree_faq spree_homepager spree_share spree_page_cache 48 Saturday, February 18, 12

Slide 52

Slide 52 text

spree extension registry http://spreecommerce.com/extensions 49 Saturday, February 18, 12

Slide 53

Slide 53 text

50 Saturday, February 18, 12

Slide 54

Slide 54 text

I need even more! • Write an extension • Customize your app 51 Saturday, February 18, 12

Slide 55

Slide 55 text

Anatomy of a Spree store 52 Saturday, February 18, 12

Slide 56

Slide 56 text

core 53 Saturday, February 18, 12

Slide 57

Slide 57 text

core api promo dash auth 53 Saturday, February 18, 12

Slide 58

Slide 58 text

core api promo dash paypal_express active_shipping auth 53 Saturday, February 18, 12

Slide 59

Slide 59 text

core api promo dash paypal_express active_shipping static_content auth 53 Saturday, February 18, 12

Slide 60

Slide 60 text

core api promo dash paypal_express active_shipping Rails Application static_content auth 53 Saturday, February 18, 12

Slide 61

Slide 61 text

How does this work? 54 Saturday, February 18, 12

Slide 62

Slide 62 text

Rails 3 engines How does this work? 54 Saturday, February 18, 12

Slide 63

Slide 63 text

auth core Rails Application gem 'spree_core' Our Setup 55 Saturday, February 18, 12

Slide 64

Slide 64 text

Directory Structure spree_core 56 Saturday, February 18, 12

Slide 65

Slide 65 text

57 Saturday, February 18, 12

Slide 66

Slide 66 text

auth app/views/spree/products/show.html.erb Rails Application View Override 58 Saturday, February 18, 12

Slide 67

Slide 67 text

auth app/views/spree/products/show.html.erb Rails Application app/views/spree/products/show.html.erb View Override 59 Saturday, February 18, 12

Slide 68

Slide 68 text

Overriden products/show.html.erb

Here's my new product detail page and I'm only going to show you the product name:

<%= @product.name %>

60 Saturday, February 18, 12

Slide 69

Slide 69 text

Directory Structure products/show.html.erb 61 Saturday, February 18, 12

Slide 70

Slide 70 text

62 Saturday, February 18, 12

Slide 71

Slide 71 text

auth core Rails Application some_extension some_other_extension More Complex Setup 63 Saturday, February 18, 12

Slide 72

Slide 72 text

Add Extensions $ spree extension some_extension $ spree extension some_other_extension 64 Saturday, February 18, 12

Slide 73

Slide 73 text

gem 'spree_core', :git => 'git://github.com/spree/spree.git' gem 'spree_some_extension', :path => '../spree_some_extension' gem 'spree_some_other_extension', :path => '../spree_some_other_extension' ~/my_store/Gemfile Add Extensions $ spree extension some_extension $ spree extension some_other_extension 64 Saturday, February 18, 12

Slide 74

Slide 74 text

Directory Structure products/*.html.erb 65 Saturday, February 18, 12

Slide 75

Slide 75 text

auth Rails Application index,show,_taxons,_thumbnails some_extension some_other_extension View Override 66 Saturday, February 18, 12

Slide 76

Slide 76 text

auth Rails Application index,show,_taxons,_thumbnails show some_other_extension View Override 67 Saturday, February 18, 12

Slide 77

Slide 77 text

auth Rails Application index,show,_taxons,_thumbnails show show,_taxons View Override 68 Saturday, February 18, 12

Slide 78

Slide 78 text

auth Rails Application show show,_taxons _thumbnails index,show,_taxons,_thumbnails View Override 69 Saturday, February 18, 12

Slide 79

Slide 79 text

Directory Structure winners 70 Saturday, February 18, 12

Slide 80

Slide 80 text

Gemfile Order Matters gem 'spree_core', :git => 'git://github.com/spree/spree.git' gem 'spree_some_extension', :path => '../spree_some_extension' gem 'spree_some_other_extension', :path => '../spree_some_other_extension' gem 'spree_core', :git => 'git://github.com/spree/spree.git' gem 'spree_some_other_extension', :path => '../spree_some_other_extension' gem 'spree_some_extension', :path => '../spree_some_extension' 71 Saturday, February 18, 12

Slide 81

Slide 81 text

auth Rails Application show show,_taxons _thumbnails index,show,_taxons,_thumbnails View Override show,_taxons show 72 Saturday, February 18, 12

Slide 82

Slide 82 text

Directory Structure winners 73 Saturday, February 18, 12

Slide 83

Slide 83 text

Deface github.com/railsdog/deface 74 Saturday, February 18, 12

Slide 84

Slide 84 text

auth app/views/spree/products/show.html.erb Rails Application View Override Deface 75 Saturday, February 18, 12

Slide 85

Slide 85 text

auth app/views/spree/products/show.html.erb Rails Application app/overrides/change_product_detail.rb View Override Deface 75 Saturday, February 18, 12

Slide 86

Slide 86 text

auth app/views/spree/products/show.html.erb Rails Application app/overrides/change_product_detail.rb Deface::Override.new(:virtual_path => "spree/products/show", :name => "add_submit_review_button", :insert_after => "[data-hook='product_properties']" :partial => "spree/shared/reviews") View Override Deface 75 Saturday, February 18, 12

Slide 87

Slide 87 text

What about model/ controllers? 76 Saturday, February 18, 12

Slide 88

Slide 88 text

auth app/models/spree/product.rb Rails Application Model Override 77 Saturday, February 18, 12

Slide 89

Slide 89 text

auth app/models/spree/product.rb Rails Application app/models/spree/product_decorator.rb Model Override 77 Saturday, February 18, 12

Slide 90

Slide 90 text

module Spree class Product < ActiveRecord::Base has_many :product_option_types, :dependent => :destroy has_many :option_types, :through => :product_option_types has_many :product_properties, :dependent => :destroy has_many :properties, :through => :product_properties ....... end spree/core/app/models/spree/product.rb Model Override 78 Saturday, February 18, 12

Slide 91

Slide 91 text

module Spree class Product < ActiveRecord::Base has_many :product_option_types, :dependent => :destroy has_many :option_types, :through => :product_option_types has_many :product_properties, :dependent => :destroy has_many :properties, :through => :product_properties ....... end Spree::Product.class_eval do def minimum_met? return true if self.minimum_order_count.blank? self.current_order_count >= self.minimum_order_count end end spree/core/app/models/spree/product.rb some_extension/app/models/spree/product_decorator.rb Model Override 78 Saturday, February 18, 12

Slide 92

Slide 92 text

auth app/controllers/spree/products_controller.rb Rails Application Controller Override 79 Saturday, February 18, 12

Slide 93

Slide 93 text

auth app/controllers/spree/products_controller.rb Rails Application app/controllers/spree/products_controller_decorator.rb Controller Override 79 Saturday, February 18, 12

Slide 94

Slide 94 text

module Spree class ProductsController < BaseController HTTP_REFERER_REGEXP = /^https?:\/\/[^\/]+\/t\/([a-z0-9\-\/]+)$/ rescue_from ActiveRecord::RecordNotFound, :with => :render_404 helper 'spree/taxons' respond_to :html def index @searcher = Spree::Config.searcher_class.new(params) @products = @searcher.retrieve_products respond_with(@products) end spree/core/app/controllers/spree/products_controller.rb Controller Override 80 Saturday, February 18, 12

Slide 95

Slide 95 text

module Spree class ProductsController < BaseController HTTP_REFERER_REGEXP = /^https?:\/\/[^\/]+\/t\/([a-z0-9\-\/]+)$/ rescue_from ActiveRecord::RecordNotFound, :with => :render_404 helper 'spree/taxons' respond_to :html def index @searcher = Spree::Config.searcher_class.new(params) @products = @searcher.retrieve_products respond_with(@products) end Spree::ProductsController.class_eval do before_filter :get_collection_products, :only => :show private def get_collection_products @collection_products ||= @object.collection.products.not_deleted end end spree/core/app/controllers/spree/products_controller.rb some_extension/app/controllers/spree/products_controller_decorator.rb Controller Override 80 Saturday, February 18, 12

Slide 96

Slide 96 text

How do they get auto-loaded? Decorators 81 Saturday, February 18, 12

Slide 97

Slide 97 text

Rails Application config/application.rb module MyStore class Application < Rails::Application config.to_prepare do # Load application's model / class decorators Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c| Rails.configuration.cache_classes ? require(c) : load(c) end end 82 Saturday, February 18, 12

Slide 98

Slide 98 text

Spree Extension lib//engine.rb def self.activate Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator*.rb")) do |c| Rails.configuration.cache_classes ? require(c) : load(c) end end 83 Saturday, February 18, 12

Slide 99

Slide 99 text

How Spree Employs These Techniques 84 Saturday, February 18, 12

Slide 100

Slide 100 text

spree_core + spree_auth core 85 Saturday, February 18, 12

Slide 101

Slide 101 text

spree_core + spree_auth core auth 85 Saturday, February 18, 12

Slide 102

Slide 102 text

User Model - auth replaces core Integration Point #1 86 Saturday, February 18, 12

Slide 103

Slide 103 text

core/app/models/spree/user.rb spree_core + spree_auth 87 Saturday, February 18, 12

Slide 104

Slide 104 text

core/app/models/spree/user.rb auth/app/models/spree/user.rb spree_core + spree_auth 87 Saturday, February 18, 12

Slide 105

Slide 105 text

core/app/models/spree/user.rb auth/app/models/spree/user.rb spree_core + spree_auth 88 Saturday, February 18, 12

Slide 106

Slide 106 text

spree/core/app/models/spree/user.rb 1 # Default implementation of User. This class is intended to be modified by extensions (ex. spree_auth) 2 module Spree 3 class User < ActiveRecord::Base 4 5 has_many :orders 6 7 belongs_to :ship_address, :foreign_key => 'ship_address_id', :class_name => 'Spree::Address' 8 belongs_to :bill_address, :foreign_key => 'bill_address_id', :class_name => 'Spree::Address' 9 10 scope :registered 11 12 def anonymous? 13 false 14 end 15 16 # Creates an anonymous user 17 def self.anonymous! 18 create 19 end 20 21 attr_accessor :password 22 attr_accessor :password_confirmation 23 end 24 end spree_core ‘user’ 89 Saturday, February 18, 12

Slide 107

Slide 107 text

1 module Spree 2 class User < ActiveRecord::Base 3 devise :database_authenticatable, :token_authenticatable, :registerable, :recoverable, 4 :rememberable, :trackable, :validatable, :encryptable, :encryptor => 'authlogic_sha512' 5 6 has_many :orders 7 has_and_belongs_to_many :roles, :join_table => 'spree_roles_users' 8 belongs_to :ship_address, :foreign_key => 'ship_address_id', :class_name => 'Spree::Address' 9 belongs_to :bill_address, :foreign_key => 'bill_address_id', :class_name => 'Spree::Address' 10 11 before_save :check_admin 12 before_validation :set_login 13 14 # Setup accessible (or protected) attributes for your model 15 attr_accessible :email, :password, :password_confirmation, :remember_me, :persistence_token 16 17 users_table_name = User.table_name 18 roles_table_name = Role.table_name 19 20 scope :admin, lambda { includes(:roles).where("#{roles_table_name}.name" => "admin") } 21 scope :registered, where("#{users_table_name}.email NOT LIKE ?", "%@example.net") 22 23 # Creates an anonymous user. An anonymous user is basically an auto-generated +User+ account that is created for the customer 24 # behind the scenes and its completely transparently to the customer. All +Orders+ must have a +User+ so this is necessary 25 # when adding to the "cart" (which is really an order) and before the customer has a chance to provide an email or to register. 26 def self.anonymous! 27 token = User.generate_token(:persistence_token) 28 User.create(:email => "#{token}@example.net", :password => token, :password_confirmation => token, :persistence_token => token) 29 end 30 31 def anonymous? 32 email =~ /@example.net$/ 33 end spree/auth/app/models/spree/user.rb spree_auth ‘user’ 90 Saturday, February 18, 12

Slide 108

Slide 108 text

Checkout Controller - spree_auth enhances spree_core Integration Point #2 91 Saturday, February 18, 12

Slide 109

Slide 109 text

spree/core/app/controllers/spree/checkout_controller.rb • No references to user • Provides basic checkout tasks spree_core ‘checkout’ 92 Saturday, February 18, 12

Slide 110

Slide 110 text

spree/auth/app/controllers/spree/checkout_controller_decorator.rb 1 Spree::CheckoutController.class_eval do 2 before_filter :check_authorization 3 before_filter :check_registration, :except => [:registration, :update_registration] 4 5 helper 'spree/users' 6 7 def registration 8 @user = Spree::User.new 9 end 10 11 def update_registration ....... 21 end 22 23 private 24 def check_authorization 25 authorize!(:edit, current_order, session[:access_token]) 26 end 27 28 # Introduces a registration step whenever the +registration_step+ preference is true. 29 def check_registration 30 return unless Spree::Auth::Config[:registration_step] 31 return if current_user or current_order.email spree_auth ‘checkout’ 93 Saturday, February 18, 12

Slide 111

Slide 111 text

Integration Point #3 User/Order Association - spree_auth utilizes spree_core placeholders 94 Saturday, February 18, 12

Slide 112

Slide 112 text

Significant User Events • Registration • Login • Change Password 95 Saturday, February 18, 12

Slide 113

Slide 113 text

spree/core/lib/spree/core/controller_helpers.rb 1 module Spree 2 module Core 3 module ControllerHelpers 4 .... 5 .... 6 def associate_user 7 return unless current_user and current_order 8 current_order.associate_user!(current_user) 9 session[:guest_token] = nil 10 end spree_core ‘helper’ 96 Saturday, February 18, 12

Slide 114

Slide 114 text

class Spree::UserPasswordsController < Devise::PasswordsController include Spree::Core::ControllerHelpers helper 'spree/users', 'spree/base' after_filter :associate_user spree/auth/app/controllers/spree/user_passwords_controller.rb spree/core/lib/spree/core/controller_helpers.rb 1 module Spree 2 module Core 3 module ControllerHelpers 4 .... 5 .... 6 def associate_user 7 return unless current_user and current_order 8 current_order.associate_user!(current_user) 9 session[:guest_token] = nil 10 end spree/auth/app/models/spree/order_decorator.rb Spree::Order.class_eval do token_resource # Associates the specified user with the order and destroys any previous association with guest user if # necessary. def associate_user!(user) self.user = user self.email = user.email # disable validations since this can cause issues when associating an incomplete address during the address step save(:validate => false) end end 97 Saturday, February 18, 12

Slide 115

Slide 115 text

Integration Point #4 Deface overrides provide login links 98 Saturday, February 18, 12

Slide 116

Slide 116 text

spree_core spree_core + spree_auth 99 Saturday, February 18, 12

Slide 117

Slide 117 text

Build an extension 100 Saturday, February 18, 12

Slide 118

Slide 118 text

Spree Reviews - Capabilities 1 Submit a review 101 Saturday, February 18, 12

Slide 119

Slide 119 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 102 Saturday, February 18, 12

Slide 120

Slide 120 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating 103 Saturday, February 18, 12

Slide 121

Slide 121 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating Spree Reviews - Capabilities 4 Rate existing reviews 104 Saturday, February 18, 12

Slide 122

Slide 122 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating 4 Rate existing reviews 5 Admin approve/deny 105 Saturday, February 18, 12

Slide 123

Slide 123 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating 4 Rate existing reviews 5 Admin approve/deny 6 Admin configuration 106 Saturday, February 18, 12

Slide 124

Slide 124 text

$ spree extension reviews $ cd spree_reviews $ bundle exec rake test_app start coding 107 Saturday, February 18, 12

Slide 125

Slide 125 text

extension directory structure 108 Saturday, February 18, 12

Slide 126

Slide 126 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating 4 Rate existing reviews 5 Admin approve/deny 6 Admin configuration 109 Saturday, February 18, 12

Slide 127

Slide 127 text

What should be in the model? Submit a Review 110 Saturday, February 18, 12

Slide 128

Slide 128 text

What should be in the model? $ bundle exec rails g model review product_id:integer Submit a Review 110 Saturday, February 18, 12

Slide 129

Slide 129 text

What should be in the model? $ bundle exec rails g model review product_id:integer name:string Submit a Review 110 Saturday, February 18, 12

Slide 130

Slide 130 text

What should be in the model? $ bundle exec rails g model review product_id:integer name:string rating:integer Submit a Review 110 Saturday, February 18, 12

Slide 131

Slide 131 text

What should be in the model? $ bundle exec rails g model review product_id:integer name:string rating:integer review:text Submit a Review 110 Saturday, February 18, 12

Slide 132

Slide 132 text

111 Saturday, February 18, 12

Slide 133

Slide 133 text

app/views/spree/shared/_reviews.html 1
2 <%= link_to t('write_your_own_review'), new_product_review_path(@product) %> 3
111 Saturday, February 18, 12

Slide 134

Slide 134 text

app/views/spree/products/show.html.erb 1
2 3
4
5 <%= render :partial => 'image' %> 6
7
8 <%= render :partial => 'thumbnails', :locals => { :product => @product } %> 9
10
11 12
13 <%= render :partial => 'properties' %> 14
15 16
112 Saturday, February 18, 12

Slide 135

Slide 135 text

app/overrides/add_reviews_after_product_properties.rb app/views/spree/products/show.html.erb 1
2 3
4
5 <%= render :partial => 'image' %> 6
7
8 <%= render :partial => 'thumbnails', :locals => { :product => @product } %> 9
10
11 12
13 <%= render :partial => 'properties' %> 14
15 16
112 Saturday, February 18, 12

Slide 136

Slide 136 text

app/overrides/add_reviews_after_product_properties.rb app/views/spree/products/show.html.erb 1
2 3
4
5 <%= render :partial => 'image' %> 6
7
8 <%= render :partial => 'thumbnails', :locals => { :product => @product } %> 9
10
11 12
13 <%= render :partial => 'properties' %> 14
15 16
Deface::Override.new(:virtual_path => "spree/products/show", 112 Saturday, February 18, 12

Slide 137

Slide 137 text

app/overrides/add_reviews_after_product_properties.rb app/views/spree/products/show.html.erb 1
2 3
4
5 <%= render :partial => 'image' %> 6
7
8 <%= render :partial => 'thumbnails', :locals => { :product => @product } %> 9
10
11 12
13 <%= render :partial => 'properties' %> 14
15 16
Deface::Override.new(:virtual_path => "spree/products/show", :name => "add_submit_review_link", 112 Saturday, February 18, 12

Slide 138

Slide 138 text

app/overrides/add_reviews_after_product_properties.rb app/views/spree/products/show.html.erb 1
2 3
4
5 <%= render :partial => 'image' %> 6
7
8 <%= render :partial => 'thumbnails', :locals => { :product => @product } %> 9
10
11 12
13 <%= render :partial => 'properties' %> 14
15 16
Deface::Override.new(:virtual_path => "spree/products/show", :name => "add_submit_review_link", :insert_after => "[data-hook='product_properties'], #product_properties[data-hook]" 112 Saturday, February 18, 12

Slide 139

Slide 139 text

app/overrides/add_reviews_after_product_properties.rb app/views/spree/products/show.html.erb 1
2 3
4
5 <%= render :partial => 'image' %> 6
7
8 <%= render :partial => 'thumbnails', :locals => { :product => @product } %> 9
10
11 12
13 <%= render :partial => 'properties' %> 14
15 16
Deface::Override.new(:virtual_path => "spree/products/show", :name => "add_submit_review_link", :insert_after => "[data-hook='product_properties'], #product_properties[data-hook]" :partial => "spree/shared/reviews") 112 Saturday, February 18, 12

Slide 140

Slide 140 text

<%= render :partial => 'image' %>
<%= render :partial => 'thumbnails', :locals => { :product => @product } %>
<%= render :partial => 'properties' %>
app/views/spree/products/show.html.erb 113 Saturday, February 18, 12

Slide 141

Slide 141 text

<%= link_to t('write_your_own_review'), new_product_review_path(@product) %>
<%= render :partial => 'image' %>
<%= render :partial => 'thumbnails', :locals => { :product => @product } %>
<%= render :partial => 'properties' %>
app/views/spree/shared/_reviews.html app/views/spree/products/show.html.erb 113 Saturday, February 18, 12

Slide 142

Slide 142 text

<%= render :partial => 'image' %>
<%= render :partial => 'thumbnails', :locals => { :product => @product } %>
<%= render :partial => 'properties' %>
<%= link_to t('write_your_own_review'), new_product_review_path(@product) %>
114 Saturday, February 18, 12

Slide 143

Slide 143 text

Spree Reviews Dummy App $ spree extension reviews $ cd spree_reviews $ bundle exec rake test_app $ cd spec/dummy $ bundle exec rails server 115 Saturday, February 18, 12

Slide 144

Slide 144 text

116 Saturday, February 18, 12

Slide 145

Slide 145 text

1
2 <%= link_to t('write_your_own_review'), new_product_review_path(@product) %> 3
116 Saturday, February 18, 12

Slide 146

Slide 146 text

config/routes.rb Create Route Spree::Core::Engine.routes.append do resources :products do resources :reviews end end 117 Saturday, February 18, 12

Slide 147

Slide 147 text

118 Saturday, February 18, 12

Slide 148

Slide 148 text

119 Saturday, February 18, 12

Slide 149

Slide 149 text

$ bundle exec rails g controller spree/reviews class Spree::ReviewsController < Spree::BaseController before_filter :load_product, :only => [:index, :new, :create] def new @review = Spree::Review.new(:product => @product) end private def load_product @product = Spree::Product.find_by_permalink!(params[:product_id]) end end app/controllers/spree/reviews_controller.rb Create Reviews 120 Saturday, February 18, 12

Slide 150

Slide 150 text

121 Saturday, February 18, 12

Slide 151

Slide 151 text

<%= I18n.t('leave_us_a_review_for') + ' "' + @product.name + '"' %>

<%= render 'form', { :review => @review, :product => @product } %> app/views/spree/reviews/new.html.erb app/views/spree/reviews/_form.html.erb <%= form_for review, :url => product_reviews_path(product), :html => {:method => :post} do |f| %>

<%= f.label :rating %> <%= f.text_field :rating%>

<%= f.label :name %> <%= f.text_field :name%>

<%= f.label :title %> <%= f.text_field :title %>

<%= f.label :review %> <%= f.text_area :review %>

<%= f.submit I18n.t('submit_your_review') %>

<% end %> Standard ‘new’ html 122 Saturday, February 18, 12

Slide 152

Slide 152 text

123 Saturday, February 18, 12

Slide 153

Slide 153 text

124 Saturday, February 18, 12

Slide 154

Slide 154 text

125 Saturday, February 18, 12

Slide 155

Slide 155 text

def create @review = Spree::Review.new(params[:review]) @review.product = @product if @review.save flash[:notice] = t('review_successfully_submitted') redirect_to (product_path(@product)) else render :action => "new" end end Spree::ReviewsController#create 126 Saturday, February 18, 12

Slide 156

Slide 156 text

127 Saturday, February 18, 12

Slide 157

Slide 157 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating 4 Rate existing reviews 5 Admin approve/deny 6 Admin configuration 128 Saturday, February 18, 12

Slide 158

Slide 158 text

app/views/spree/shared/_reviews.html 1
2
<%= t(:reviews) %>
3 4 <% for review in @product.reviews %> 5 <%= render :partial => 'spree/shared/review', :locals => {:review => review} %> 6 <% end %> 7 8
9 <%= link_to t('write_your_own_review'), new_product_review_path(@product) %> 10
11 12
Revisit our partial 129 Saturday, February 18, 12

Slide 159

Slide 159 text

app/views/spree/shared/_review.html
<%= review.name %>   <%= review.title %>
<%= t('submitted_on') %><%= l review.created_at.to_date %>
<%= review.review %>
partial within a partial 130 Saturday, February 18, 12

Slide 160

Slide 160 text

131 Saturday, February 18, 12

Slide 161

Slide 161 text

app/views/spree/shared/_reviews.html 1
2
<%= t(:reviews) %>
3 4 <% for review in @product.reviews %> 5 <%= render :partial => 'spree/shared/review', :locals => {:review => review} %> 6 <% end %> 7 8
9 <%= link_to t('write_your_own_review'), new_product_review_path(@product) %> 10
11 12
Offending line #4 132 Saturday, February 18, 12

Slide 162

Slide 162 text

app/models/spree/product_decorator.rb 1 # Add access to reviews to the product model 2 Spree::Product.class_eval do 3 has_many :reviews 4 end Decorate the model 133 Saturday, February 18, 12

Slide 163

Slide 163 text

134 Saturday, February 18, 12

Slide 164

Slide 164 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating 4 Rate existing reviews 5 Admin approve/deny 6 Admin configuration 135 Saturday, February 18, 12

Slide 165

Slide 165 text

app/views/spree/shared/_reviews.html 1
2
<%= t(:reviews) %>
3 <%= render :partial => 'spree/shared/rating', :locals => {:product => @product, :review => 0} %> 4 5 <% for review in @product.reviews.approval_filter %> 6 <%= render :partial => 'spree/shared/review', :locals => {:review => review} %> 7
8 <% end %> 9 10
11 <%= link_to t('write_your_own_review'), new_product_review_path(@product) %> 12
13
New line #3 136 Saturday, February 18, 12

Slide 166

Slide 166 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating 4 Rate existing reviews 5 Admin approve/deny 6 Admin configuration 137 Saturday, February 18, 12

Slide 167

Slide 167 text

https://github.com/ spree/ spree_reviews 138 Saturday, February 18, 12

Slide 168

Slide 168 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating 4 Rate existing reviews 5 Admin approve/deny 6 Admin configuration 139 Saturday, February 18, 12

Slide 169

Slide 169 text

app/overrides/add_reviews_tab_to_admin.rb 1 Deface::Override.new(:virtual_path => "spree/layouts/admin", 2 :name => "reviews_admin_tab", 3 :insert_bottom => "[data-hook='admin_tabs']", 4 :text => "<%= tab(:reviews, :label => 'review_management') %>") Admin Tab 140 Saturday, February 18, 12

Slide 170

Slide 170 text

141 Saturday, February 18, 12

Slide 171

Slide 171 text

class Spree::Admin::ReviewsController < Spree::Admin::ResourceController helper Spree::ReviewsHelper def index @unapproved_reviews = Spree::Review.not_approved.find(:all, :order => "created_at DESC") @approved_reviews = Spree::Review.approved.find(:all, :order => "created_at DESC") end def approve r = Spree::Review.find(params[:id]) if r.update_attribute(:approved, true) r.product.recalculate_rating flash[:notice] = t("info_approve_review") else flash[:error] = t("error_approve_review") end redirect_to admin_reviews_path end end Implement a Spree Admin Controller 142 Saturday, February 18, 12

Slide 172

Slide 172 text

Spree Reviews - Capabilities 1 Submit a review 2 See existing reviews 3 See average rating 4 Rate existing reviews 5 Admin approve/deny 6 Admin configuration 143 Saturday, February 18, 12

Slide 173

Slide 173 text

144 Saturday, February 18, 12

Slide 174

Slide 174 text

spree/core/app/views/spree/admin/configurations/index.html.erb 1

<%= t(:configurations) %>

2 3 4 5 6 <%= t(:system) %> 7 <%= t(:description) %> 8 9 10 11 12 <%= link_to t(:general_settings), admin_general_settings_path %> 13 <%= t(:general_settings_description) %> 14 15 16 <%= link_to t(:mail_methods), admin_mail_methods_path %> 17 <%= t(:email_server_settings_description) %> 18 19 20 <%= link_to t(:tax_categories), admin_tax_categories_path %> 21 <%= t(:tax_categories_setting_description) %> 22 23 ..... 24 ..... 25 ..... 26 27 <%= link_to t(:zones), admin_zones_path %> 28 <%= t(:zone_setting_description) %> 29 30 31 Original Configuration HTML 145 Saturday, February 18, 12

Slide 175

Slide 175 text

app/overrides/add_reviews_to_admin_configuration_menu.rb Deface::Override.new( :virtual_path => "spree/admin/configurations/index", :name => "add_reviews_to_admin_configuration_menu", :insert_bottom => "[data-hook='admin_configurations_menu'], #admin_configurations_menu[data-hook]", :text => "<%= configurations_menu_item(t('spree_reviews.review_settings'), admin_review_settings_path, t('spree_reviews.manage_review_settings')) %>" ) Override for Configuration Item Deface 146 Saturday, February 18, 12

Slide 176

Slide 176 text

147 Saturday, February 18, 12

Slide 177

Slide 177 text

148 Saturday, February 18, 12

Slide 178

Slide 178 text

Spree Reviews - Preferences class Spree::Review < ActiveRecord::Base .... scope :preview, :limit => Spree::Reviews::Config[:preview_size], :order=>"created_at desc" app/models/spree/review.rb 149 Saturday, February 18, 12

Slide 179

Slide 179 text

1 class Spree::ReviewsAbility 2 include CanCan::Ability 3 4 def initialize(user) 5 can :create, Spree::Review do |review| 6 user.has_role?(:user) || !Spree::Reviews::Config[:require_login] 7 end 8 can :create, Spree::FeedbackReview do |review| 9 user.has_role?(:user) || !Spree::Reviews::Config[:require_login] 10 end 11 end 12 end Spree Reviews - Preferences app/models/spree/reviews_ability.rb 150 Saturday, February 18, 12

Slide 180

Slide 180 text

Spree Reviews - CanCan 1 module SpreeReviews 2 class Engine < Rails::Engine 3 engine_name 'spree_reviews' 4 5 config.autoload_paths += %W(#{config.root}/lib) 6 7 def self.activate 8 Dir.glob(File.join(File.dirname(__FILE__),"../../app/**/*_decorator*.rb")) do |c| 9 Rails.env.production? ? require(c) : load(c) 10 end 11 Spree::Ability.register_ability(Spree::ReviewsAbility) 12 end 13 14 config.to_prepare &method(:activate).to_proc 15 end 16 end lib/spree_reviews/engine.rb 151 Saturday, February 18, 12

Slide 181

Slide 181 text

Extension Assets 152 Saturday, February 18, 12

Slide 182

Slide 182 text

assets/javascripts/store/all.js assets/javascripts/admin/all.js assets/stylesheets/store/all.css assets/stylesheets/admin/all.css (auto-generated) spree apps have 153 Saturday, February 18, 12

Slide 183

Slide 183 text

//= require store/spree_core //= require store/spree_auth //= require store/spree_api //= require store/spree_promo //= require_tree . assets/javascripts/store/all.js spree apps have 154 Saturday, February 18, 12

Slide 184

Slide 184 text

assets/javascripts/store/spree_some_extension.js assets/javascripts/admin/spree_some_extension.js assets/stylesheets/store/spree_some_extension.css assets/stylesheets/admin/spree_some_extension.css spree extensions have (auto-generated) 155 Saturday, February 18, 12

Slide 185

Slide 185 text

lib/generators/spree_some_extension/install/install_generator.rb module SpreeSomeExtension module Generators class InstallGenerator < Rails::Generators::Base def add_javascripts append_file "app/assets/javascripts/store/all.js", "//= require store/spree_some_extension\n" append_file "app/assets/javascripts/admin/all.js", "//= require admin/spree_some_extension\n" end def add_stylesheets inject_into_file "app/assets/stylesheets/store/all.css", " *= require store/spree_some_extension\n", :before => /\*\//, :verbose => true inject_into_file "app/assets/stylesheets/admin/all.css", " *= require admin/spree_some_extension\n", :before => /\*\//, :verbose => true end extension assets 156 Saturday, February 18, 12

Slide 186

Slide 186 text

vendor/assets/javascripts/store/jquery.rating.js reviews assets (not auto-generated) 157 Saturday, February 18, 12

Slide 187

Slide 187 text

lib/generators/spree_reviews/install/install_generator.rb 1 module SpreeReviews 2 module Generators 3 class InstallGenerator < Rails::Generators::Base 4 5 def add_javascripts 6 append_file "app/assets/javascripts/store/all.js", "//= require store/jquery.rating\n" 7 end 8 9 def add_stylesheets 10 inject_into_file "app/assets/stylesheets/store/all.css", " *= require store/spree_reviews \n", :before => /\*\//, :verbose => true 11 end reviews assets 158 Saturday, February 18, 12

Slide 188

Slide 188 text

my_store/app/assets/javascripts/store/all.js //= require store/spree_core //= require store/spree_auth //= require store/spree_api //= require store/spree_promo //= require_tree . //= require store/jquery.rating your spree app - voila 159 Saturday, February 18, 12

Slide 189

Slide 189 text

reviews migrations 160 Saturday, February 18, 12

Slide 190

Slide 190 text

1 module SpreeReviews 2 module Generators 3 class InstallGenerator < Rails::Generators::Base 4 5 def add_javascripts 6 append_file "app/assets/javascripts/store/all.js", "//= require store/jquery.rating\n" 7 end 8 9 def add_stylesheets 10 inject_into_file "app/assets/stylesheets/store/all.css", " *= require store/ spree_reviews\n", :before => /\*\//, :verbose => true 11 end 12 13 def add_migrations 14 run 'rake railties:install:migrations FROM=spree_reviews' 15 end 16 17 def run_migrations 18 res = ask "Would you like to run the migrations now? [Y/n]" 19 if res == "" || res.downcase == "y" 20 run 'rake db:migrate' 21 else 22 puts "Skipping rake db:migrate, don't forget to run it!" 23 end 24 end 25 end 26 end 27 end 161 Saturday, February 18, 12

Slide 191

Slide 191 text

finished product - spree reviews gem 'spree', ‘~> 1.0.0’ gem 'spree_reviews', :git => 'git://github.com/spree/spree_reviews.git bundle exec rails g spree_reviews:install 162 Saturday, February 18, 12

Slide 192

Slide 192 text

Versionfile "1.0.x" => { :branch => 'master' } "0.70.x" => { :ref => '41c5318612a38f9173226379c3790c6c79e11ffe' } 163 Saturday, February 18, 12

Slide 193

Slide 193 text

Learning More 164 Saturday, February 18, 12

Slide 194

Slide 194 text

http://spreecommerce.com 165 Saturday, February 18, 12

Slide 195

Slide 195 text

http://github.com/spree 166 Saturday, February 18, 12

Slide 196

Slide 196 text

@spreecommerce 167 Saturday, February 18, 12