spree_related_products 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ Related Products
2
+ ================
3
+
4
+ This extension provides a generic way for you to define different types of relationships between your products, by defining a RelationType for each type of relationship you'd like to maintain.
5
+
6
+ You can manage RelationTypes via the admin configuration menu, and you can maintain product relationships via __Related Products__ tab on the edit product UI.
7
+
8
+ Possible uses
9
+ -------------
10
+
11
+ * Accessories
12
+
13
+ * Cross Sells
14
+
15
+ * Up Sells
16
+
17
+ * Compatible Products
18
+
19
+ * Replacement Products
20
+
21
+ * Warranty & Support Products
22
+
23
+
24
+
25
+ Relation Types
26
+ --------------
27
+ When you create a RelationType you can access that set of related products by referencing the relation_type name, see below for an example:
28
+
29
+ rt = RelationType.create(:name => "Accessories", :applies_to => "Product")
30
+ => #<RelationType id: 4, name: "Accessories" ...>
31
+ product = Product.last
32
+ => #<Product id: 1060500592 ...>
33
+ product.accessories
34
+ => []
35
+
36
+ You can access all related products regardless of RelationType by:
37
+
38
+ product.relations
39
+ => []
40
+
41
+
42
+ Discounts
43
+ ---------
44
+ If you install the spree-automatic-coupons extension you can also specify a discount amount to be applied if a customer purchases both products. Note: In order for the coupon to be automatically applied, you must create a coupon of type: __RelatedProductDiscount__ and leave the __code__ value empty (blank codes are required for coupons to be automatically applied).
45
+
@@ -0,0 +1,7 @@
1
+ class Admin::RelationTypesController < Admin::BaseController
2
+ resource_controller
3
+
4
+ update.wants.html { redirect_to collection_url }
5
+ create.wants.html { redirect_to collection_url }
6
+ destroy.success.wants.js { render_js_for_destroy }
7
+ end
@@ -0,0 +1,21 @@
1
+ class Admin::RelationsController < Admin::BaseController
2
+ resource_controller
3
+ belongs_to :product
4
+
5
+ actions :create, :destroy
6
+
7
+ create do
8
+ flash nil
9
+ end
10
+
11
+ create.before do
12
+ object.related_to = Variant.find(params[:relation][:related_to_id]).product
13
+ end
14
+
15
+ create.response do |wants|
16
+ wants.html { render :partial => "admin/products/related_products_table", :locals => {:product => @product}, :layout => false}
17
+ end
18
+
19
+ destroy.success.wants.js { render_js_for_destroy }
20
+
21
+ end
@@ -0,0 +1,2 @@
1
+ module Admin::RelationTypesHelper
2
+ end
@@ -0,0 +1,2 @@
1
+ module Admin::RelationsHelper
2
+ end
@@ -0,0 +1,49 @@
1
+ class Calculator::RelatedProductDiscount < Calculator
2
+ preference :item_total_threshold, :decimal, :default => 5
3
+
4
+ def self.description
5
+ I18n.t("related_product_discount")
6
+ end
7
+
8
+ def self.register
9
+ super
10
+ Coupon.register_calculator(self)
11
+ end
12
+
13
+ def compute(object)
14
+ if object.is_a?(Array)
15
+ return if object.empty?
16
+ order = object.first.order
17
+ else
18
+ order = object
19
+ end
20
+
21
+ return unless eligible?(order)
22
+ total = order.line_items.inject(0) do |total, line_item|
23
+ relations = Relation.find(:all, :conditions => ["discount_amount <> 0.0 AND relatable_type = ? AND relatable_id = ?", "Product", line_item.variant.product.id])
24
+ discount_applies_to = relations.map {|rel| rel.related_to.master }
25
+
26
+ order.line_items.each do |li|
27
+ if discount_applies_to.include? li.variant
28
+ discount = relations.detect {|rel| rel.related_to.variant == li.variant}.discount_amount
29
+
30
+ total += if li.quantity < line_item.quantity
31
+ (discount * li.quantity)
32
+ else
33
+ (discount * line_item.quantity)
34
+ end
35
+ end
36
+ end
37
+
38
+ total
39
+ end
40
+
41
+ total == 0 ? nil : total
42
+ end
43
+
44
+ def eligible?(order)
45
+ order.line_items.any? { |line_item| Relation.exists?(["discount_amount <> 0.0 AND relatable_type = ? AND relatable_id = ?", "Product", line_item.variant.product.id])}
46
+ end
47
+
48
+ end
49
+
@@ -0,0 +1,7 @@
1
+ class Relation < ActiveRecord::Base
2
+ belongs_to :relation_type
3
+ belongs_to :relatable, :polymorphic => true
4
+ belongs_to :related_to, :polymorphic => true
5
+
6
+ validates_presence_of :relation_type, :relatable, :related_to
7
+ end
@@ -0,0 +1,3 @@
1
+ class RelationType < ActiveRecord::Base
2
+ has_many :relations
3
+ end
@@ -0,0 +1,5 @@
1
+ <% if url_options_authenticate?(:controller => 'admin/products') %>
2
+ <li<%= ' class="active"' if current == "Related Products" %>>
3
+ <%= link_to t("related_products"), related_admin_product_url(@product) %>
4
+ </li>
5
+ <% end %>
@@ -0,0 +1,26 @@
1
+ <table class="index">
2
+ <thead>
3
+ <tr>
4
+ <th><%= t('name') %></th>
5
+ <% if Spree::Extension.descendants.map(&:to_s).include? "AutomaticCouponsExtension" %>
6
+ <th><%= t('discount_amount') %></th>
7
+ <% end %>
8
+ <th><%= t('type') %></th>
9
+ <th></th>
10
+ </tr>
11
+ </thead>
12
+ <tbody>
13
+ <% product.relations.each do |relation| %>
14
+ <tr id="<%= dom_id relation %>">
15
+ <td><%= relation.related_to.name %></td>
16
+ <% if Spree::Extension.descendants.map(&:to_s).include? "AutomaticCouponsExtension" %>
17
+ <td><%= relation.discount_amount != 0 ? number_to_currency(relation.discount_amount) : "-" %></td>
18
+ <% end %>
19
+ <td><%= relation.relation_type.name %></td>
20
+ <td width="70px">
21
+ <%= link_to_delete relation, {:url => admin_product_relation_url(relation.relatable, relation)} %>
22
+ </td>
23
+ </tr>
24
+ <% end %>
25
+ </tbody>
26
+ </table>
@@ -0,0 +1,50 @@
1
+ <%= render :partial => 'admin/shared/product_sub_menu' %>
2
+
3
+ <%= render :partial => 'admin/shared/product_tabs', :locals => {:current => "Related Products"} %>
4
+
5
+
6
+ <% if @relation_types.empty? %>
7
+ <%= t("no_relation_types") %>
8
+ <% else %>
9
+ <div id="add-line-item">
10
+ <fieldset>
11
+ <legend><%= t('add_related_product') %></legend>
12
+ <div style="float:left;width:<%= Spree::Extension.descendants.map(&:to_s).include?("AutomaticCouponsExtension") ? "40" : "55" %>%;margin-right:5%;">
13
+ <%= label_tag :add_product_name, t("name_or_sku") %>
14
+ <%= text_field_tag :add_product_name, {}, :class => 'fullwidth title' %>
15
+ <%= hidden_field_tag :add_variant_id %>
16
+ </div>
17
+ <% if Spree::Extension.descendants.map(&:to_s).include? "AutomaticCouponsExtension" %>
18
+ <div style="float:left;width:10%;margin-right:5%;">
19
+ <%= label_tag :add_discount, t("discount_amount") %>
20
+ <%= text_field_tag :add_discount, 0.0, :style => 'margin: 0pt; padding: 4px; font-size: 1.5em; width: 98%;' %>
21
+ </div>
22
+ <% end %>
23
+ <div style="float:left;width:20%;margin-right:5%;">
24
+ <%= label_tag :add_type, t("type") %>
25
+ <%= select_tag :add_type, options_for_select(@relation_types.map{|rt| [rt.name, rt.id]}), :style => 'margin: 0pt; padding: 4px; font-size: 1.5em; width: 98%;' %>
26
+ </div>
27
+ <div style="float: left; width: 15%; padding-top: 17px;">
28
+ <%= button_link_to_remote t("add"),
29
+ {:url => admin_product_relations_url(@product),
30
+ :method => :post,
31
+ :with => "'relation[related_to_type]=Product&relation[related_to_id]=' + $('#add_variant_id').val() + '&relation[relation_type_id]=' + $('#add_type').val() + '&relation[discount_amount]=' + $('#add_discount').val()",
32
+ :before => "if($('#add_variant_id').val()==''){ return false; }",
33
+ :after => "$('#add_product_name').val('');$('#add_variant_id').val('');$('#add_quantity').val(1)",
34
+ :update => "products-table-wrapper"}, :icon => 'add' %>
35
+ </div>
36
+
37
+ </fieldset>
38
+ </div>
39
+
40
+ <div id="products-table-wrapper">
41
+ <%= render :partial => "related_products_table", :locals => {:product => @product} %>
42
+ </div>
43
+
44
+ <% content_for :head do %>
45
+ <%= javascript_tag "var expand_variants = false;" %>
46
+ <%= javascript_include_tag 'admin/orders/edit.js' %>
47
+ <%= stylesheet_link_tag 'admin/edit_orders.css' %>
48
+ <% end %>
49
+
50
+ <% end %>
@@ -0,0 +1,22 @@
1
+ <%- locals = {:f => f} -%>
2
+ <% hook :admin_relation_type_form_fields, locals do %>
3
+ <% f.field_container :name do %>
4
+ <%= f.label :name, t("name") %><br />
5
+ <%= f.text_field :name, :class => 'fullwidth title' %>
6
+ <%= error_message_on :relation_type, :name %>
7
+ <% end %>
8
+
9
+ <% f.field_container :applies_to do %>
10
+ <%= f.label :applies_to, t("applies_to") %><br />
11
+ <%= f.text_field :applies_to, :value => f.object.nil? ? "Product" : f.object.applies_to, :class => 'fullwidth title' %>
12
+ <%= error_message_on :relation_type, :applies_to%>
13
+ <% end %>
14
+
15
+ <% f.field_container :description do %>
16
+ <%= f.label :description, t("description") %><br />
17
+ <%= f.text_area :description, {:cols => 60, :rows => 2, :class => 'fullwidth'} %>
18
+ <%= error_message_on :relation_type, :description %>
19
+ <% end %>
20
+
21
+ <% end %>
22
+
@@ -0,0 +1,16 @@
1
+ <%= render :partial => 'admin/shared/configuration_menu' %>
2
+
3
+ <% hook :admin_relation_type_edit_form_header do %>
4
+ <h1><%= t("editing_relation_type") %></h1>
5
+ <%= error_messages_for :relation_type %>
6
+ <% end %>
7
+
8
+ <% hook :admin_relation_type_edit_form do %>
9
+ <% form_for(@relation_type, :url => object_url, :html => { :method => :put }) do |f| %>
10
+ <%= render :partial => "form", :locals => { :f => f } %>
11
+
12
+ <% hook :admin_relation_type_edit_form_buttons do %>
13
+ <%= render :partial => "admin/shared/edit_resource_links" %>
14
+ <% end %>
15
+ <% end %>
16
+ <% end %>
@@ -0,0 +1,46 @@
1
+ <%= render :partial => 'admin/shared/configuration_menu' %>
2
+
3
+ <div class='toolbar'>
4
+ <ul class='actions'>
5
+ <li>
6
+ <%= button_link_to t("new_relation_type"), new_object_url, :icon => 'add' %>
7
+ </li>
8
+ </ul>
9
+ <br class='clear' />
10
+ </div>
11
+
12
+ <h1><%= t("relation_types") %></h1>
13
+
14
+ <table class="index">
15
+ <thead>
16
+ <tr>
17
+ <% hook :admin_relation_types_index_headers do %>
18
+ <th><%= t("name") %></th>
19
+ <th><%= t("applies_to") %></th>
20
+ <th><%= t("description") %></th>
21
+ <% end %>
22
+ <th>
23
+ <% hook :admin_relation_types_index_header_actions %>
24
+ </th>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <% @relation_types.each do |relation_type| %>
29
+ <tr id="<%= dom_id relation_type %>">
30
+ <%- locals = {:relation_type => relation_type} -%>
31
+ <% hook :admin_relation_types_index_rows, locals do %>
32
+ <td><%= relation_type.name %></td>
33
+ <td><%= relation_type.applies_to %></td>
34
+ <td><%= relation_type.description %></td>
35
+ <% end %>
36
+ <td width="140px">
37
+ <% hook :admin_relation_types_index_row_actions, locals do %>
38
+ <%= link_to_edit relation_type %> &nbsp;
39
+ <%= link_to_delete relation_type %>
40
+ <% end %>
41
+ </td>
42
+ </tr>
43
+ <% end %>
44
+ </tbody>
45
+ </table>
46
+
@@ -0,0 +1,17 @@
1
+ <%= render :partial => 'admin/shared/configuration_menu' %>
2
+
3
+ <% hook :admin_relation_type_new_form_header do %>
4
+ <h1><%= t("new_relation_type") %></h1>
5
+
6
+ <%= error_messages_for :relation_type %>
7
+ <% end %>
8
+
9
+ <% hook :admin_relation_type_new_form do %>
10
+ <% form_for(:relation_type, :url => collection_url) do |f| %>
11
+ <%= render :partial => "form", :locals => { :f => f } %>
12
+
13
+ <% hook :admin_relation_type_new_form_buttons do %>
14
+ <%= render :partial => "admin/shared/new_resource_links" %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>
@@ -0,0 +1,43 @@
1
+ require 'spree_core'
2
+
3
+ module SpreeRelatedProducts
4
+ class Engine < Rails::Engine
5
+
6
+ def self.activate
7
+
8
+ # Calculator::RelatedProductDiscount.register
9
+
10
+ Product.class_eval do
11
+ has_many :relations, :as => :relatable
12
+
13
+ def self.relation_types
14
+ RelationType.find_all_by_applies_to(self.to_s, :order => :name)
15
+ end
16
+
17
+ def method_missing(method, *args)
18
+ relation_type = self.class.relation_types.detect { |rt| rt.name.downcase.gsub(" ", "_").pluralize == method.to_s.downcase }
19
+
20
+ if relation_type.nil?
21
+ super
22
+ else
23
+ relations.find_all_by_relation_type_id(relation_type.id).map(&:related_to).select {|product| product.deleted_at.nil? && product.available_on <= Time.now()}
24
+ end
25
+
26
+ end
27
+ end
28
+
29
+ Admin::ProductsController.class_eval do
30
+ def related
31
+ load_object
32
+ @relation_types = Product.relation_types
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/calculator)
39
+ config.to_prepare &method(:activate).to_proc
40
+
41
+ end
42
+ end
43
+
@@ -0,0 +1,9 @@
1
+ class SpreeRelatedProductsHooks < Spree::ThemeSupport::HookListener
2
+
3
+ insert_after :admin_product_tabs, "admin/products/related_products"
4
+
5
+ insert_after :admin_configurations_menu do
6
+ "<%= configurations_menu_item(I18n.t('relation_types'), admin_relation_types_url, I18n.t('manage_relation_types')) %>"
7
+ end
8
+
9
+ end
@@ -0,0 +1,17 @@
1
+ namespace :spree do
2
+ namespace :extensions do
3
+ namespace :related_products do
4
+ desc "Copies public assets of the Related Products to the instance public/ directory."
5
+ task :update => :environment do
6
+ is_svn_git_or_dir = proc {|path| path =~ /\.svn/ || path =~ /\.git/ || File.directory?(path) }
7
+ Dir[RelationsExtension.root + "/public/**/*"].reject(&is_svn_git_or_dir).each do |file|
8
+ path = file.sub(RelationsExtension.root, '')
9
+ directory = File.dirname(path)
10
+ puts "Copying #{path}..."
11
+ mkdir_p RAILS_ROOT + directory
12
+ cp file, RAILS_ROOT + path
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spree_related_products
3
+ version: !ruby/object:Gem::Version
4
+ hash: 5
5
+ prerelease: false
6
+ segments:
7
+ - 3
8
+ - 0
9
+ - 1
10
+ version: 3.0.1
11
+ platform: ruby
12
+ authors: []
13
+
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-03 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: spree_core
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - "="
28
+ - !ruby/object:Gem::Version
29
+ hash: -1848229955
30
+ segments:
31
+ - 0
32
+ - 30
33
+ - 0
34
+ - beta1
35
+ version: 0.30.0.beta1
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ description:
39
+ email:
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ files:
47
+ - README.markdown
48
+ - lib/spree_related_products.rb
49
+ - lib/spree_related_products_hooks.rb
50
+ - lib/tasks/related_products_extension_tasks.rake
51
+ - app/controllers/admin/relation_types_controller.rb
52
+ - app/controllers/admin/relations_controller.rb
53
+ - app/helpers/admin/relation_types_helper.rb
54
+ - app/helpers/admin/relations_helper.rb
55
+ - app/models/calculator/related_product_discount.rb
56
+ - app/models/relation.rb
57
+ - app/models/relation_type.rb
58
+ - app/views/admin/products/_related_products.html.erb
59
+ - app/views/admin/products/_related_products_table.html.erb
60
+ - app/views/admin/products/related.html.erb
61
+ - app/views/admin/relation_types/_form.html.erb
62
+ - app/views/admin/relation_types/edit.html.erb
63
+ - app/views/admin/relation_types/index.html.erb
64
+ - app/views/admin/relation_types/new.html.erb
65
+ has_rdoc: true
66
+ homepage:
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options: []
71
+
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ hash: 57
80
+ segments:
81
+ - 1
82
+ - 8
83
+ - 7
84
+ version: 1.8.7
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ requirements:
95
+ - none
96
+ rubyforge_project:
97
+ rubygems_version: 1.3.7
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: Allows multiple types of relationships between products to be defined
101
+ test_files: []
102
+