Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Port @jstirk pull request to apply to the latest namespace updates. […

…fixes #9]
  • Loading branch information...
commit 0d43d9f77d6654c8fbaca587d8e0c54fb9824b2c 1 parent ae12c0c
Jeff Dutil JDutil authored
6 .gitignore
View
@@ -1,4 +1,6 @@
-.DS_Store
*.swp
+.DS_Store
+.rvmrc
+Gemfile.lock
pkg
-
+spec/dummy
4 .rspec
View
@@ -0,0 +1,4 @@
+--color
+--format=nested
+--backtrace
+--profile
11 Gemfile
View
@@ -0,0 +1,11 @@
+source 'http://rubygems.org'
+
+# TODO: remove when 0.80.0.beta or higher is released.
+gem 'spree', :git => 'git://github.com/spree/spree.git'
+
+group :test do
+ gem 'faker'
+ gem 'factory_girl'
+end
+
+gemspec
11 README.md
View
@@ -51,3 +51,14 @@ Run:
$ bundle
$ bundle exec rails g spree_related_products:install
+
+Development
+-----------
+
+ * Fork the repo
+ * clone your repo
+ * Run `bundle`
+ * Run `bundle exec rake test_app` to create the test application in `spec/test_app`.
+ * Make your changes.
+ * Ensure specs pass by running `bundle exec rake`
+ * Submit your pull request
29 Rakefile
View
@@ -1,18 +1,17 @@
-require 'rubygems'
-require 'rake'
-require 'rake/testtask'
-require 'rake/packagetask'
-require 'rake/gempackagetask'
+require 'bundler'
+Bundler::GemHelper.install_tasks
+Bundler.setup
-spec = eval(File.read('spree_related_products.gemspec'))
+require 'rspec/core/rake_task'
+RSpec::Core::RakeTask.new
-Rake::GemPackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
+require 'spree/core/testing_support/common_rake'
+
+desc "Default Task"
+task :default => [:spec]
-desc "Release to gemcutter"
-task :release => :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
+desc "Generates a dummy app for testing"
+task :test_app do
+ ENV['LIB_NAME'] = 'spree_related_products'
+ Rake::Task['common:test_app'].invoke
+end
98 app/models/spree/calculator/related_product_discount.rb
View
@@ -1,52 +1,50 @@
module Spree
- module Calculator
- class RelatedProductDiscount < Calculator
- preference :item_total_threshold, :decimal, :default => 5
-
- def self.description
- I18n.t("related_product_discount")
- end
-
- def self.register
- super
- Spree::Coupon.register_calculator(self)
- end
-
- def compute(object)
- if object.is_a?(Array)
- return if object.empty?
- order = object.first.order
- else
- order = object
- end
-
- return unless eligible?(order)
- total = order.line_items.inject(0) do |total, line_item|
- relations = Relation.find(:all, :conditions => ["discount_amount <> 0.0 AND relatable_type = ? AND relatable_id = ?", "Product", line_item.variant.product.id])
- discount_applies_to = relations.map {|rel| rel.related_to.master }
-
- order.line_items.each do |li|
- if discount_applies_to.include? li.variant
- discount = relations.detect {|rel| rel.related_to.variant == li.variant}.discount_amount
-
- total += if li.quantity < line_item.quantity
- (discount * li.quantity)
- else
- (discount * line_item.quantity)
- end
- end
- end
-
- total
- end
-
- total == 0 ? nil : total
- end
-
- def eligible?(order)
- order.line_items.any? { |line_item| Relation.exists?(["discount_amount <> 0.0 AND relatable_type = ? AND relatable_id = ?", "Product", line_item.variant.product.id])}
- end
-
- end
- end
+ class RelatedProductDiscount < Spree::Calculator
+ preference :item_total_threshold, :decimal, :default => 5
+
+ def self.description
+ I18n.t("related_product_discount")
+ end
+
+ def self.register
+ super
+ Spree::Coupon.register_calculator(self)
+ end
+
+ def compute(object)
+ if object.is_a?(Array)
+ return if object.empty?
+ order = object.first.order
+ else
+ order = object
+ end
+
+ return unless eligible?(order)
+ total = order.line_items.inject(0) do |total, line_item|
+ relations = Spree::Relation.find(:all, :conditions => ["discount_amount <> 0.0 AND relatable_type = ? AND relatable_id = ?", "Product", line_item.variant.product.id])
+ discount_applies_to = relations.map {|rel| rel.related_to.master }
+
+ order.line_items.each do |li|
+ if discount_applies_to.include? li.variant
+ discount = relations.detect {|rel| rel.related_to.variant == li.variant}.discount_amount
+
+ total += if li.quantity < line_item.quantity
+ (discount * li.quantity)
+ else
+ (discount * line_item.quantity)
+ end
+ end
+ end
+
+ total
+ end
+
+ total == 0 ? nil : total
+ end
+
+ def eligible?(order)
+ order.line_items.any? { |line_item| Spree::Relation.exists?(["discount_amount <> 0.0 AND relatable_type = ? AND relatable_id = ?", "Product", line_item.variant.product.id])}
+ end
+
+ end
end
59 app/models/spree/product_decorator.rb
View
@@ -1,23 +1,72 @@
Spree::Product.class_eval do
has_many :relations, :as => :relatable
+ # Returns all the Spree::RelationType's which apply_to this class.
def self.relation_types
Spree::RelationType.find_all_by_applies_to(self.to_s, :order => :name)
end
- def method_missing(method, *args)
- relation_type = self.class.relation_types.detect { |rt| rt.name.downcase.gsub(" ", "_").pluralize == method.to_s.downcase }
+ # The AREL Relations that will be used to filter the resultant items.
+ #
+ # By default this will remove any items which are deleted, or not yet available.
+ #
+ # You can override this method to fine tune the filter. For example,
+ # to only return Spree::Product's with more than 2 items in stock, you could
+ # do the following:
+ #
+ # def self.relation_filter
+ # set = super
+ # set.where('spree_products.count_on_hand >= 2')
+ # end
+ #
+ # This could also feasibly be overridden to sort the result in a
+ # particular order, or restrict the number of items returned.
+ def self.relation_filter
+ where('spree_products.deleted_at' => nil).where('spree_products.available_on IS NOT NULL').where('spree_products.available_on <= ?', Time.now)
+ end
+ # Decides if there is a relevant Spree::RelationType related to this class
+ # which should be returned for this method.
+ #
+ # If so, it calls relations_for_relation_type. Otherwise it passes
+ # it up the inheritance chain.
+ def method_missing(method, *args)
# Fix for Ruby 1.9
raise NoMethodError if method == :to_ary
+ relation_type = self.class.relation_types.detect { |rt| rt.name.downcase.gsub(" ", "_").pluralize == method.to_s.downcase }
+
if relation_type.nil?
super
else
- relations.find_all_by_relation_type_id(relation_type.id).map(&:related_to).select do |product|
- product.deleted_at.nil? && product.available_on && product.available_on <= Time.now()
- end
+ relations_for_relation_type(relation_type)
end
+ end
+ private
+
+ # Returns all the Products that are related to this record for the given RelationType.
+ #
+ # Uses the Relations to find all the related items, and then filters
+ # them using +Product.relation_filter+ to remove unwanted items.
+ def relations_for_relation_type(relation_type)
+ # Find all the relations that belong to us for this RelationType
+ related_ids = relations.where(:relation_type_id => relation_type.id).select(:related_to_id).collect(&:related_to_id)
+
+ # Construct a query for all these records
+ result = self.class.where(:id => related_ids)
+
+ # Merge in the relation_filter if it's available
+ result = result.merge(self.class.relation_filter.scoped) if relation_filter
+
+ result
end
+
+ # Simple accessor for the class-level relation_filter.
+ # Could feasibly be overloaded to filter results relative to this
+ # record (eg. only higher priced items)
+ def relation_filter
+ self.class.relation_filter
+ end
+
end
2  lib/spree_related_products.rb
View
@@ -10,7 +10,6 @@ def self.activate
Rails.configuration.cache_classes ? require(c) : load(c)
end
- # Calculator::RelatedProductDiscount.register
end
config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/spree/calculator)
@@ -18,4 +17,3 @@ def self.activate
end
end
-
123 spec/models/spree/product_spec.rb
View
@@ -0,0 +1,123 @@
+require 'spec_helper'
+
+describe Spree::Product do
+
+ context "class" do
+ describe ".relation_types" do
+ it "should return all the RelationTypes in use for this Product" do
+ relation_type = Spree::RelationType.create!(:name => "Related Products", :applies_to => "Spree::Product")
+ Spree::Product.relation_types.should include(relation_type)
+ end
+ end
+ end
+
+ context "instance" do
+ before(:each) do
+ @product = Factory(:product)
+ @relation_type = Spree::RelationType.create(:name => "Related Products", :applies_to => "Spree::Product")
+ end
+
+ describe ".relations" do
+ it "has many relations" do
+ @product.save!
+ other1 = Factory(:product)
+ other2 = Factory(:product)
+
+ relation1 = Spree::Relation.create!(:relatable => @product, :related_to => other1, :relation_type => @relation_type)
+ relation2 = Spree::Relation.create!(:relatable => @product, :related_to => other2, :relation_type => @relation_type)
+
+ @product.reload
+ @product.relations.should include(relation1)
+ @product.relations.should include(relation2)
+ end
+
+ it "has many relations for different RelationTypes" do
+ @product.save!
+ other = Factory(:product)#valid_product!
+
+ other_relation_type = Spree::RelationType.new(:name => "Recommended Products")
+
+ relation1 = Spree::Relation.create!(:relatable => @product, :related_to => other, :relation_type => @relation_type)
+ relation2 = Spree::Relation.create!(:relatable => @product, :related_to => other, :relation_type => other_relation_type)
+
+ @product.reload
+ @product.relations.should include(relation1)
+ @product.relations.should include(relation2)
+ end
+ end
+
+ describe "RelationType finders" do
+ before(:each) do
+ @product.save!
+ @other = Factory(:product)
+ @relation = Spree::Relation.create!(:relatable => @product, :related_to => @other, :relation_type => @relation_type)
+ @product.reload
+ end
+
+ it "should return the relevant relations" do
+ @product.related_products.should include(@other)
+ end
+
+ it "should be the pluralised form of the RelationType name" do
+ @relation_type.update_attributes(:name => 'Related Product')
+ @product.related_products.should include(@other)
+ end
+
+ it "should not return relations for another RelationType" do
+ @product.save!
+ other2 = Factory(:product)
+
+ other_relation_type = Spree::RelationType.new(:name => "Recommended Products")
+
+ relation1 = Spree::Relation.create!(:relatable => @product, :related_to => @other, :relation_type => @relation_type)
+ relation2 = Spree::Relation.create!(:relatable => @product, :related_to => other2, :relation_type => other_relation_type)
+
+ @product.reload
+ @product.related_products.should include(@other)
+ @product.related_products.should_not include(other2)
+ end
+
+ it "should not return Products that are deleted" do
+ @other.update_attributes(:deleted_at => Time.now)
+
+ @product.related_products.should be_blank
+ end
+
+ it "should not return Products that are not yet available" do
+ @other.update_attributes(:available_on => Time.now + 1.hour)
+
+ @product.related_products.should be_blank
+ end
+
+ it "should not return Products where available_on are blank" do
+ @other.update_attributes(:available_on => nil)
+
+ @product.related_products.should be_blank
+ end
+
+ it "should return all results if .relation_filter is nil" do
+ Spree::Product.should_receive(:relation_filter).and_return(nil)
+ @other.update_attributes(:available_on => Time.now + 1.hour)
+
+ @product.related_products.should include(@other)
+ end
+
+ context "with an enhanced Product.relation_filter" do
+ it "should restrict the filter" do
+ relation_filter = Spree::Product.relation_filter
+ Spree::Product.should_receive(:relation_filter).at_least(:once).and_return(relation_filter.includes(:master).where('spree_variants.count_on_hand > 2'))
+
+ @other.master.update_attributes(:count_on_hand => 1)
+
+ other2 = Factory(:product)
+ other2.master.update_attributes(:count_on_hand => 3)
+ relation = Spree::Relation.create!(:relatable => @product, :related_to => other2, :relation_type => @relation_type)
+
+ results = @product.related_products
+ results.should_not include(@other)
+ results.should include(other2)
+ end
+ end
+ end
+ end
+end
18 spec/spec_helper.rb
View
@@ -0,0 +1,18 @@
+# Configure Rails Environment
+ENV["RAILS_ENV"] = "test"
+require File.expand_path("../dummy/config/environment.rb", __FILE__)
+require 'rspec/rails'
+
+# Requires supporting ruby files with custom matchers and macros, etc,
+# in spec/support/ and its subdirectories.
+Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
+
+# Requires factories defined in spree_core
+require 'spree/core/testing_support/factories'
+
+RSpec.configure do |config|
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
+ # examples within a transaction, remove the following line or assign false
+ # instead of true.
+ config.use_transactional_fixtures = true
+end
3  spree.txt
View
@@ -1,3 +0,0 @@
-0.50.x :branch => '0-50-stable'
-# 0.40.x :ref => '251f6c20fee3d2f07d93'
-# 0.30.x :tag => 'v0.30.x'
5 spree_related_products.gemspec
View
@@ -17,5 +17,8 @@ Gem::Specification.new do |s|
s.has_rdoc = true
- s.add_dependency('spree_core', '>= 0.80.0.beta')
+ s.add_dependency 'spree_core', '>= 0.80.0.beta'
+
+ s.add_development_dependency 'rspec-rails', ' ~> 2.8.0.rc1'
+ s.add_development_dependency 'sqlite3'
end
12 test/functional/admin/relations_controller_test.rb
View
@@ -1,12 +0,0 @@
-require File.dirname(__FILE__) + '/../../test_helper'
-
-# Re-raise errors caught by the controller.
-Spree::Admin::RelationsController.class_eval { def rescue_action(e) raise e end }
-
-class Spree::Admin::RelationsControllerTest < ActionController::TestCase
-
- # Replace this with your real tests.
- def test_truth
- assert true
- end
-end
0  test/unit/helpers/admin/relations_helper_test.rb
View
No changes.
Please sign in to comment.
Something went wrong with that request. Please try again.