Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit 535e753a9aa2eb7fa040d7f02c5dabe4d3156cac Jacqui Maher committed Sep 22, 2009
@@ -0,0 +1,13 @@
+Spree Product Features
+=========================
+
+Spree Product Features is an extension for the Spree E-Commerce Platform (http://spreecommerce.com/) that allows you to associate products with features. In this extension, features are just objects with names and images.
+
+Usage
+=====
+
+>> @feature = Feature.create(:name => '10 year warranty', :image => File.open('images/10_year_warranty.png', 'rb'))
+
+>> @product.features << @feature
+>> @product.features.delete(@feature)
+
@@ -0,0 +1,120 @@
+# I think this is the one that should be moved to the extension Rakefile template
+
+# In rails 1.2, plugins aren't available in the path until they're loaded.
+# Check to see if the rspec plugin is installed first and require
+# it if it is. If not, use the gem version.
+
+# Determine where the RSpec plugin is by loading the boot
+unless defined? SPREE_ROOT
+ ENV["RAILS_ENV"] = "test"
+ case
+ when ENV["SPREE_ENV_FILE"]
+ require File.dirname(ENV["SPREE_ENV_FILE"]) + "/boot"
+ when File.dirname(__FILE__) =~ %r{vendor/SPREE/vendor/extensions}
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
+ else
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
+ end
+end
+
+require 'rake'
+require 'rake/rdoctask'
+require 'rake/testtask'
+
+rspec_base = File.expand_path(SPREE_ROOT + '/vendor/plugins/rspec/lib')
+$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
+require 'spec/rake/spectask'
+# require 'spec/translator'
+
+# Cleanup the SPREE_ROOT constant so specs will load the environment
+Object.send(:remove_const, :SPREE_ROOT)
+
+extension_root = File.expand_path(File.dirname(__FILE__))
+
+task :default => :spec
+task :stats => "spec:statsetup"
+
+desc "Run all specs in spec directory"
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
+ t.spec_files = FileList["#{extension_root}/spec/**/*_spec.rb"]
+end
+
+namespace :spec do
+ desc "Run all specs in spec directory with RCov"
+ Spec::Rake::SpecTask.new(:rcov) do |t|
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.rcov = true
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
+ end
+
+ desc "Print Specdoc for all specs"
+ Spec::Rake::SpecTask.new(:doc) do |t|
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ end
+
+ [:models, :controllers, :views, :helpers].each do |sub|
+ desc "Run the specs under spec/#{sub}"
+ Spec::Rake::SpecTask.new(sub) do |t|
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
+ end
+ end
+
+ # Hopefully no one has written their extensions in pre-0.9 style
+ # desc "Translate specs from pre-0.9 to 0.9 style"
+ # task :translate do
+ # translator = ::Spec::Translator.new
+ # dir = RAILS_ROOT + '/spec'
+ # translator.translate(dir, dir)
+ # end
+
+ # Setup specs for stats
+ task :statsetup do
+ require 'code_statistics'
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
+ ::CodeStatistics::TEST_TYPES << "Model specs"
+ ::CodeStatistics::TEST_TYPES << "View specs"
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
+ end
+
+ namespace :db do
+ namespace :fixtures do
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
+ task :load => :environment do
+ require 'active_record/fixtures'
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
+ end
+ end
+ end
+ end
+end
+
+desc 'Generate documentation for the product_features extension.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'ProductFeaturesExtension'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+# For extensions that are in transition
+desc 'Test the product_features extension.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+# Load any custom rakefiles for extension
+Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
@@ -0,0 +1,26 @@
+class Admin::FeaturesController < Admin::BaseController
+ resource_controller
+ before_filter :product_features
+
+ create.response do |format|
+ format.html { redirect_to admin_features_url }
+ end
+ update.response do |format|
+ format.html { redirect_to admin_features_url }
+ end
+
+private
+
+ def product_features
+ if params[:product_id]
+ @product = Product.find_by_param(params[:product_id]) || Product.find_by_id(params[:product_id])
+ @product_features ||= @product.features
+ end
+ end
+
+ def load_objects
+ if params[:id]
+ @feature = Feature.find_by_param(params[:id]) || Feature.find_by_id(params[:id])
+ end
+ end
+end
@@ -0,0 +1,5 @@
+class Feature < ActiveRecord::Base
+ validates_presence_of :name
+ has_and_belongs_to_many :products
+ has_attached_file :image
+end
@@ -0,0 +1,12 @@
+<tr>
+ <td>
+ <%= feature.name %>
+ </td>
+ <td>
+ <%= image_tag feature.image.url %>
+ </td>
+ <td>
+ <%= link_to t('edit'), edit_admin_feature_url(feature), :class => 'iconlink' %>
+ <%= link_to t('remove'), admin_feature_url(feature), :method => :delete, :class => 'iconlink' %>
+ </td>
+</tr>
@@ -0,0 +1,10 @@
+ <dt><label for='feature[name]'>Name</label></dt>
+ <dd><%= f.text_field :name %></dd>
+ <dt><label for='feature[image]'>Image</label></dt>
+ <dd><%= f.file_field :image %></dd>
+
+ <% if @feature.image.exists? %>
+ <dt><label>Current Image</label></dt>
+ <dd><%= image_tag @feature.image.url %></dd>
+ <% end %>
+
@@ -0,0 +1,10 @@
+<%= error_messages_for :feature, :header => false %>
+
+<% form_for :feature, @feature, :url => admin_feature_url(@feature), :html => {:multipart => true, :method => :put} do |f| -%>
+ <dl>
+ <%= render :partial => 'form', :locals => { :f => f } %>
+ <dt></dt>
+ <dd><%= f.submit 'Update' %></dd>
+ </dl>
+<% end -%>
+
@@ -0,0 +1,16 @@
+<% if @features.blank? %>
+ <p>
+ You have not added any features yet.
+ </p>
+
+<% else %>
+ <table id="features">
+ <tr>
+ <th>Name</th>
+ <th>Image</th>
+ </tr>
+ <%= render :partial => 'feature', :collection => @features %>
+ </table>
+<% end %>
+
+<%= link_to 'new feature', new_admin_feature_url %>
@@ -0,0 +1,10 @@
+<%= error_messages_for :feature, :header => false %>
+
+<% form_for :feature, @feature, :url => admin_features_url, :html => {:multipart => true, :method => :post} do |f| -%>
+ <dl>
+ <%= render :partial => 'form', :locals => { :f => f } %>
+ <dt></dt>
+ <dd><%= f.submit 'Create' %></dd>
+ </dl>
+<% end -%>
+
@@ -0,0 +1,11 @@
+<tr>
+ <td>
+ <%= feature.name %>
+ </td>
+ <td>
+ <%= image_tag feature.image.url %>
+ </td>
+ <td>
+ <%= link_to t('remove'), remove_admin_product_feature_url(@product, feature), :method => :delete, :class => 'iconlink' %>
+ </td>
+</tr>
@@ -0,0 +1,25 @@
+<% if @features.blank? %>
+ <p>
+ You have not added any features to this product yet.
+ </p>
+
+<% else %>
+ <table id="features">
+ <tr>
+ <th>Name</th>
+ <th>Image</th>
+ </tr>
+ <%= render :partial => 'feature', :collection => @features %>
+ </table>
+<% end %>
+
+<p>
+ Select features from the list below:
+</p>
+
+<% form_for :product, :url => add_admin_product_features_url(:product_id => @product.id), :method => :post do |f| %>
+ <%= f.collection_select(:feature_ids, Product.all.sort_by(&:name), :id, :name, {}, :multiple => true, :size => 5) %>
+ <%= f.submit 'Add' %>
+<% end %>
+
+<%= link_to 'new feature', new_admin_feature_url %>
@@ -0,0 +1,2 @@
+en-US:
+ features: Features
@@ -0,0 +1,8 @@
+# Put your extension routes here.
+
+map.namespace :admin do |admin|
+ admin.resources :features
+ admin.resources :products, :member => {:features => :get} do |product|
+ product.resources :features, :member => {:remove => :delete}, :collection => {:add => :post}
+ end
+end
@@ -0,0 +1,24 @@
+class CreateFeatures < ActiveRecord::Migration
+ def self.up
+ create_table :features do |t|
+ t.string :name
+ t.string :image_file_name
+ t.string :image_content_type
+ t.integer :image_file_size
+ t.datetime :image_updated_at
+
+ t.timestamps
+ end
+
+ create_table :features_products, :id => false do |t|
+ t.integer :feature_id
+ t.integer :product_id
+ end
+ end
+
+ def self.down
+ drop_table :features_products
+
+ drop_table :features
+ end
+end
@@ -0,0 +1,28 @@
+namespace :db do
+ desc "Bootstrap your database for Spree."
+ task :bootstrap => :environment do
+ # load initial database fixtures (in db/sample/*.yml) into the current environment's database
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
+ Dir.glob(File.join(ProductFeaturesExtension.root, "db", 'sample', '*.{yml,csv}')).each do |fixture_file|
+ Fixtures.create_fixtures("#{ProductFeaturesExtension.root}/db/sample", File.basename(fixture_file, '.*'))
+ end
+ end
+end
+
+namespace :spree do
+ namespace :extensions do
+ namespace :product_features do
+ desc "Copies public assets of the Product Features to the instance public/ directory."
+ task :update => :environment do
+ is_svn_git_or_dir = proc {|path| path =~ /\.svn/ || path =~ /\.git/ || File.directory?(path) }
+ Dir[ProductFeaturesExtension.root + "/public/**/*"].reject(&is_svn_git_or_dir).each do |file|
+ path = file.sub(ProductFeaturesExtension.root, '')
+ directory = File.dirname(path)
+ puts "Copying #{path}..."
+ mkdir_p RAILS_ROOT + directory
+ cp file, RAILS_ROOT + path
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,50 @@
+# Uncomment this if you reference any of your controllers in activate
+require_dependency 'application'
+
+class ProductFeaturesExtension < Spree::Extension
+ version "1.0"
+ description "Gives you the ability to add features to products using a habtm association. Features are simply a name with an associated image."
+ url "http://github.com/eastmedia/spree-product-features"
+
+ def self.require_gems(config)
+ config.gem "thoughtbot-factory_girl", :lib => "factory_girl", :source => "http://gems.github.com"
+ config.gem "faker"
+ end
+
+ def activate
+ Product.class_eval do
+ has_and_belongs_to_many :features
+ end
+
+ Admin::ProductsController.class_eval do
+ include ActionView::Helpers::TextHelper
+
+ def add_feature
+ @product = Product.find_by_param(params[:product_id]) || Product.find_by_id(params[:product_id])
+ @product.features << Feature.find_all_by_id(params[:product][:feature_ids])
+ @features = @product.features
+ flash[:notice] = "Successfully added #{@features.map(&:name).sort.join(',')} as #{pluralize(@features.size, 'feature')}!"
+ redirect_to admin_product_features_url(@product)
+ end
+
+ def remove_feature
+ @product = Product.find_by_param(params[:product_id]) || Product.find_by_id(params[:product_id])
+ @feature = Feature.find_by_id params[:feature_id]
+ @product.features.delete(@feature)
+ flash[:notice] = "Successfully removed #{@feature.name} from #{@product.name}'s features!"
+ @features = @product.features
+ redirect_to admin_product_features_url(@product)
+ end
+ end
+
+ end
+
+ Admin::BaseController.class_eval do
+ before_filter :add_features_tab
+
+ private
+ def add_features_tab
+ @product_admin_tabs << {:name => t('features'), :url => "admin_product_features_url"}
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 535e753

Please sign in to comment.