piggybak_variants 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +16 -0
- data/Rakefile +40 -0
- data/app/assets/javascripts/piggybak_variants/piggybak_variants.js +32 -0
- data/app/assets/stylesheets/piggybak_variants/application.css +13 -0
- data/app/controllers/piggybak_variants/application_controller.rb +4 -0
- data/app/helpers/piggybak_variants_helper.rb +17 -0
- data/app/models/piggybak_variants/option.rb +17 -0
- data/app/models/piggybak_variants/option_configuration.rb +18 -0
- data/app/models/piggybak_variants/option_value.rb +15 -0
- data/app/models/piggybak_variants/variant.rb +36 -0
- data/app/views/piggybak_variants/cart/_form.html.erb +29 -0
- data/app/views/rails_admin/main/_option_values.html.haml +12 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20121015144538_create_new_variants.rb +13 -0
- data/db/migrate/20121016150359_create_options.rb +13 -0
- data/db/migrate/20121016153016_create_option_values.rb +14 -0
- data/db/migrate/20121016161156_create_option_values_variants.rb +12 -0
- data/db/migrate/20121022205428_create_option_configurations.rb +13 -0
- data/lib/acts_as_sellable_with_variants.rb +18 -0
- data/lib/piggybak_variants.rb +5 -0
- data/lib/piggybak_variants/engine.rb +81 -0
- data/lib/piggybak_variants/version.rb +3 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +56 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/piggybak_variants_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- metadata +152 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'PiggybakVariants'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
Bundler::GemHelper.install_tasks
|
29
|
+
|
30
|
+
require 'rake/testtask'
|
31
|
+
|
32
|
+
Rake::TestTask.new(:test) do |t|
|
33
|
+
t.libs << 'lib'
|
34
|
+
t.libs << 'test'
|
35
|
+
t.pattern = 'test/**/*_test.rb'
|
36
|
+
t.verbose = false
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
task :default => :test
|
@@ -0,0 +1,32 @@
|
|
1
|
+
$(function() {
|
2
|
+
$('.variant_options input[type=radio]').click(function() { piggybak_variants.toggle_variant_form(); });
|
3
|
+
piggybak_variants.toggle_variant_form();
|
4
|
+
});
|
5
|
+
|
6
|
+
var piggybak_variants = {
|
7
|
+
toggle_variant_form: function() {
|
8
|
+
$('.unavailable, .all_selected').hide();
|
9
|
+
var all_selected = true;
|
10
|
+
var selected = new Array();
|
11
|
+
$.each($('.option'), function(i, j) {
|
12
|
+
var option_id = $(j).attr('id');
|
13
|
+
if($('input[name=' + option_id + ']:checked').length) {
|
14
|
+
selected.push($('input[name=' + option_id + ']:checked').val());
|
15
|
+
} else {
|
16
|
+
all_selected = false;
|
17
|
+
}
|
18
|
+
});
|
19
|
+
var selected_key = selected.join('_');
|
20
|
+
if(all_selected && variant_map[selected_key]) {
|
21
|
+
$('.variant_options form').show();
|
22
|
+
$('#sellable_id').val(variant_map[selected_key].id);
|
23
|
+
$('#variant_price span').html(variant_map[selected_key].price);
|
24
|
+
} else if(all_selected) { // if all selected and not available
|
25
|
+
$('.variant_options form').hide();
|
26
|
+
$('.unavailable').show();
|
27
|
+
} else { // if not all selected
|
28
|
+
$('.all_selected').show();
|
29
|
+
$('.variant_options form').hide();
|
30
|
+
}
|
31
|
+
}
|
32
|
+
};
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PiggybakVariantsHelper
|
2
|
+
def variant_cart_form(object)
|
3
|
+
render "piggybak_variants/cart/form", :object => object
|
4
|
+
end
|
5
|
+
|
6
|
+
def options_for_klass(klass)
|
7
|
+
options = ::PiggybakVariants::OptionConfiguration.find_all_by_klass(klass).collect { |oc| oc.option }
|
8
|
+
end
|
9
|
+
|
10
|
+
def variant_map(object)
|
11
|
+
map = {}
|
12
|
+
object.variants.available.each do |variant|
|
13
|
+
map[variant.option_values.collect { |ov| ov.id }.join('_')] = { :id => variant.piggybak_sellable.id, :price => number_to_currency(variant.piggybak_sellable.price) }
|
14
|
+
end
|
15
|
+
map.to_json
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PiggybakVariants
|
2
|
+
class Option < ActiveRecord::Base
|
3
|
+
self.table_name = "options"
|
4
|
+
|
5
|
+
attr_accessible :name, :position
|
6
|
+
has_many :option_values
|
7
|
+
has_many :option_configurations
|
8
|
+
|
9
|
+
default_scope :order => "position ASC"
|
10
|
+
|
11
|
+
accepts_nested_attributes_for :option_values, :allow_destroy => true
|
12
|
+
attr_accessible :option_values_attributes, :allow_destroy => true
|
13
|
+
|
14
|
+
accepts_nested_attributes_for :option_configurations, :allow_destroy => true
|
15
|
+
attr_accessible :option_configurations_attributes, :allow_destroy => true
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module PiggybakVariants
|
2
|
+
class OptionConfiguration < ActiveRecord::Base
|
3
|
+
self.table_name = "option_configurations"
|
4
|
+
|
5
|
+
VARIANT_CLASSES = []
|
6
|
+
|
7
|
+
attr_accessible :klass, :option_id
|
8
|
+
belongs_to :option
|
9
|
+
|
10
|
+
def klass_enum
|
11
|
+
VARIANT_CLASSES
|
12
|
+
end
|
13
|
+
|
14
|
+
def admin_label
|
15
|
+
self.klass
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module PiggybakVariants
|
2
|
+
class OptionValue < ActiveRecord::Base
|
3
|
+
self.table_name = "option_values"
|
4
|
+
|
5
|
+
attr_accessible :name, :position
|
6
|
+
has_and_belongs_to_many :variants
|
7
|
+
belongs_to :option, :inverse_of => :option_values
|
8
|
+
|
9
|
+
default_scope :order => "position ASC"
|
10
|
+
|
11
|
+
def admin_label
|
12
|
+
"#{self.option.name}: #{self.name}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module PiggybakVariants
|
2
|
+
class Variant < ActiveRecord::Base
|
3
|
+
self.table_name = "variants"
|
4
|
+
|
5
|
+
acts_as_sellable
|
6
|
+
belongs_to :item, :polymorphic => true
|
7
|
+
has_and_belongs_to_many :option_values
|
8
|
+
|
9
|
+
scope :available, joins(:piggybak_sellable).where("sellables.active IS TRUE AND (sellables.quantity > 0 OR sellables.unlimited_inventory IS TRUE)")
|
10
|
+
validate :option_value_validation
|
11
|
+
|
12
|
+
def admin_label
|
13
|
+
"#{self.piggybak_sellable.sku}: #{self.piggybak_sellable.price}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def option_value_validation
|
17
|
+
# TODO: Figure out why this validation is only being called sometime
|
18
|
+
# TODO: Add verification to prevent duplicate option value sets
|
19
|
+
|
20
|
+
klass = self.item.class
|
21
|
+
options = ::PiggybakVariants::OptionConfiguration.find_all_by_klass(klass).collect { |oc| oc.option }
|
22
|
+
error = false
|
23
|
+
options.each do |option|
|
24
|
+
matching_option_values = self.option_values.select { |ov| ov.option == option }.size
|
25
|
+
if matching_option_values > 1
|
26
|
+
self.errors.add(:option_values, "You must have only option value for #{option.name}")
|
27
|
+
error = true
|
28
|
+
elsif matching_option_values < 1
|
29
|
+
self.errors.add(:option_values, "You must have one option value for #{option.name}")
|
30
|
+
error = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
self.errors.add(:base, "Option value errors") if error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<% if object.reflections.keys.include?(:variants) -%>
|
2
|
+
<%= javascript_include_tag "piggybak_variants/piggybak_variants" %>
|
3
|
+
|
4
|
+
<script type="text/javascript">
|
5
|
+
var variant_map = <%= raw variant_map(object) %>;
|
6
|
+
</script>
|
7
|
+
|
8
|
+
<div class="variant_options">
|
9
|
+
<% options_for_klass(object.class).each do |option| -%>
|
10
|
+
<div class="option" id="option_<%= option.id %>">
|
11
|
+
<h4><%= option.name %></h4>
|
12
|
+
<% option.option_values.each do |option_value| -%>
|
13
|
+
<input type="radio" name="option_<%= option.id %>" value="<%= option_value.id %>" />
|
14
|
+
<%= option_value.name %><br />
|
15
|
+
<% end -%>
|
16
|
+
<br />
|
17
|
+
</div>
|
18
|
+
<% end -%>
|
19
|
+
<span class="all_selected">You must select an choice from each option.</span>
|
20
|
+
<span class="unavailable">This combination is not available.</span>
|
21
|
+
<%= form_tag piggybak.cart_add_url do -%>
|
22
|
+
<input type="hidden" id="sellable_id" name="sellable_id" />
|
23
|
+
<div id="variant_price">Price: <span></span></div>
|
24
|
+
<label for="quantity">Quantity</label>
|
25
|
+
<%= text_field_tag :quantity, 1 %>
|
26
|
+
<%= submit_tag "Add to Cart", :id => "submit" %>
|
27
|
+
<% end -%>
|
28
|
+
</div>
|
29
|
+
<% end -%>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
:ruby
|
2
|
+
column_type_dom_id = form.dom_id(field).sub(field.method_name.to_s, "")
|
3
|
+
attr = column_type_dom_id.gsub("frame_variants_attributes", '').gsub(/^_/, '[').gsub(/_$/, ']')
|
4
|
+
|
5
|
+
= #TODO: Add JavaScript to limit selectors here
|
6
|
+
|
7
|
+
- options_for_klass(@object.class).each do |option|
|
8
|
+
%h5= option.name
|
9
|
+
- option.option_values.each do |option_value|
|
10
|
+
%p
|
11
|
+
= check_box_tag "frame[variants_attributes]#{attr}[option_value_ids][]", option_value.id, form.object.option_values.include?(option_value), :style => "margin:-2px 5px 0px 0px;"
|
12
|
+
%span= option_value.name
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateNewVariants < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
create_table :variants, :force => true do |t|
|
4
|
+
t.integer :item_id, :null => false
|
5
|
+
t.string :item_type, :null => false
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def down
|
11
|
+
drop_table :variants
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateOptionValues < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
create_table :option_values, :force => true do |t|
|
4
|
+
t.integer :option_id
|
5
|
+
t.string :name
|
6
|
+
t.integer :position
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def down
|
12
|
+
drop_table :options_values
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateOptionValuesVariants < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
create_table :option_values_variants, :id => false do |t|
|
4
|
+
t.integer :option_value_id
|
5
|
+
t.integer :variant_id
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def down
|
10
|
+
drop_table :option_values_variants
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateOptionConfigurations < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
create_table :option_configurations, :force => true do |t|
|
4
|
+
t.string :klass, :null => false
|
5
|
+
t.references :option, :null => false
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def down
|
11
|
+
drop_table :option_assignments
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Piggybak
|
2
|
+
module ActsAsSellableWithVariants
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def acts_as_sellable_with_variants
|
7
|
+
has_many :variants, :dependent => :destroy, :class_name => "::PiggybakVariants::Variant", :as => :item
|
8
|
+
|
9
|
+
accepts_nested_attributes_for :variants, :allow_destroy => true
|
10
|
+
attr_accessible :variants_attributes #, :allow_destroy => true
|
11
|
+
|
12
|
+
::PiggybakVariants::OptionConfiguration::VARIANT_CLASSES << self.to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
::ActiveRecord::Base.send :include, Piggybak::ActsAsSellableWithVariants
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module PiggybakVariants
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace PiggybakVariants
|
4
|
+
|
5
|
+
config.to_prepare do
|
6
|
+
ApplicationController.class_eval do
|
7
|
+
helper :piggybak_variants
|
8
|
+
end
|
9
|
+
RailsAdmin::ApplicationController.class_eval do
|
10
|
+
helper :piggybak_variants
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
initializer "piggybak_variants.precompile_hook" do |app|
|
15
|
+
app.config.assets.precompile += ['piggybak_variants/piggybak_variants.js']
|
16
|
+
end
|
17
|
+
|
18
|
+
initializer "piggybak.rails_admin_config" do |app|
|
19
|
+
RailsAdmin.config do |config|
|
20
|
+
config.model PiggybakVariants::OptionConfiguration do
|
21
|
+
visible false
|
22
|
+
object_label_method :admin_label
|
23
|
+
label "Classes with Option"
|
24
|
+
edit do
|
25
|
+
field :klass
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
config.model PiggybakVariants::OptionValue do
|
30
|
+
visible false
|
31
|
+
object_label_method :admin_label
|
32
|
+
edit do
|
33
|
+
field :name
|
34
|
+
field :position
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
config.model PiggybakVariants::Option do
|
39
|
+
navigation_label "Variants"
|
40
|
+
list do
|
41
|
+
field :name
|
42
|
+
field :position
|
43
|
+
field :option_values
|
44
|
+
field :option_configurations do
|
45
|
+
label "Classes with Option"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
edit do
|
49
|
+
field :name
|
50
|
+
field :position
|
51
|
+
field :option_configurations do
|
52
|
+
label "Classes with Option"
|
53
|
+
active true
|
54
|
+
end
|
55
|
+
field :option_values do
|
56
|
+
active true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
config.model PiggybakVariants::Variant do
|
62
|
+
object_label_method :admin_label
|
63
|
+
show do
|
64
|
+
field :option_values do
|
65
|
+
visible true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
edit do
|
69
|
+
field :option_values do
|
70
|
+
partial "option_values"
|
71
|
+
help "An option value for each option must be selected"
|
72
|
+
end
|
73
|
+
field :piggybak_sellable do
|
74
|
+
active true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|