glysellin 0.4.1 → 0.4.1.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.
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