glysellin 0.4.1 → 0.4.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +8 -8
  2. data/app/helpers/glysellin/cart_helper.rb +4 -4
  3. data/app/helpers/glysellin/products_helper.rb +19 -0
  4. data/app/models/glysellin/{order_item.rb → line_item.rb} +17 -7
  5. data/app/models/glysellin/order.rb +2 -2
  6. data/app/models/glysellin/product.rb +12 -114
  7. data/app/models/glysellin/product_property.rb +10 -3
  8. data/app/models/glysellin/product_property_type.rb +6 -4
  9. data/app/models/glysellin/variant.rb +25 -23
  10. data/app/views/glysellin/cart/_added_to_cart_warning.html.erb +11 -4
  11. data/app/views/glysellin/cart/_products.html.erb +1 -0
  12. data/app/views/glysellin/products/_add_to_cart.html.haml +13 -0
  13. data/config/locales/glysellin.en.yml +5 -4
  14. data/config/locales/glysellin.fr.yml +3 -0
  15. data/db/migrate/20130609013900_add_sellable_references_to_glysellin_products.rb +6 -0
  16. data/db/migrate/20130609023300_remove_unused_fields_from_glysellin_products.rb +18 -0
  17. data/db/migrate/20130609025800_remove_taxonomies_from_system.rb +21 -0
  18. data/db/migrate/20130609040600_rename_order_items_to_line_items.rb +6 -0
  19. data/db/migrate/20130609142100_add_variant_id_to_glysellin_line_items.rb +5 -0
  20. data/db/migrate/20130609155600_make_variants_belong_to_sellables_and_not_products.rb +16 -0
  21. data/db/migrate/20130609193600_drop_product_types.rb +22 -0
  22. data/db/migrate/20130609200000_remove_unused_product_properties_columns.rb +15 -0
  23. data/db/migrate/20130609200700_remove_position_from_glysellin_variants.rb +9 -0
  24. data/lib/glysellin.rb +0 -1
  25. data/lib/glysellin/engine.rb +7 -0
  26. data/lib/glysellin/engine/routes.rb +0 -11
  27. data/lib/glysellin/property_finder.rb +2 -2
  28. data/lib/glysellin/sellable.rb +69 -0
  29. data/lib/glysellin/sellable/multi.rb +24 -0
  30. data/lib/glysellin/sellable/simple.rb +30 -0
  31. data/lib/glysellin/version.rb +1 -1
  32. data/lib/tasks/glysellin_tasks.rake +1 -1
  33. metadata +16 -13
  34. data/app/controllers/glysellin/products_controller.rb +0 -12
  35. data/app/models/glysellin/product_type.rb +0 -11
  36. data/app/models/glysellin/taxonomy.rb +0 -35
  37. data/app/views/glysellin/products/_add_to_cart.html.erb +0 -16
  38. data/app/views/glysellin/products/_item.html.erb +0 -21
  39. data/app/views/glysellin/products/filter.html.erb +0 -0
  40. data/app/views/glysellin/products/index.html.erb +0 -3
  41. data/app/views/glysellin/products/show.html.erb +0 -3
  42. data/lib/glysellin/acts_as_sellable.rb +0 -14
  43. data/lib/glysellin/product_methods.rb +0 -66
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NTc3ZmMwNGU1YTk5ODFhMDAxZTgwMjBiNDk1Njk5OGZlYzJmOGJmMQ==
4
+ OGJiYTNiMzg1M2E3MTAxZjkyMzJkMjQzMWUxY2MwYzg3NDg2NTY1Zg==
5
5
  data.tar.gz: !binary |-
6
- YzY4Y2U4NmE1NDEzODQ2NmEwNjVlNWE3NmYzN2NjMDNlYWZlMmIyYw==
6
+ Zjg4ZTQ2ZGQzMjk1ODNiMmFkZDAwZDg4ZTllZDhiYjE4ZjRhMjk1OA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MmZjMzA5YTRiOWUzN2Y5ZjgxYWZiZmUzOWI3NDQ3ODI0ZDA1YzJmZDBkYzBk
10
- MjYzMGE2NThkYWE5NDc4NTM2NDlkMmJmMzU4NmE4ZGI2ZGQxMmU1MTM5OGQ5
11
- MTU3NTYyMjg5ZGQxMzEwNTEwM2ZjMmE2YjQxMDRmYjcxZDZmZjE=
9
+ MTU5ZjJhNTcxZTNiNTJkZmEwODQwNDczMTZlZWM1M2MwZjI5MTk5OWJkMjMx
10
+ MTE0OTRiZjU0ZTBlNWIwNzY4NWQyYzU4YTNlNzllNmYwMDI1NzkwMjY3NjY0
11
+ YzBjZGUxZDgzY2NjOTc5MTBkN2VhZjVmYjExYTFkNDNjZDZjNmI=
12
12
  data.tar.gz: !binary |-
13
- MTU1ZjBiY2M0ZjlkNDBmYzdiYThhNzViMDY3N2M0YjY4ODI5Zjk2OTE5MGQ3
14
- NzQ4ZjRmNzdmMGNkMDhiNzVhNWJkMzAxY2YyMGVjMjZlYTc3MDNkNmM3MWJm
15
- MmM4NjAwYTA3YmE5NDc5N2ZhN2QxZGIwODAwYTE4MTg0MWY5OGM=
13
+ OGMwZDVjNDk1ZGU0NzAyNDM5YzBmYzlmMzZkOTBmMTlmYTY1NjBlZGZlODRl
14
+ YmZjNTc2Y2ZkODc0MTVhOWY0NDU5NzVkNmIzNmNmZThmZWE3NjJkZjU5YTEy
15
+ NWJlZGRiOWU2YmVkZDRmZGYyNGVjNmE5NzljNDA5YjZiMGJlOGM=
@@ -1,11 +1,11 @@
1
1
  module Glysellin
2
2
  module CartHelper
3
- def add_to_cart_form product, options = {}
3
+ def add_to_cart_form sellable, options = {}
4
4
  # Default to remote form
5
5
  options[:remote] = true unless options[:remote] == false
6
6
  # Render actual form
7
7
  render partial: 'glysellin/products/add_to_cart', locals: {
8
- product: product,
8
+ sellable: sellable,
9
9
  options: options
10
10
  }
11
11
  end
@@ -14,8 +14,8 @@ module Glysellin
14
14
  render partial: 'glysellin/cart/added_to_cart_warning'
15
15
  end
16
16
 
17
- def render_cart cart
18
- render partial: "glysellin/cart/cart", locals: { cart: cart }
17
+ def render_cart
18
+ render partial: "glysellin/cart/cart", locals: { cart: current_cart }
19
19
  end
20
20
  end
21
21
  end
@@ -1,4 +1,23 @@
1
1
  module Glysellin
2
2
  module ProductsHelper
3
+ def variants_options_for sellable
4
+ # Prepare ou arguments arrays
5
+ all_variants, disabled_variants = [], []
6
+ # Iterate on each variant to find which are enabled and disabled
7
+ sellable.published_variants.each do |variant|
8
+ next unless variant.published?
9
+ if variant.in_stock?
10
+ name = variant.name
11
+ else
12
+ disabled_variants << variant.id
13
+ name = variant.name +
14
+ " (#{ t('glysellin.labels.sellables.out_of_stock') })"
15
+ end
16
+
17
+ all_variants << [name, variant.id]
18
+ end
19
+
20
+ options_for_select(all_variants, disabled: disabled_variants)
21
+ end
3
22
  end
4
23
  end
@@ -1,10 +1,12 @@
1
1
  module Glysellin
2
- class OrderItem < ActiveRecord::Base
3
- self.table_name = 'glysellin_order_items'
2
+ class LineItem < ActiveRecord::Base
3
+ self.table_name = "glysellin_line_items"
4
4
  belongs_to :order, inverse_of: :products
5
5
 
6
+ belongs_to :variant, class_name: "Glysellin::Variant"
7
+
6
8
  attr_accessible :sku, :name, :eot_price, :vat_rate, :bundle, :price,
7
- :quantity, :weight
9
+ :quantity, :weight, :variant_id
8
10
 
9
11
  # The attributes we getch from a product to build our order item
10
12
  PRODUCT_ATTRIBUTES_FOR_ITEM = %w(sku name eot_price vat_rate price weight)
@@ -15,15 +17,19 @@ module Glysellin
15
17
  # @param [String] id The id string of the item
16
18
  # @param [Boolean] bundle If it's a bundle or just one product
17
19
  #
18
- # @return [OrderItem] The created order item
20
+ # @return [LineItem] The created order item
21
+ #
19
22
  def build_from_product id, quantity
20
- product = Glysellin::Variant.find_by_id(id)
23
+ variant = Glysellin::Variant.find_by_id(id)
21
24
 
22
25
  attrs = PRODUCT_ATTRIBUTES_FOR_ITEM.map do |key|
23
- [key, product.public_send(key)]
26
+ [key, variant.public_send(key)]
24
27
  end
25
28
 
26
- OrderItem.new(Hash[attrs].merge('quantity' => quantity))
29
+ self.new Hash[attrs].merge(
30
+ "quantity" => quantity,
31
+ "variant_id" => variant.id
32
+ )
27
33
  end
28
34
  end
29
35
 
@@ -34,5 +40,9 @@ module Glysellin
34
40
  def total_price
35
41
  quantity * price
36
42
  end
43
+
44
+ def sellable
45
+ variant && variant.product && variant.product.sellable
46
+ end
37
47
  end
38
48
  end
@@ -27,7 +27,7 @@ module Glysellin
27
27
  #
28
28
  # Order products are used to map order to cloned and simplified products
29
29
  # so the Order propererties can't be affected by product updates
30
- has_many :products, class_name: 'Glysellin::OrderItem',
30
+ has_many :products, class_name: 'Glysellin::LineItem',
31
31
  foreign_key: 'order_id', dependent: :destroy
32
32
 
33
33
  # The actual buyer
@@ -158,7 +158,7 @@ module Glysellin
158
158
 
159
159
  def products=(attributes)
160
160
  products = attributes.reduce([]) do |list, product|
161
- item = OrderItem.build_from_product(product[:id], product[:quantity])
161
+ item = LineItem.build_from_product(product[:id], product[:quantity])
162
162
  item ? (list << item) : list
163
163
  end
164
164
 
@@ -3,134 +3,32 @@ require 'friendly_id'
3
3
 
4
4
  module Glysellin
5
5
  class Product < ActiveRecord::Base
6
- include ProductMethods
7
- extend FriendlyId
8
-
9
- friendly_id :name, use: :slugged
10
6
 
11
7
  self.table_name = 'glysellin_products'
12
8
 
13
- # Relations
14
- #
15
- # The ProductImage model is used for products and bundles with the same
16
- has_many :images, as: :imageable, class_name: 'Glysellin::ProductImage',
17
- inverse_of: :imageable, dependent: :destroy
18
-
19
- # Can have multiple taxonomies
20
- has_and_belongs_to_many :taxonomies, :class_name => 'Glysellin::Taxonomy',
21
- join_table: 'glysellin_products_taxonomies', :foreign_key => 'product_id'
22
-
23
- # N..N relation between bundles and products
24
- # has_many :bundle_products, class_name: 'Glysellin::BundleProduct',
25
- # foreign_key: 'product_id'
26
- # has_many :bundles, class_name: 'Glysellin::Product',
27
- # through: :bundle_products
28
-
29
- # # Bundled products in current_product
30
- # has_many :product_bundles, class_name: 'Glysellin::BundleProduct',
31
- # foreign_key: 'bundle_id'
32
- # has_many :bundled_products, class_name: 'Glysellin::Product',
33
- # through: :product_bundles
9
+ belongs_to :sellable, polymorphic: true, inverse_of: :product
34
10
 
35
11
  # Products can belong to a brand
36
- belongs_to :brand, :inverse_of => :products
37
-
38
- belongs_to :product_type
39
-
40
- has_many :variants, class_name: 'Glysellin::Variant',
41
- before_add: :set_product_on_variant, dependent: :destroy
42
-
43
- accepts_nested_attributes_for :images, allow_destroy: true, reject_if: :all_blank
44
- accepts_nested_attributes_for :variants, allow_destroy: true, reject_if: :all_blank
45
-
46
- # accepts_nested_attributes_for :bundled_products, allow_destroy: true, reject_if: :all_blank
47
-
48
- attr_accessible :description, :name, :sku, :slug, :vat_rate,
49
- :brand, :taxonomies, :images, :published,
50
- :display_priority, :images_attributes, :taxonomy_ids, :unlimited_stock,
51
- :position, :brand_id, :variants_attributes, :variants, :product_type_id
52
-
53
- # :bundled_products_attributes
54
-
55
- # Validations
56
- #
57
- validates_presence_of :name, :slug
58
- # Validates price related attributes only unless we have bundled products
59
- # so we can defer validations to them
60
- validates :vat_rate, presence: true,
61
- numericality: true # , unless: proc { |p| p.bundled_products.length > 0 }
62
-
63
- # We check presence of sku if set in global config
64
- validates :sku, presence: true, if: proc { Glysellin.autoset_sku }
12
+ belongs_to :brand, class_name: "Glysellin::Brand", inverse_of: :products
65
13
 
66
- # Callbacks
67
- #
68
- before_validation :set_sku, :set_vat_rate, :ensure_variant
14
+ attr_accessible :vat_rate, :brand, :brand_id
69
15
 
70
- scope :published, where('glysellin_products.published = ?', true)
71
- scope :with_taxonomy, lambda { |*taxonomies|
72
- # Ensure we only have slugs so we got a string vector
73
- taxonomies.map! { |t| t.kind_of?(Glysellin::Taxonomy) ? t.slug : t }
74
- # Get products with those taxonomies
75
- includes(:taxonomies).where('glysellin_taxonomies.slug IN (?)', taxonomies)
76
- }
16
+ validates :vat_rate, presence: true, numericality: true
77
17
 
78
- # Master variant methods delegation
79
- delegate :price, :unmarked_price, :marked_down?, to: :master_variant
80
-
81
-
82
- # And as for the validation, if the SKU is configured to be autoset,
83
- # we check generate it
84
- def set_sku
85
- unless (sku && sku.length > 0) || !Glysellin.autoset_sku
86
- self.sku = generate_sku
87
- end
88
- end
89
-
90
- def set_vat_rate
91
- if !vat_rate
92
- self.vat_rate = Glysellin.default_vat_rate
93
- end
94
- end
95
-
96
- def ensure_variant
97
- variants.build(product: self) unless variants.length > 0
18
+ def name
19
+ sellable && sellable.name
98
20
  end
99
21
 
100
- # Fetches all available variants for the current product
101
- #
102
- def available_variants
103
- variants.available
22
+ def vat_rate
23
+ super.presence || Glysellin.default_vat_rate
104
24
  end
105
25
 
106
- def image
107
- images.length > 0 ? images.first.image : nil
26
+ def vat_ratio
27
+ 1 + vat_rate / 100
108
28
  end
109
29
 
110
- def master_variant
111
- self.variants.first
30
+ def variants
31
+ sellable.published_variants
112
32
  end
113
-
114
- def set_product_on_variant variant
115
- variant.product = self
116
- end
117
-
118
- # bundle_attribute :price do |product|
119
- # product.bundled_products.reduce(0) do |total, product|
120
- # total + product.price
121
- # end
122
- # end
123
-
124
- # bundle_attribute :eot_price do |product, att|
125
- # product.bundled_products.reduce(0) do |total, product|
126
- # total + product.eot_price
127
- # end
128
- # end
129
-
130
- # bundle_attribute :vat_rate do |product|
131
- # product.bundled_products.reduce(0) do |total, product|
132
- # total + (product.vat_rate * product.price)
133
- # end / product.price
134
- # end
135
33
  end
136
34
  end
@@ -1,10 +1,13 @@
1
1
  module Glysellin
2
2
  class ProductProperty < ActiveRecord::Base
3
3
  self.table_name = 'glysellin_product_properties'
4
- attr_accessible :adjustement, :name, :value, :variant, :variant_id, :type, :type_id
4
+ attr_accessible :value, :variant_id, :type_id
5
5
 
6
- belongs_to :variant, polymorphic: true, foreign_key: 'variant_id'
7
- belongs_to :type, class_name: 'Glysellin::ProductPropertyType', foreign_key: 'type_id'
6
+ belongs_to :variant, class_name: "Glysellin::Variant",
7
+ inverse_of: :properties
8
+
9
+ belongs_to :type, class_name: 'Glysellin::ProductPropertyType',
10
+ inverse_of: :properties
8
11
 
9
12
  # validate :check_uniqueness_of_type
10
13
 
@@ -13,6 +16,10 @@ module Glysellin
13
16
 
14
17
  private
15
18
 
19
+ def name
20
+ type && type.name
21
+ end
22
+
16
23
  def check_uniqueness_of_type
17
24
  if self.variant.properties.map(&:type).include? self.type
18
25
  errors.add(:type, I18n.t("glysellin.controllers.errors.double_property"))
@@ -1,11 +1,13 @@
1
1
  module Glysellin
2
2
  class ProductPropertyType < ActiveRecord::Base
3
3
  self.table_name = 'glysellin_product_property_types'
4
- attr_accessible :eot_price, :in_stock, :name, :position, :price, :published, :sku, :slug, :unlimited_stock
4
+ attr_accessible :name
5
5
 
6
- has_many :properties, class_name: "Glysellin::ProductProperty"
6
+ has_many :properties, class_name: "Glysellin::ProductProperty",
7
+ foreign_key: 'type_id', inverse_of: :type
7
8
 
8
- has_and_belongs_to_many :product_types, class_name: 'Glysellin::ProductType',
9
- join_table: 'glysellin_product_types_property_types'
9
+ def name
10
+ "hoo"
11
+ end
10
12
  end
11
13
  end
@@ -2,7 +2,6 @@ require 'friendly_id'
2
2
 
3
3
  module Glysellin
4
4
  class Variant < ActiveRecord::Base
5
- include ProductMethods
6
5
  extend FriendlyId
7
6
 
8
7
  friendly_id :name, use: :slugged
@@ -10,24 +9,28 @@ module Glysellin
10
9
  self.table_name = 'glysellin_variants'
11
10
 
12
11
  attr_accessible :eot_price, :in_stock, :name, :position, :price,
13
- :published, :sku, :slug, :unlimited_stock, :product, :product_id,
14
- :properties_attributes, :properties, :weight, :unmarked_price
12
+ :published, :sku, :slug, :unlimited_stock, :sellable_id,
13
+ :sellable_type, :properties_attributes, :weight,
14
+ :unmarked_price
15
15
 
16
- belongs_to :product, class_name: 'Glysellin::Product',
17
- foreign_key: 'product_id'
16
+ belongs_to :sellable, polymorphic: true
18
17
 
19
18
  has_many :properties, class_name: 'Glysellin::ProductProperty',
20
- as: :variant, extend: Glysellin::PropertyFinder, dependent: :destroy
19
+ extend: Glysellin::PropertyFinder, dependent: :destroy,
20
+ inverse_of: :variant
21
21
 
22
22
  accepts_nested_attributes_for :properties, allow_destroy: true
23
23
 
24
- validates_presence_of :name, if: proc { |v| v.product.variants.length > 1 }
24
+ validates_presence_of :name, if: proc { |variant|
25
+ variant.sellable.variants.length > 1
26
+ }
27
+
25
28
  validates_numericality_of :price
26
29
  validates_numericality_of :in_stock, if: proc { |v| v.in_stock.presence }
27
30
 
28
31
  before_validation :check_prices
29
32
 
30
- after_initialize :prepare_properties
33
+ # after_initialize :prepare_properties
31
34
 
32
35
  AVAILABLE_QUERY = <<-SQL
33
36
  glysellin_variants.published = ? AND (
@@ -37,25 +40,24 @@ module Glysellin
37
40
  SQL
38
41
 
39
42
  scope :available, where(AVAILABLE_QUERY, true, true, 0)
43
+ scope :published, where(published: true)
40
44
 
41
- def prepare_properties
42
- if product && product.product_type
43
- product.product_type.property_types.each do |type|
44
- properties.build(type: type) if properties.send(type.name) == false
45
- end
46
- end
47
- end
45
+ # def prepare_properties
46
+ # if product && product.product_type
47
+ # product.product_type.property_types.each do |type|
48
+ # properties.build(type: type) if properties.send(type.name) == false
49
+ # end
50
+ # end
51
+ # end
48
52
 
49
53
  def description
50
- product.description
54
+ sellable.description if sellable.respond_to?(:description)
51
55
  end
52
56
 
53
- def vat_rate
54
- product.vat_rate
55
- end
57
+ delegate :vat_rate, :vat_ratio, to: :sellable
58
+
56
59
 
57
60
  def check_prices
58
- vat_ratio = self.product.vat_ratio rescue Glysellin.default_vat_rate
59
61
  # If we have to fill one of the prices when changed
60
62
  if eot_changed_alone?
61
63
  self.price = (self.eot_price * vat_ratio).round(2)
@@ -84,12 +86,12 @@ module Glysellin
84
86
  end
85
87
 
86
88
  def name fullname = true
87
- variant_name, product_name = super().presence, product.name
89
+ variant_name, sellable_name = super().presence, (sellable && sellable.name)
88
90
 
89
91
  if fullname
90
- variant_name ? "#{ product_name } - #{ variant_name }" : product_name
92
+ variant_name ? "#{ sellable_name } - #{ variant_name }" : sellable_name
91
93
  else
92
- variant_name ? variant_name : product_name
94
+ variant_name ? variant_name : sellable_name
93
95
  end
94
96
  end
95
97
 
@@ -2,12 +2,19 @@
2
2
  To implement custom behavior, just replace this view, keeping the .added-to-cart-warning container
3
3
  Default behavior is triggered when [data-warning] attribute is present on the container
4
4
  %>
5
- <div class="added-to-cart-warning" style="position:fixed;width:500px;height:400px;top:50%;left:50%;margin-left:-250px;margin-top:-200px;border:1px solid #CCC;border-radius:2px;padding:10px;box-shadow:0 0 15px #DDD;z-index:100;background:white;" data-warning>
5
+
6
+ <% product = current_cart.product(params[:cart][:product_id]).variant %>
7
+
8
+ <div class="added-to-cart-warning" style="position:fixed;width:400px;height:200px;top:50%;left:50%;margin-left:-200px;margin-top:-140px;border:1px solid #CCC;border-radius:2px;padding:40px;box-shadow:0 0 15px #DDD;z-index:100;background:white;" data-warning>
6
9
  <h3><%= t('glysellin.labels.cart.added_to_cart') %></h3>
7
10
  <hr>
8
-
9
- <% product = current_cart.product(params[:cart][:product_id]).variant %>
10
- <%= params[:cart][:quantity] %> &times; <%= product.name %> - <%= number_to_currency product.price %>
11
+ <strong>
12
+ <%= (quantity = params[:cart][:quantity].to_i) %> &times;
13
+ </strong>
14
+ <%= product.name %> - <%= number_to_currency product.price %>
15
+ <strong style="float:right;">
16
+ <%= number_to_currency (product.price * quantity) %>
17
+ </strong>
11
18
  <br>
12
19
 
13
20
  <hr>
@@ -31,6 +31,7 @@
31
31
  <% end %>
32
32
  </td>
33
33
  <td class="product-eot-price">
34
+ <% puts "***** #{quantity} -- #{item.inspect}" %>
34
35
  <%= number_to_currency(quantity * item.eot_price) %>
35
36
  </td>
36
37
  <td class="product-price">
@@ -0,0 +1,13 @@
1
+ - if sellable.published_variants.length > 0
2
+ = form_tag cart_products_path, class: 'add-to-cart-form', remote: options[:remote], data: { "add-to-cart-form" => true } do
3
+ .product-quantity
4
+ = number_field :cart, :quantity, value: 1
5
+
6
+ .submit-container
7
+ - if sellable.published_variants.length > 1
8
+ = select_tag 'cart[product_id]', variants_options_for(sellable), prompt: t("glysellin.labels.sellables.choose_your_variant"), required: "required"
9
+ - else
10
+ = hidden_field_tag 'cart[product_id]', sellable.published_variants.first.id
11
+
12
+ %button.submit{ type: "submit" }
13
+ = t('glysellin.labels.cart.add_to_cart')
@@ -78,6 +78,7 @@ en:
78
78
  back_to_shop: "Back to shop"
79
79
  remove_from_cart: "Delete"
80
80
  update: "Update cart"
81
+ added_to_cart: "Product added to cart"
81
82
  update_discount: "Update discount code"
82
83
  create_order_form: "Submit order"
83
84
  follow: "Go to order"
@@ -86,12 +87,12 @@ en:
86
87
  informations: "Your informations"
87
88
  shipping_method: "Shipping method"
88
89
  payment_method: "Payment method"
90
+ sellables:
91
+ out_of_stock: "Out of stock"
92
+ choose_your_variant: "--- Choose your product ---"
89
93
  orders:
90
94
  states:
91
- created: "Created"
92
- filling_address: "Addresses not filled"
93
- address: "Addresses filled"
94
- payment: "Payment method chosen"
95
+ ready: "Payment pending"
95
96
  paid: "Paid"
96
97
  shipped: "Shipped"
97
98
  cart:
@@ -97,6 +97,9 @@ fr:
97
97
  informations: "Vos informations"
98
98
  shipping_method: "Mode de livraison"
99
99
  payment_method: "Mode de paiement"
100
+ sellables:
101
+ out_of_stock: "Épuisé"
102
+ choose_your_variant: "--- Choisissez votre produit ---"
100
103
  orders:
101
104
  states:
102
105
  ready: "Paiement en attente"
@@ -0,0 +1,6 @@
1
+ class AddSellableReferencesToGlysellinProducts < ActiveRecord::Migration
2
+ def change
3
+ add_column :glysellin_products, :sellable_type, :string
4
+ add_column :glysellin_products, :sellable_id, :integer
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ class RemoveUnusedFieldsFromGlysellinProducts < ActiveRecord::Migration
2
+ def up
3
+ remove_column :glysellin_products, :name, :description, :display_priority,
4
+ :position, :slug, :sku, :published
5
+ end
6
+
7
+ def down
8
+ change_table :glysellin_products do |t|
9
+ t.string :name
10
+ t.text :description
11
+ t.integer :display_priority
12
+ t.integer :position
13
+ t.string :slug
14
+ t.string :sku
15
+ t.boolean :published, default: true
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ class RemoveTaxonomiesFromSystem < ActiveRecord::Migration
2
+ def up
3
+ drop_table :glysellin_taxonomies
4
+ drop_table :glysellin_products_taxonomies
5
+ end
6
+
7
+ def down
8
+ create_table :glysellin_taxonomies do |t|
9
+ t.string :name
10
+ t.string :slug
11
+ t.integer :parent_taxonomy_id
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ create_table :glysellin_products_taxonomies, :id => false do |t|
17
+ t.integer :product_id
18
+ t.integer :taxonomy_id
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ class RenameOrderItemsToLineItems < ActiveRecord::Migration
2
+ def change
3
+ rename_table :glysellin_order_items, :glysellin_line_items
4
+ end
5
+ end
6
+
@@ -0,0 +1,5 @@
1
+ class AddVariantIdToGlysellinLineItems < ActiveRecord::Migration
2
+ def change
3
+ add_column :glysellin_line_items, :variant_id, :integer
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ class MakeVariantsBelongToSellablesAndNotProducts < ActiveRecord::Migration
2
+ def up
3
+ change_table :glysellin_variants do |t|
4
+ t.integer :sellable_id
5
+ t.string :sellable_type
6
+ t.remove :product_id
7
+ end
8
+ end
9
+
10
+ def down
11
+ change_table :glysellin_variants do |t|
12
+ t.integer :product_id
13
+ t.remove :sellable_id, :sellable_type
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ class DropProductTypes < ActiveRecord::Migration
2
+ def up
3
+ drop_table :glysellin_product_types
4
+ drop_table :glysellin_product_types_properties
5
+
6
+ remove_column :glysellin_products, :product_type_id
7
+ end
8
+
9
+ def down
10
+ create_table :glysellin_product_types_properties, id: false do |t|
11
+ t.references :product_type, :product_property
12
+ end
13
+
14
+ create_table :glysellin_product_types do |t|
15
+ t.string :name
16
+ t.timestamps
17
+ end
18
+
19
+ add_column :glysellin_products, :product_type_id, :integer
20
+ add_column :glysellin_product_types_properties, :product_type_id, :integer
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ class RemoveUnusedProductPropertiesColumns < ActiveRecord::Migration
2
+ def up
3
+ change_table :glysellin_product_properties do |t|
4
+ t.remove :name, :adjustement, :variant_type
5
+ end
6
+ end
7
+
8
+ def down
9
+ change_table :glysellin_product_properties do |t|
10
+ t.string :name
11
+ t.decimal :adjustement, :precision => 11, :scale => 2
12
+ t.string :variant_type
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ class RemovePositionFromGlysellinVariants < ActiveRecord::Migration
2
+ def up
3
+ remove_column :glysellin_variants, :position
4
+ end
5
+
6
+ def down
7
+ add_column :glysellin_variants, :position, :integer
8
+ end
9
+ end
@@ -5,7 +5,6 @@ require "glysellin/engine"
5
5
  require "glysellin/helpers"
6
6
  require "glysellin/gateway"
7
7
  require "glysellin/discount_type_calculator"
8
- require "glysellin/product_methods"
9
8
  require "glysellin/property_finder"
10
9
  require "glysellin/products_list"
11
10
  require "glysellin/shipping_carrier"
@@ -1,4 +1,5 @@
1
1
  require 'glysellin/helpers'
2
+ require 'glysellin/sellable'
2
3
  require 'glysellin/engine/routes'
3
4
 
4
5
  module Glysellin
@@ -10,5 +11,11 @@ module Glysellin
10
11
  end
11
12
  end
12
13
 
14
+ initializer "Mix acts_as_sellable into ActiveRecord::Base" do
15
+ ActiveSupport.on_load :active_record do
16
+ ActiveRecord::Base.send(:include, Sellable)
17
+ end
18
+ end
19
+
13
20
  end
14
21
  end
@@ -18,16 +18,6 @@ module ActionDispatch::Routing
18
18
  end
19
19
  end
20
20
 
21
- resources :products, controller: controllers[:products] do
22
- collection do
23
- get 'taxonomy/:id', action: "filter", as: "filter"
24
- end
25
- end
26
-
27
- resources :taxonomies, only: [] do
28
- resources :products, only: [:index]
29
- end
30
-
31
21
  resource :cart, controller: controllers[:cart], only: [:show, :destroy] do
32
22
  resources :products, controller: "glysellin/cart/products", only: [:create, :update, :destroy] do
33
23
  collection do
@@ -51,7 +41,6 @@ module ActionDispatch::Routing
51
41
  def parse_controllers options
52
42
  defaults = {
53
43
  orders: 'glysellin/orders',
54
- products: 'glysellin/products',
55
44
  cart: 'glysellin/cart'
56
45
  }
57
46
 
@@ -1,12 +1,12 @@
1
1
  module Glysellin
2
2
  module PropertyFinder
3
- def method_missing method, *args, &block
3
+ def method_missing method, *args
4
4
  if (prop = find { |prop| prop.type.name == method.to_s })
5
5
  prop.value
6
6
  elsif Glysellin::ProductPropertyType.select('1').find_by_name(method)
7
7
  false
8
8
  else
9
- super(method, *args, &block)
9
+ super
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,69 @@
1
+ require "glysellin/sellable/simple"
2
+ require "glysellin/sellable/multi"
3
+
4
+ module Glysellin
5
+ module Sellable
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ # Macro to include sellable behaviour in side any model
11
+ #
12
+ # @param [Hash] options An option hash to configure the mixin
13
+ #
14
+ # @option options [Symbol] :simple If set to true, sellable will only have
15
+ # one variant, a has_one relation is defined between sellable and the
16
+ # Variant model
17
+ # @option options [Boolean] :stock If set to false, consider every itmes
18
+ # should have unlimited stock. Defaults to true
19
+ #
20
+ def acts_as_sellable options = {}
21
+ # Merge options with defaults and store them for further use
22
+ cattr_accessor :sellable_options
23
+ self.sellable_options = options.reverse_merge(
24
+ simple: false,
25
+ stock: true
26
+ )
27
+
28
+ if sellable_options[:simple]
29
+ include Sellable::Simple
30
+ else
31
+ include Sellable::Multi
32
+ end
33
+
34
+ # Defines the polymorphic has_one association with the
35
+ # Glysellin::Product model, holding product wide configurations
36
+ has_one :product, as: :sellable, class_name: "Glysellin::Product",
37
+ inverse_of: :sellable, dependent: :destroy
38
+ accepts_nested_attributes_for :product, allow_destroy: true
39
+ attr_accessible :product_attributes
40
+
41
+ unless sellable_options[:stock]
42
+ before_validation :ensure_unlimited_stock
43
+ end
44
+
45
+ # Attributes delegation. Allowing to consider Product as a simple
46
+ # configuration model for the sellable one.
47
+ #
48
+ delegate :vat_rate, :vat_ratio, to: :product
49
+
50
+ # delegate :price, :unmarked_price, :marked_down?, to: :master_variant
51
+ end
52
+ end
53
+
54
+ # If acts_as_sellable is called with the option `stock: false`, we admit
55
+ # every variant must be unlimited stock, so admins never have to bother
56
+ # filling in stock or selecting `unlimited_stock: true`
57
+ #
58
+ def ensure_unlimited_stock
59
+ variants.each do |variant|
60
+ variant.unlimited_stock = true
61
+ variant.save unless variant.new_record?
62
+ end
63
+ end
64
+
65
+ def published_variants
66
+ variants.published
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,24 @@
1
+ module Glysellin
2
+ module Sellable
3
+ module Multi
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_many :variants, as: :sellable, class_name: "Glysellin::Variant",
8
+ inverse_of: :sellable, dependent: :destroy
9
+ accepts_nested_attributes_for :variants, allow_destroy: true,
10
+ reject_if: :all_blank
11
+ attr_accessible :variants_attributes
12
+
13
+ # Published sellables are the ones that have at least one variant
14
+ # published.
15
+ #
16
+ # This behaviour can be overriden inside the sellable's model if the
17
+ # scope is declared after the `acts_as_sellable` call
18
+ scope :published, -> {
19
+ includes(:variants).where(glysellin_variants: { published: true })
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ module Glysellin
2
+ module Sellable
3
+ module Simple
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_one :variant, as: :sellable, class_name: "Glysellin::Variant",
8
+ inverse_of: :sellable, dependent: :destroy
9
+ accepts_nested_attributes_for :variant, allow_destroy: true,
10
+ reject_if: :all_blank
11
+ attr_accessible :variant_attributes
12
+
13
+ # Published sellables are the ones that have at least one variant
14
+ # published.
15
+ #
16
+ # This behaviour can be overriden inside the sellable's model if the
17
+ # scope is declared after the `acts_as_sellable` call
18
+ scope :published, -> {
19
+ includes(:variant).where( glysellin_variants: { published: true })
20
+ }
21
+ end
22
+
23
+ # Fetches variant wrapping it in an ActiveRecord::Relation object to
24
+ # make behavior consistent with sellables in Multi mode
25
+ def variants
26
+ Variant.where(sellable_id: id, sellable_type: self.class.to_s)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module Glysellin
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.1.1"
3
3
  end
@@ -5,7 +5,7 @@ namespace :glysellin do
5
5
  task :seed => :environment do
6
6
  [['Chèque', 'check'],
7
7
  ['Paypal', 'paypal-integral'],
8
- ['Atos', 'atos']].each do |payment_method|
8
+ ['Carte bancaire', 'atos']].each do |payment_method|
9
9
  attributes = Hash[[:name, :slug].zip(payment_method)]
10
10
  Glysellin::PaymentMethod.create(attributes)
11
11
  puts "Created payment method : #{ attributes[:name] }"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glysellin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Valentin Ballestrino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-07 00:00:00.000000000 Z
11
+ date: 2013-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -161,7 +161,6 @@ files:
161
161
  - app/controllers/glysellin/cart/state_controller.rb
162
162
  - app/controllers/glysellin/cart_controller.rb
163
163
  - app/controllers/glysellin/orders_controller.rb
164
- - app/controllers/glysellin/products_controller.rb
165
164
  - app/helpers/glysellin/application_helper.rb
166
165
  - app/helpers/glysellin/cart_helper.rb
167
166
  - app/helpers/glysellin/orders_helper.rb
@@ -172,9 +171,9 @@ files:
172
171
  - app/models/glysellin/brand.rb
173
172
  - app/models/glysellin/discount_code.rb
174
173
  - app/models/glysellin/discount_type.rb
174
+ - app/models/glysellin/line_item.rb
175
175
  - app/models/glysellin/order.rb
176
176
  - app/models/glysellin/order_adjustment.rb
177
- - app/models/glysellin/order_item.rb
178
177
  - app/models/glysellin/orderer.rb
179
178
  - app/models/glysellin/payment.rb
180
179
  - app/models/glysellin/payment_method.rb
@@ -182,9 +181,7 @@ files:
182
181
  - app/models/glysellin/product_image.rb
183
182
  - app/models/glysellin/product_property.rb
184
183
  - app/models/glysellin/product_property_type.rb
185
- - app/models/glysellin/product_type.rb
186
184
  - app/models/glysellin/shipping_method.rb
187
- - app/models/glysellin/taxonomy.rb
188
185
  - app/models/glysellin/variant.rb
189
186
  - app/views/glysellin/cart/_added_to_cart_warning.html.erb
190
187
  - app/views/glysellin/cart/_cart.html.erb
@@ -214,11 +211,7 @@ files:
214
211
  - app/views/glysellin/orders/_payment_successful.html.erb
215
212
  - app/views/glysellin/orders/payment_response.html.erb
216
213
  - app/views/glysellin/payment_methods/_paypal-integral.html.erb
217
- - app/views/glysellin/products/_add_to_cart.html.erb
218
- - app/views/glysellin/products/_item.html.erb
219
- - app/views/glysellin/products/filter.html.erb
220
- - app/views/glysellin/products/index.html.erb
221
- - app/views/glysellin/products/show.html.erb
214
+ - app/views/glysellin/products/_add_to_cart.html.haml
222
215
  - app/views/glysellin/shared/_address.html.erb
223
216
  - app/views/glysellin/shared/_address.text.erb
224
217
  - app/views/glysellin/shared/_order.text.erb
@@ -261,6 +254,15 @@ files:
261
254
  - db/migrate/20130311135842_add_addressable_to_glysellin_addresses.rb
262
255
  - db/migrate/20130314114048_change_addressable_associations_in_glysellin_addresses.rb
263
256
  - db/migrate/20130325113903_remove_address_ids_from_glysellin_orders.rb
257
+ - db/migrate/20130609013900_add_sellable_references_to_glysellin_products.rb
258
+ - db/migrate/20130609023300_remove_unused_fields_from_glysellin_products.rb
259
+ - db/migrate/20130609025800_remove_taxonomies_from_system.rb
260
+ - db/migrate/20130609040600_rename_order_items_to_line_items.rb
261
+ - db/migrate/20130609142100_add_variant_id_to_glysellin_line_items.rb
262
+ - db/migrate/20130609155600_make_variants_belong_to_sellables_and_not_products.rb
263
+ - db/migrate/20130609193600_drop_product_types.rb
264
+ - db/migrate/20130609200000_remove_unused_product_properties_columns.rb
265
+ - db/migrate/20130609200700_remove_position_from_glysellin_variants.rb
264
266
  - db/seeds/shipping_carrier/rates/colissimo.csv
265
267
  - db/seeds/shipping_carrier/rates/lettre-max.csv
266
268
  - lib/active_model/model.rb
@@ -269,7 +271,6 @@ files:
269
271
  - lib/generators/glysellin/install/templates/initializer.rb
270
272
  - lib/generators/glysellin/install/templates/order_observer.rb
271
273
  - lib/generators/glysellin/install/utils.rb
272
- - lib/glysellin/acts_as_sellable.rb
273
274
  - lib/glysellin/cart/address.rb
274
275
  - lib/glysellin/cart/adjustment/base.rb
275
276
  - lib/glysellin/cart/adjustment/discount_code.rb
@@ -299,9 +300,11 @@ files:
299
300
  - lib/glysellin/helpers/countries.rb
300
301
  - lib/glysellin/helpers/views.rb
301
302
  - lib/glysellin/helpers.rb
302
- - lib/glysellin/product_methods.rb
303
303
  - lib/glysellin/products_list.rb
304
304
  - lib/glysellin/property_finder.rb
305
+ - lib/glysellin/sellable/multi.rb
306
+ - lib/glysellin/sellable/simple.rb
307
+ - lib/glysellin/sellable.rb
305
308
  - lib/glysellin/shipping_carrier/base.rb
306
309
  - lib/glysellin/shipping_carrier/colissimo.rb
307
310
  - lib/glysellin/shipping_carrier/flat_rate.rb
@@ -1,12 +0,0 @@
1
- module Glysellin
2
- class ProductsController < ApplicationController
3
- def index
4
- @products = Glysellin::Product.order('created_at DESC').limit(10)
5
- @products = @products.with_taxonomy(params[:taxonomy_id]) if params[:taxonomy_id]
6
- end
7
-
8
- def show
9
- @product = Glysellin::Product.find(params[:id])
10
- end
11
- end
12
- end
@@ -1,11 +0,0 @@
1
- class Glysellin::ProductType < ActiveRecord::Base
2
- self.table_name = 'glysellin_product_types'
3
-
4
- has_and_belongs_to_many :property_types,
5
- class_name: 'Glysellin::ProductPropertyType',
6
- join_table: 'glysellin_product_types_property_types'
7
-
8
- has_many :products
9
-
10
- attr_accessible :name, :product_ids, :property_type_ids
11
- end
@@ -1,35 +0,0 @@
1
- module Glysellin
2
- class Taxonomy < ActiveRecord::Base
3
- extend FriendlyId
4
-
5
- self.table_name = 'glysellin_taxonomies'
6
-
7
- friendly_id :name, use: :slugged
8
-
9
- has_and_belongs_to_many :products,
10
- join_table: 'glysellin_products_taxonomies'
11
- has_many :sub_taxonomies, class_name: 'Taxonomy',
12
- foreign_key: 'parent_taxonomy_id'
13
- belongs_to :parent_taxonomy, foreign_key: 'parent_taxonomy_id',
14
- class_name: 'Taxonomy'
15
-
16
- attr_accessible :name, :parent_taxonomy, :sub_taxonomies, :products,
17
- :product_ids, :sub_taxonomy_ids, :parent_taxonomy_id
18
-
19
- def all_products
20
- Product.includes(:taxonomies).where(
21
- 'glysellin_products_taxonomies.taxonomy_id IN (?)',
22
- taxonomy_tree_ids
23
- )
24
- end
25
-
26
- def taxonomy_tree_ids
27
- if sub_taxonomies.length > 0
28
- ts = sub_taxonomies.includes(:sub_taxonomies).map(&:taxonomy_tree_ids)
29
- ts.flatten << self.id
30
- else
31
- self.id
32
- end
33
- end
34
- end
35
- end
@@ -1,16 +0,0 @@
1
- <%= form_tag cart_products_path, class: 'add-to-cart-form', remote: options[:remote], data: { "add-to-cart-form" => true } do %>
2
- <div class="product-quantity">
3
- <%= number_field :cart, :quantity, value: 1 %>
4
- </div>
5
- <div class="submit-container">
6
- <% if product.variants.length > 1 %>
7
- <%= select_tag 'cart[product_id]', options_for_select(product.variants.collect {|v| [ v.name, v.id ] }) %>
8
- <% else %>
9
- <%= hidden_field_tag 'cart[product_id]', product.variants.first.id %>
10
- <% end %>
11
-
12
- <button class="submit" type="submit">
13
- <%= t('glysellin.labels.cart.add_to_cart') %>
14
- </button>
15
- </div>
16
- <% end %>
@@ -1,21 +0,0 @@
1
- <div class="product-container">
2
- <span class="name">
3
- <%= link_to product.name, product_path(product) %>
4
- </span>
5
- <% if product.image %>
6
- <div class="image">
7
- <%= image_tag product.image.url(:thumb) %>
8
- </div>
9
- <% end %>
10
- <div class="description">
11
- <%= product.description %>
12
- </div>
13
-
14
- <div class="buying-infos">
15
- <span class="price">
16
- <%= number_to_currency(product.price) %>
17
- </span>
18
-
19
- <%= add_to_cart_form(product) %>
20
- </div>
21
- </div>
@@ -1,3 +0,0 @@
1
- <% @products.each do |product| %>
2
- <%= render partial: "item", locals: { product: product } %>
3
- <% end %>
@@ -1,3 +0,0 @@
1
- <%= render partial: "item", locals: { product: @product } %>
2
-
3
- <%= link_to t('glysellin.labels.misc.back_to_shop'), root_path %>
@@ -1,14 +0,0 @@
1
- module Glysellin
2
- module ActsAsSellable
3
- extend ActiveSupport::Concern
4
-
5
- module ClassMethods
6
- def acts_as_sellable
7
- has_one :product, :class_name => "::Glysellin::Product", :inverse_of => :item
8
- accepts_nested_attributes_for :product, :allow_destroy => true
9
- end
10
- end
11
- end
12
- end
13
-
14
- ::ActiveRecord::Base.send :include, Glysellin::ActsAsSellable
@@ -1,66 +0,0 @@
1
- module Glysellin
2
- module ProductMethods
3
- extend ActiveSupport::Concern
4
-
5
- module ClassMethods
6
- attr_writer :bundle_attributes
7
-
8
- def bundle_attributes
9
- @bundle_attributes ||= {}
10
- end
11
-
12
- def bundle_attribute attribute, &block
13
- # Store block to be called to retrieve
14
- self.bundle_attributes[attribute.to_sym] = block
15
-
16
- class_eval <<-CLASS, __FILE__, __LINE__
17
- def bundle_#{ attribute }
18
- if bundled_products.length > 0
19
- self.class.bundle_attributes[:#{ attribute }].call(self)
20
- end
21
- end
22
-
23
- def #{ attribute }
24
- value = super
25
- value = bundle_#{ attribute } if value == nil
26
- value
27
- end
28
-
29
- def #{ attribute }=(value)
30
- super(value) if value != bundle_#{ attribute }
31
- end
32
- CLASS
33
- end
34
- end
35
-
36
- # SKU generation helper
37
- #
38
- # @return [String] The generated SKU
39
- def generate_sku
40
- # Get last product only selecting id
41
- last_item = self.class.select(:id).order('id DESC').first
42
- # Generate SKU from the last product id we got,
43
- # or appending "1" if there's no product
44
- sku = (last_item ? (last_item.id + 1).to_s : '1')
45
- sku += self.name.parameterize
46
- end
47
-
48
- # Checks if a product is a bundle of other sub products
49
- #
50
- # @return [true, false]
51
- def bundle?
52
- false
53
- # bundled_products.length > 0
54
- end
55
-
56
- # Gives calculable vat rate
57
- #
58
- # Example :
59
- # If vat_rate is set to 19.6%, returns back 1.196
60
- #
61
- # @return [Float] The processed VAT rate
62
- def vat_ratio
63
- 1 + vat_rate / 100
64
- end
65
- end
66
- end