comable-core 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +97 -0
- data/app/assets/javascripts/comable/application.js +13 -0
- data/app/assets/stylesheets/comable/application.css +13 -0
- data/app/controllers/concerns/comable/permitted_attributes.rb +15 -0
- data/app/helpers/comable/application_helper.rb +84 -0
- data/app/helpers/comable/products_helper.rb +82 -0
- data/app/mailers/comable/order_mailer.rb +18 -0
- data/app/models/comable/ability.rb +18 -0
- data/app/models/comable/address.rb +39 -0
- data/app/models/comable/category.rb +72 -0
- data/app/models/comable/image.rb +15 -0
- data/app/models/comable/order.rb +121 -0
- data/app/models/comable/order/associations.rb +22 -0
- data/app/models/comable/order/callbacks.rb +43 -0
- data/app/models/comable/order/morrisable.rb +20 -0
- data/app/models/comable/order/scopes.rb +17 -0
- data/app/models/comable/order/validations.rb +39 -0
- data/app/models/comable/order_item.rb +98 -0
- data/app/models/comable/order_item/csvable.rb +32 -0
- data/app/models/comable/page.rb +30 -0
- data/app/models/comable/payment.rb +87 -0
- data/app/models/comable/payment_method.rb +27 -0
- data/app/models/comable/product.rb +55 -0
- data/app/models/comable/product/csvable.rb +20 -0
- data/app/models/comable/shipment.rb +85 -0
- data/app/models/comable/shipment_method.rb +9 -0
- data/app/models/comable/stock.rb +71 -0
- data/app/models/comable/stock/csvable.rb +26 -0
- data/app/models/comable/store.rb +30 -0
- data/app/models/comable/theme.rb +25 -0
- data/app/models/comable/tracker.rb +17 -0
- data/app/models/comable/user.rb +130 -0
- data/app/models/concerns/comable/cart_owner.rb +94 -0
- data/app/models/concerns/comable/checkout.rb +85 -0
- data/app/models/concerns/comable/importable.rb +67 -0
- data/app/models/concerns/comable/liquidable.rb +12 -0
- data/app/models/concerns/comable/product/search.rb +41 -0
- data/app/models/concerns/comable/ransackable.rb +38 -0
- data/app/models/concerns/comable/role_owner.rb +15 -0
- data/app/models/concerns/comable/sku_choice.rb +19 -0
- data/app/models/concerns/comable/sku_item.rb +17 -0
- data/app/uploaders/image_uploader.rb +7 -0
- data/app/views/comable/order_mailer/complete.text.erb +42 -0
- data/config/initializers/comma.rb +8 -0
- data/config/locales/en.yml +424 -0
- data/config/locales/ja.yml +425 -0
- data/db/migrate/20131214194807_create_comable_products.rb +15 -0
- data/db/migrate/20140120032559_create_comable_users.rb +46 -0
- data/db/migrate/20140502060116_create_comable_stocks.rb +14 -0
- data/db/migrate/20140723175431_create_comable_orders.rb +20 -0
- data/db/migrate/20140723175810_create_comable_order_items.rb +19 -0
- data/db/migrate/20140817194104_create_comable_payment_methods.rb +13 -0
- data/db/migrate/20140921191416_create_comable_shipment_methods.rb +11 -0
- data/db/migrate/20140926063541_create_comable_stores.rb +11 -0
- data/db/migrate/20141024025526_create_comable_addresses.rb +17 -0
- data/db/migrate/20150111031228_create_comable_categories.rb +10 -0
- data/db/migrate/20150111031229_create_comable_products_categories.rb +8 -0
- data/db/migrate/20150112173706_create_comable_images.rb +9 -0
- data/db/migrate/20150423095210_create_comable_shipments.rb +13 -0
- data/db/migrate/20150511171940_create_comable_payments.rb +12 -0
- data/db/migrate/20150513185230_create_comable_trackers.rb +12 -0
- data/db/migrate/20150519080729_create_comable_pages.rb +17 -0
- data/db/migrate/20150612143226_create_comable_themes.rb +15 -0
- data/db/migrate/20150612143445_add_theme_id_to_comable_stores.rb +7 -0
- data/db/seeds.rb +5 -0
- data/db/seeds/comable/users.rb +51 -0
- data/lib/comable/core.rb +48 -0
- data/lib/comable/core/configuration.rb +22 -0
- data/lib/comable/core/engine.rb +50 -0
- data/lib/comable/deprecator.rb +26 -0
- data/lib/comable/payment_provider.rb +14 -0
- data/lib/comable/payment_provider/base.rb +42 -0
- data/lib/comable/payment_provider/general.rb +15 -0
- data/lib/comable/state_machine_patch.rb +32 -0
- data/lib/comma_extractor_extentions.rb +31 -0
- data/lib/generators/comable/install/install_generator.rb +133 -0
- data/lib/generators/comable/install/templates/config/initializers/comable.rb +31 -0
- data/lib/tasks/comable_tasks.rake +4 -0
- metadata +346 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
module Comable
|
2
|
+
class Payment < ActiveRecord::Base
|
3
|
+
include Comable::Ransackable
|
4
|
+
|
5
|
+
belongs_to :order, class_name: Comable::Order.name, inverse_of: :payment
|
6
|
+
belongs_to :payment_method, class_name: Comable::PaymentMethod.name
|
7
|
+
|
8
|
+
before_validation :copy_attributes_from_payment_method, unless: :order_completed?
|
9
|
+
|
10
|
+
validates :order, presence: true
|
11
|
+
validates :payment_method, presence: true
|
12
|
+
validates :fee, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
13
|
+
|
14
|
+
delegate :name, to: :payment_method
|
15
|
+
delegate :payment_provider, to: :payment_method
|
16
|
+
delegate :authorize!, :complete!, :cancel!, :resume!, to: :payment_provider, prefix: :provider
|
17
|
+
|
18
|
+
ransack_options ransackable_attributes: { except: [:order_id, :payment_method_id] }
|
19
|
+
|
20
|
+
# The #state attribute assigns the following values:
|
21
|
+
#
|
22
|
+
# pending when Order is not able to pay (default)
|
23
|
+
# ready when Order is able to pay
|
24
|
+
# completed when Order is already paid
|
25
|
+
# canceled when Order is canceled
|
26
|
+
# resumed when Order is resumed from the "canceled" state
|
27
|
+
state_machine initial: :pending do
|
28
|
+
state :pending
|
29
|
+
state :ready
|
30
|
+
state :completed
|
31
|
+
state :canceled
|
32
|
+
state :resumed
|
33
|
+
|
34
|
+
event :next_state do
|
35
|
+
transition :pending => :ready
|
36
|
+
transition :ready => :completed
|
37
|
+
end
|
38
|
+
|
39
|
+
event :cancel do
|
40
|
+
transition [:completed, :resumed] => :canceled
|
41
|
+
end
|
42
|
+
|
43
|
+
event :resume do
|
44
|
+
transition :canceled => :resumed
|
45
|
+
end
|
46
|
+
|
47
|
+
after_transition to: :ready, do: :next_state!
|
48
|
+
before_transition to: :ready, do: -> (s) { s.provider_authorize! }
|
49
|
+
before_transition to: :completed, do: -> (s) { s.complete! }
|
50
|
+
before_transition to: :canceled, do: -> (s) { s.provider_cancel! }
|
51
|
+
before_transition to: :canceled, do: -> (s) { s.provider_resume! }
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
def state_names
|
56
|
+
state_machine.states.keys
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def stated?(target_state)
|
61
|
+
target_state_index = self.class.state_names.index(target_state.to_sym)
|
62
|
+
current_state_index = self.class.state_names.index(state_name)
|
63
|
+
target_state_index < current_state_index
|
64
|
+
end
|
65
|
+
|
66
|
+
def completed?
|
67
|
+
completed_at?
|
68
|
+
end
|
69
|
+
|
70
|
+
def complete!
|
71
|
+
provider_complete!
|
72
|
+
touch :completed_at
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def order_completed?
|
78
|
+
order.completed?
|
79
|
+
end
|
80
|
+
|
81
|
+
def copy_attributes_from_payment_method
|
82
|
+
self.attributes = {
|
83
|
+
fee: payment_method.fee
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Comable
|
2
|
+
class PaymentMethod < ActiveRecord::Base
|
3
|
+
validates :name, presence: true, length: { maximum: 255 }
|
4
|
+
validates :payment_provider_type, presence: true, length: { maximum: 255 }
|
5
|
+
validates :payment_provider_kind, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
6
|
+
validates :fee, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
7
|
+
validates :enable_price_from, numericality: { greater_than_or_equal_to: 0, allow_blank: true }
|
8
|
+
validates :enable_price_to, numericality: { greater_than_or_equal_to: 0, allow_blank: true }
|
9
|
+
|
10
|
+
def payment_provider
|
11
|
+
return unless Object.const_defined?(payment_provider_type)
|
12
|
+
Object.const_get(payment_provider_type)
|
13
|
+
end
|
14
|
+
|
15
|
+
def payment_provider_name
|
16
|
+
payment_provider.display_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def payment_provider_kind_key
|
20
|
+
payment_provider.kind.keys.slice(payment_provider_kind)
|
21
|
+
end
|
22
|
+
|
23
|
+
def payment_provider_kind_name
|
24
|
+
payment_provider.kind.slice(payment_provider_kind_key).values.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Comable
|
2
|
+
class Product < ActiveRecord::Base
|
3
|
+
include Comable::SkuItem
|
4
|
+
include Comable::Ransackable
|
5
|
+
include Comable::Liquidable
|
6
|
+
include Comable::Product::Search
|
7
|
+
include Comable::Product::Csvable
|
8
|
+
|
9
|
+
has_many :stocks, class_name: Comable::Stock.name, dependent: :destroy
|
10
|
+
has_many :images, class_name: Comable::Image.name, dependent: :destroy
|
11
|
+
has_and_belongs_to_many :categories, class_name: Comable::Category.name, join_table: :comable_products_categories
|
12
|
+
|
13
|
+
accepts_nested_attributes_for :images, allow_destroy: true
|
14
|
+
|
15
|
+
validates :name, presence: true, length: { maximum: 255 }
|
16
|
+
validates :code, presence: true, length: { maximum: 255 }
|
17
|
+
validates :price, presence: true, numericality: { greater_than_or_equal_to: 0, allow_blank: true }
|
18
|
+
validates :sku_h_item_name, length: { maximum: 255 }
|
19
|
+
validates :sku_v_item_name, length: { maximum: 255 }
|
20
|
+
|
21
|
+
liquid_methods :id, :code, :name, :price, :images, :image_url
|
22
|
+
|
23
|
+
ransack_options attribute_select: { associations: :stocks }
|
24
|
+
|
25
|
+
# Add conditions for the images association.
|
26
|
+
# Override method of the images association to support Rails 3.x.
|
27
|
+
def images
|
28
|
+
super.order(:id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def image_url
|
32
|
+
image = images.first
|
33
|
+
return image.url if image
|
34
|
+
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTcxIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDE3MSAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxkZWZzLz48cmVjdCB3aWR0aD0iMTcxIiBoZWlnaHQ9IjE4MCIgZmlsbD0iI0VFRUVFRSIvPjxnPjx0ZXh0IHg9IjU4IiB5PSI5MCIgc3R5bGU9ImZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxMHB0O2RvbWluYW50LWJhc2VsaW5lOmNlbnRyYWwiPjE3MXgxODA8L3RleHQ+PC9nPjwvc3ZnPg=='
|
35
|
+
end
|
36
|
+
|
37
|
+
def stocked?
|
38
|
+
stocks.stocked.exists?
|
39
|
+
end
|
40
|
+
|
41
|
+
def unstocked?
|
42
|
+
!stocked?
|
43
|
+
end
|
44
|
+
|
45
|
+
def category_path_names=(category_path_names, delimiter: Comable::Category::DEFAULT_PATH_NAME_DELIMITER)
|
46
|
+
self.categories = Comable::Category.find_by_path_names(category_path_names, delimiter: delimiter)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def create_stock
|
52
|
+
stocks.create(code: code) unless stocks.exists?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Comable
|
2
|
+
class Product < ActiveRecord::Base
|
3
|
+
module Csvable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
include Comable::Importable
|
7
|
+
|
8
|
+
included do
|
9
|
+
comma do
|
10
|
+
name
|
11
|
+
code
|
12
|
+
price
|
13
|
+
caption
|
14
|
+
sku_h_item_name
|
15
|
+
sku_v_item_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Comable
|
2
|
+
class Shipment < ActiveRecord::Base
|
3
|
+
include Comable::Ransackable
|
4
|
+
|
5
|
+
belongs_to :order, class_name: Comable::Order.name, inverse_of: :shipment
|
6
|
+
belongs_to :shipment_method, class_name: Comable::ShipmentMethod.name
|
7
|
+
|
8
|
+
before_validation :copy_attributes_from_shipment_method, unless: :order_completed?
|
9
|
+
|
10
|
+
validates :order, presence: true
|
11
|
+
validates :shipment_method, presence: true
|
12
|
+
validates :fee, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
13
|
+
validates :tracking_number, length: { maximum: 255 }
|
14
|
+
|
15
|
+
delegate :name, to: :shipment_method
|
16
|
+
|
17
|
+
ransack_options ransackable_attributes: { except: [:order_id, :shipment_method_id] }
|
18
|
+
|
19
|
+
# The #state attribute assigns the following values:
|
20
|
+
#
|
21
|
+
# pending when Order is not able to ship (default)
|
22
|
+
# ready when Order is able to ship
|
23
|
+
# completed when Order is already shipped
|
24
|
+
# canceled when Order is canceled
|
25
|
+
# resumed when Order is resumed from the "canceled" state
|
26
|
+
state_machine initial: :pending do
|
27
|
+
state :pending
|
28
|
+
state :ready
|
29
|
+
state :completed
|
30
|
+
state :canceled
|
31
|
+
state :resumed
|
32
|
+
|
33
|
+
event :next_state do
|
34
|
+
transition :pending => :ready
|
35
|
+
transition :ready => :completed
|
36
|
+
end
|
37
|
+
|
38
|
+
event :ship do
|
39
|
+
transition :ready => :completed
|
40
|
+
end
|
41
|
+
|
42
|
+
event :cancel do
|
43
|
+
transition [:completed, :resumed] => :canceled
|
44
|
+
end
|
45
|
+
|
46
|
+
event :resume do
|
47
|
+
transition :canceled => :resumed
|
48
|
+
end
|
49
|
+
|
50
|
+
before_transition to: :completed, do: -> (s) { s.complete! }
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def state_names
|
55
|
+
state_machine.states.keys
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def stated?(target_state)
|
60
|
+
target_state_index = self.class.state_names.index(target_state.to_sym)
|
61
|
+
current_state_index = self.class.state_names.index(state_name)
|
62
|
+
target_state_index < current_state_index
|
63
|
+
end
|
64
|
+
|
65
|
+
def completed?
|
66
|
+
completed_at?
|
67
|
+
end
|
68
|
+
|
69
|
+
def complete!
|
70
|
+
touch :completed_at
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def order_completed?
|
76
|
+
order.completed?
|
77
|
+
end
|
78
|
+
|
79
|
+
def copy_attributes_from_shipment_method
|
80
|
+
self.attributes = {
|
81
|
+
fee: shipment_method.fee
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Comable
|
2
|
+
class ShipmentMethod < ActiveRecord::Base
|
3
|
+
validates :name, presence: true, length: { maximum: 255 }
|
4
|
+
validates :fee, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
5
|
+
validates :traking_url, length: { maximum: 255 }
|
6
|
+
|
7
|
+
scope :activated, -> { where(activated_flag: true) }
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Comable
|
2
|
+
#
|
3
|
+
# 在庫モデル。
|
4
|
+
# 商品に複数紐付き、品数やSKU(Stock Keeping Unit)情報を保持する。
|
5
|
+
#
|
6
|
+
class Stock < ActiveRecord::Base
|
7
|
+
include Comable::SkuItem
|
8
|
+
include Comable::SkuChoice
|
9
|
+
include Comable::Ransackable
|
10
|
+
include Comable::Liquidable
|
11
|
+
include Comable::Stock::Csvable
|
12
|
+
|
13
|
+
belongs_to :product, class_name: Comable::Product.name
|
14
|
+
|
15
|
+
#
|
16
|
+
# @!group Scope
|
17
|
+
#
|
18
|
+
|
19
|
+
# @!scope class
|
20
|
+
# 品切れでない在庫インスタンスを返す
|
21
|
+
scope :stocked, -> { where('quantity > ?', 0) }
|
22
|
+
|
23
|
+
# @!scope class
|
24
|
+
# 品切れの在庫インスタンスを返す
|
25
|
+
scope :unstocked, -> { where('quantity <= ?', 0) }
|
26
|
+
|
27
|
+
#
|
28
|
+
# @!endgroup
|
29
|
+
#
|
30
|
+
|
31
|
+
validates :product, presence: { message: Comable.t('admin.is_not_exists') }
|
32
|
+
validates :code, presence: true, length: { maximum: 255 }
|
33
|
+
validates :sku_h_choice_name, length: { maximum: 255 }
|
34
|
+
validates :sku_v_choice_name, length: { maximum: 255 }
|
35
|
+
# TODO: add conditions (by limitless flag, backoder flag and etc..)
|
36
|
+
validates :quantity, numericality: { greater_than_or_equal_to: 0 }
|
37
|
+
|
38
|
+
delegate :name, to: :product
|
39
|
+
delegate :price, to: :product
|
40
|
+
delegate :sku_h_item_name, to: :product
|
41
|
+
delegate :sku_v_item_name, to: :product
|
42
|
+
|
43
|
+
ransack_options attribute_select: { associations: :product }, ransackable_attributes: { except: :product_id }
|
44
|
+
|
45
|
+
# 在庫の有無を取得する
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# stock.quanaity = 1
|
49
|
+
# stock.stocked? #=> true
|
50
|
+
#
|
51
|
+
# @param quantity [Fixnum] 減算する在庫数を指定する
|
52
|
+
# @return [Boolean] 在庫があれば true を返す
|
53
|
+
# @see #unstocked?
|
54
|
+
def stocked?(quantity: 1)
|
55
|
+
(self.quantity - quantity) >= 0
|
56
|
+
end
|
57
|
+
|
58
|
+
# 在庫の有無を取得する
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# stock.quanaity = 1
|
62
|
+
# stock.unstocked? #=> false
|
63
|
+
#
|
64
|
+
# @param quantity [Fixnum] 減算する在庫数を指定する
|
65
|
+
# @return [Boolean] {#stocked?} の逆。在庫がなければ true を返す
|
66
|
+
# @see #stocked?
|
67
|
+
def unstocked?(quantity: 1)
|
68
|
+
!stocked?(quantity: quantity)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Comable
|
2
|
+
class Stock < ActiveRecord::Base
|
3
|
+
module Csvable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
include Comable::Importable
|
7
|
+
|
8
|
+
included do
|
9
|
+
comma do
|
10
|
+
product_code
|
11
|
+
code
|
12
|
+
quantity
|
13
|
+
sku_h_choice_name
|
14
|
+
sku_v_choice_name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
delegate :code, to: :product, prefix: true, allow_nil: true
|
19
|
+
|
20
|
+
def product_code=(code)
|
21
|
+
return if product_code == code
|
22
|
+
self.product = Comable::Product.find_by(code: code)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Comable
|
2
|
+
class Store < ActiveRecord::Base
|
3
|
+
include Comable::Liquidable
|
4
|
+
|
5
|
+
belongs_to :theme, class_name: Comable::Theme.name
|
6
|
+
|
7
|
+
validates :name, length: { maximum: 255 }
|
8
|
+
validates :meta_keywords, length: { maximum: 255 }
|
9
|
+
validates :meta_description, length: { maximum: 255 }
|
10
|
+
validates :email, length: { maximum: 255 }
|
11
|
+
|
12
|
+
liquid_methods :name, :meta_keywords, :meta_description, :email
|
13
|
+
|
14
|
+
delegate :name, to: :theme, prefix: true, allow_nil: true
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def instance
|
18
|
+
first || new(name: default_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_name
|
22
|
+
Comable.t('default_store_name')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def can_send_mail?
|
27
|
+
email.present?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Comable
|
2
|
+
class Theme < ActiveRecord::Base
|
3
|
+
has_one :store, class_name: Comable::Store.name
|
4
|
+
|
5
|
+
validates :name, uniqueness: { scope: :version }
|
6
|
+
validates :name, presence: true, length: { maximum: 255 }
|
7
|
+
validates :version, presence: true, length: { maximum: 255 }
|
8
|
+
validates :display, length: { maximum: 255 }
|
9
|
+
validates :description, length: { maximum: 255 }
|
10
|
+
validates :homepage, length: { maximum: 255 }
|
11
|
+
validates :author, length: { maximum: 255 }
|
12
|
+
|
13
|
+
def default_version
|
14
|
+
'0.1.0'
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_param
|
18
|
+
name
|
19
|
+
end
|
20
|
+
|
21
|
+
def display_name
|
22
|
+
display.presence || name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|