biovision-courses 0.0.180221
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +26 -0
- data/app/assets/config/biovision_courses_manifest.js +2 -0
- data/app/assets/javascripts/biovision/courses/application.js +13 -0
- data/app/assets/stylesheets/biovision/courses/application.css +15 -0
- data/app/controllers/admin/course_categories_controller.rb +28 -0
- data/app/controllers/admin/course_tags_controller.rb +28 -0
- data/app/controllers/admin/courses_controller.rb +29 -0
- data/app/controllers/biovision/courses/application_controller.rb +7 -0
- data/app/controllers/course_categories_controller.rb +70 -0
- data/app/controllers/course_tags_controller.rb +66 -0
- data/app/controllers/courses_controller.rb +94 -0
- data/app/helpers/biovision/courses/application_helper.rb +6 -0
- data/app/helpers/biovision_courses_helper.rb +80 -0
- data/app/jobs/biovision/courses/application_job.rb +6 -0
- data/app/mailers/biovision/courses/application_mailer.rb +8 -0
- data/app/models/application_record.rb +3 -0
- data/app/models/biovision/courses/application_record.rb +7 -0
- data/app/models/course.rb +109 -0
- data/app/models/course_category.rb +137 -0
- data/app/models/course_course_tag.rb +13 -0
- data/app/models/course_tag.rb +49 -0
- data/app/uploaders/course_image_uploader.rb +46 -0
- data/app/views/admin/course_categories/_nav_item.html.erb +2 -0
- data/app/views/admin/course_categories/entity/_in_list.html.erb +43 -0
- data/app/views/admin/course_categories/index.html.erb +17 -0
- data/app/views/admin/course_categories/show.html.erb +85 -0
- data/app/views/admin/course_tags/_nav_item.html.erb +2 -0
- data/app/views/admin/course_tags/entity/_in_list.html.erb +21 -0
- data/app/views/admin/course_tags/index.html.erb +19 -0
- data/app/views/admin/course_tags/show.html.erb +39 -0
- data/app/views/admin/courses/_nav_item.html.erb +2 -0
- data/app/views/admin/courses/entity/_in_list.html.erb +40 -0
- data/app/views/admin/courses/index.html.erb +17 -0
- data/app/views/admin/courses/show.html.erb +117 -0
- data/app/views/admin/index/dashboard/_biovision_courses.html.erb +12 -0
- data/app/views/course_categories/_form.html.erb +96 -0
- data/app/views/course_categories/edit.html.erb +22 -0
- data/app/views/course_categories/edit.js.erb +1 -0
- data/app/views/course_categories/new.html.erb +15 -0
- data/app/views/course_categories/new.js.erb +1 -0
- data/app/views/course_tags/_form.html.erb +72 -0
- data/app/views/course_tags/edit.html.erb +17 -0
- data/app/views/course_tags/edit.js.erb +1 -0
- data/app/views/course_tags/new.html.erb +15 -0
- data/app/views/course_tags/new.js.erb +1 -0
- data/app/views/courses/_form.html.erb +244 -0
- data/app/views/courses/edit.html.erb +17 -0
- data/app/views/courses/edit.js.erb +1 -0
- data/app/views/courses/form/_wysiwyg.html.erb +14 -0
- data/app/views/courses/new.html.erb +15 -0
- data/app/views/courses/new.js.erb +1 -0
- data/app/views/layouts/biovision/courses/application.html.erb +14 -0
- data/config/locales/courses-ru.yml +138 -0
- data/config/routes.rb +26 -0
- data/db/migrate/20180218120000_prepare_course_privileges.rb +19 -0
- data/db/migrate/20180218120005_create_course_categories.rb +28 -0
- data/db/migrate/20180218120010_create_course_tags.rb +20 -0
- data/db/migrate/20180218120015_create_courses.rb +42 -0
- data/db/migrate/20180218120020_create_course_course_tags.rb +17 -0
- data/lib/biovision/courses.rb +7 -0
- data/lib/biovision/courses/engine.rb +18 -0
- data/lib/biovision/courses/version.rb +5 -0
- data/lib/tasks/biovision/courses_tasks.rake +4 -0
- metadata +207 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module BiovisionCoursesHelper
|
2
|
+
# @param [CourseTag] entity
|
3
|
+
# @param [String] text
|
4
|
+
def admin_course_tag_link(entity, text = entity.name)
|
5
|
+
link_to(text, admin_course_tag_path(entity.id))
|
6
|
+
end
|
7
|
+
|
8
|
+
# @param [CourseCategory] entity
|
9
|
+
# @param [String] text
|
10
|
+
def admin_course_category_link(entity, text = entity.name)
|
11
|
+
link_to(text, admin_course_category_path(entity.id))
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [Course] entity
|
15
|
+
# @param [String] text
|
16
|
+
def admin_course_link(entity, text = entity.title)
|
17
|
+
link_to(text, admin_course_path(entity.id))
|
18
|
+
end
|
19
|
+
|
20
|
+
def course_categories_for_select
|
21
|
+
options = []
|
22
|
+
CourseCategory.for_tree.each do |category|
|
23
|
+
options << [category.name, category.id]
|
24
|
+
if category.child_categories.any?
|
25
|
+
CourseCategory.for_tree(category.id).each do |subcategory|
|
26
|
+
options << ["-#{subcategory.name}", subcategory.id]
|
27
|
+
if subcategory.child_categories.any?
|
28
|
+
CourseCategory.for_tree(subcategory.id).each do |deep_category|
|
29
|
+
options << ["--#{deep_category.name}", deep_category.id]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
options
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [Course] entity
|
39
|
+
# @param [String] text
|
40
|
+
# @param [Hash] options
|
41
|
+
def course_link(entity, text = entity.title, options = {})
|
42
|
+
link_to(text, course_path(entity.id), options)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Course image preview for displaying in "administrative" lists
|
46
|
+
#
|
47
|
+
# @param [Course] entity
|
48
|
+
def course_image_preview(entity)
|
49
|
+
return '' if entity.image.blank?
|
50
|
+
|
51
|
+
versions = "#{entity.image.preview_2x.url} 2x"
|
52
|
+
image_tag(entity.image.preview.url, alt: entity.title, srcset: versions)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Small course image for displaying in lists of courses and feeds
|
56
|
+
#
|
57
|
+
# @param [Course] entity
|
58
|
+
# @param [Hash] add_options
|
59
|
+
def course_image_small(entity, add_options = {})
|
60
|
+
return '' if entity.image.blank?
|
61
|
+
|
62
|
+
alt_text = entity.image_alt_text.to_s
|
63
|
+
versions = "#{entity.image.medium.url} 2x"
|
64
|
+
options = { alt: alt_text, srcset: versions }.merge(add_options)
|
65
|
+
image_tag(entity.image.small.url, options)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Medium course image for displaying on course page
|
69
|
+
#
|
70
|
+
# @param [Course] entity
|
71
|
+
# @param [Hash] add_options
|
72
|
+
def course_image_medium(entity, add_options = {})
|
73
|
+
return '' if entity.image.blank?
|
74
|
+
|
75
|
+
alt_text = entity.image_alt_text.to_s
|
76
|
+
versions = "#{entity.image.big.url} 2x"
|
77
|
+
options = { alt: alt_text, srcset: versions }.merge(add_options)
|
78
|
+
image_tag(entity.image.medium.url, options)
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
class Course < ApplicationRecord
|
2
|
+
include HasOwner
|
3
|
+
include Toggleable
|
4
|
+
|
5
|
+
PER_PAGE = 24
|
6
|
+
|
7
|
+
DESCRIPTION_LIMIT = 50000
|
8
|
+
DURATION_LIMIT = 50
|
9
|
+
LEAD_LIMIT = 1000
|
10
|
+
META_LIMIT = 255
|
11
|
+
PRIORITY_RANGE = (1..999)
|
12
|
+
SLUG_LIMIT = 250
|
13
|
+
SLUG_PATTERN = /\A[a-z][-0-9a-z]*[0-9a-z]\z/i
|
14
|
+
SLUG_PATTERN_HTML = '^[a-zA-Z][-0-9a-zA-Z]*[0-9a-zA-Z]$'
|
15
|
+
TITLE_LIMIT = 250
|
16
|
+
|
17
|
+
toggleable :visible, :highlight, :online
|
18
|
+
|
19
|
+
mount_uploader :image, CourseImageUploader
|
20
|
+
|
21
|
+
belongs_to :course_category, counter_cache: true
|
22
|
+
belongs_to :user, optional: true
|
23
|
+
belongs_to :agent, optional: true
|
24
|
+
has_many :course_course_tags, dependent: :destroy
|
25
|
+
has_many :course_tags, through: :course_course_tags
|
26
|
+
|
27
|
+
after_initialize :set_next_priority
|
28
|
+
before_validation { self.slug = Canonizer.transliterate(title.to_s) if slug.blank? }
|
29
|
+
before_validation { self.slug = slug.to_s.downcase }
|
30
|
+
before_validation :normalize_priority
|
31
|
+
|
32
|
+
validates_presence_of :title, :slug
|
33
|
+
validates_uniqueness_of :title
|
34
|
+
validates_uniqueness_of :slug
|
35
|
+
validates_length_of :title, maximum: TITLE_LIMIT
|
36
|
+
validates_length_of :subtitle, maximum: TITLE_LIMIT
|
37
|
+
validates_length_of :slug, maximum: SLUG_LIMIT
|
38
|
+
validates_format_of :slug, with: SLUG_PATTERN
|
39
|
+
validates_length_of :image_alt_text, maximum: META_LIMIT
|
40
|
+
validates_length_of :meta_title, maximum: META_LIMIT
|
41
|
+
validates_length_of :meta_keywords, maximum: META_LIMIT
|
42
|
+
validates_length_of :meta_description, maximum: META_LIMIT
|
43
|
+
validates_length_of :lead, maximum: LEAD_LIMIT
|
44
|
+
validates_length_of :description, maximum: DESCRIPTION_LIMIT
|
45
|
+
validates_length_of :duration, maximum: DURATION_LIMIT
|
46
|
+
validates_numericality_of :price, greater_than_or_equal_to: 0, allow_blank: true
|
47
|
+
validates_numericality_of :special_price, greater_than_or_equal_to: 0, allow_blank: true
|
48
|
+
|
49
|
+
scope :ordered_by_priority, -> { order 'priority asc, title asc' }
|
50
|
+
scope :visible, -> { where(visible: true, deleted: false) }
|
51
|
+
scope :for_tree, ->(course_category_id = nil) { siblings(course_category_id).ordered_by_priority }
|
52
|
+
scope :siblings, ->(course_category_id) { where(course_category_id: course_category_id) }
|
53
|
+
scope :list_for_visitors, -> { visible.ordered_by_priority }
|
54
|
+
|
55
|
+
# @param [Integer] page
|
56
|
+
def self.page_for_administration(page = 1)
|
57
|
+
ordered_by_priority.page(page).per(PER_PAGE)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param [Integer] page
|
61
|
+
def self.page_for_visitors(page = 1)
|
62
|
+
list_for_visitors.page(page).per(PER_PAGE)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.entity_parameters
|
66
|
+
texts = %i(title subtitle slug lead description duration)
|
67
|
+
decoration = %i(price special_price special_price_end image image_alt_text)
|
68
|
+
meta_texts = %i(meta_title meta_keywords meta_description metadata)
|
69
|
+
meta_data = %i(course_category_id priority visible highlight online)
|
70
|
+
|
71
|
+
texts + decoration + meta_texts + meta_data
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param [User] user
|
75
|
+
def editable_by?(user)
|
76
|
+
owned_by?(user) || UserPrivilege.user_has_privilege?(user, :chief_course_manager)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param [User] user
|
80
|
+
def lockable_by?(user)
|
81
|
+
UserPrivilege.user_has_privilege?(user, :chief_course_manager)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param [Integer] delta
|
85
|
+
def change_priority(delta)
|
86
|
+
new_priority = priority + delta
|
87
|
+
criteria = { course_category_id: course_category_id, priority: new_priority }
|
88
|
+
adjacent = self.class.find_by(criteria)
|
89
|
+
if adjacent.is_a?(self.class) && (adjacent.id != id)
|
90
|
+
adjacent.update!(priority: priority)
|
91
|
+
end
|
92
|
+
self.update(priority: new_priority)
|
93
|
+
|
94
|
+
self.class.for_tree(course_category_id).map { |e| [e.id, e.priority] }.to_h
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def set_next_priority
|
100
|
+
if id.nil? && priority == 1
|
101
|
+
self.priority = self.class.siblings(course_category_id).maximum(:priority).to_i + 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def normalize_priority
|
106
|
+
self.priority = PRIORITY_RANGE.first if priority < PRIORITY_RANGE.first
|
107
|
+
self.priority = PRIORITY_RANGE.last if priority > PRIORITY_RANGE.last
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
class CourseCategory < ApplicationRecord
|
2
|
+
include Toggleable
|
3
|
+
|
4
|
+
PRIORITY_RANGE = (1..500)
|
5
|
+
NAME_LIMIT = 50
|
6
|
+
SLUG_LIMIT = 50
|
7
|
+
SLUG_PATTERN = /\A[a-z][-0-9a-z]*[0-9a-z]\z/i
|
8
|
+
SLUG_PATTERN_HTML = '^[a-zA-Z][-0-9a-zA-Z]*[0-9a-zA-Z]$'
|
9
|
+
|
10
|
+
toggleable :visible
|
11
|
+
|
12
|
+
mount_uploader :image, CourseImageUploader
|
13
|
+
|
14
|
+
belongs_to :parent, class_name: CourseCategory.to_s, optional: true, touch: true
|
15
|
+
has_many :child_categories, class_name: CourseCategory.to_s, foreign_key: :parent_id, dependent: :destroy
|
16
|
+
has_many :courses, dependent: :destroy
|
17
|
+
|
18
|
+
after_initialize :set_next_priority
|
19
|
+
before_validation { self.slug = Canonizer.transliterate(name.to_s) if slug.blank? }
|
20
|
+
before_validation { self.slug = slug.to_s.downcase }
|
21
|
+
before_validation :generate_long_slug
|
22
|
+
before_validation :normalize_priority
|
23
|
+
before_save { self.children_cache.uniq! }
|
24
|
+
after_create :cache_parents!
|
25
|
+
after_save { parent.cache_children! unless parent.nil? }
|
26
|
+
|
27
|
+
validates_presence_of :name, :slug
|
28
|
+
validates_uniqueness_of :name, scope: [:parent_id]
|
29
|
+
validates_uniqueness_of :slug, scope: [:parent_id]
|
30
|
+
validates_length_of :name, maximum: NAME_LIMIT
|
31
|
+
validates_length_of :slug, maximum: SLUG_LIMIT
|
32
|
+
validates_format_of :slug, with: SLUG_PATTERN
|
33
|
+
|
34
|
+
scope :ordered_by_priority, -> { order 'priority asc, name asc' }
|
35
|
+
scope :visible, -> { where(visible: true, deleted: false) }
|
36
|
+
scope :for_tree, ->(parent_id = nil) { siblings(parent_id).ordered_by_priority }
|
37
|
+
scope :siblings, ->(parent_id) { where(parent_id: parent_id) }
|
38
|
+
|
39
|
+
def self.entity_parameters
|
40
|
+
%i(image name slug priority visible)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.creation_parameters
|
44
|
+
entity_parameters + %i(parent_id)
|
45
|
+
end
|
46
|
+
|
47
|
+
def full_title
|
48
|
+
(parents.map { |parent| parent.name } + [name]).join ' / '
|
49
|
+
end
|
50
|
+
|
51
|
+
def depth
|
52
|
+
parent_ids.count
|
53
|
+
end
|
54
|
+
|
55
|
+
def parent_ids
|
56
|
+
parents_cache.split(',').compact
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Array<Integer>]
|
60
|
+
def branch_ids
|
61
|
+
parents_cache.split(',').map(&:to_i).reject { |i| i < 1 }.uniq + [id]
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Array<Integer>]
|
65
|
+
def subbranch_ids
|
66
|
+
[id] + children_cache
|
67
|
+
end
|
68
|
+
|
69
|
+
def image_alt_text
|
70
|
+
name
|
71
|
+
end
|
72
|
+
|
73
|
+
def title
|
74
|
+
name
|
75
|
+
end
|
76
|
+
|
77
|
+
def parents
|
78
|
+
return [] if parents_cache.blank?
|
79
|
+
CourseCategory.where(id: parent_ids).order('id asc')
|
80
|
+
end
|
81
|
+
|
82
|
+
def cache_parents!
|
83
|
+
return if parent.nil?
|
84
|
+
self.parents_cache = "#{parent.parents_cache},#{parent_id}".gsub(/\A,/, '')
|
85
|
+
save!
|
86
|
+
end
|
87
|
+
|
88
|
+
def cache_children!
|
89
|
+
child_categories.order('id asc').each do |child|
|
90
|
+
self.children_cache += [child.id] + child.children_cache
|
91
|
+
end
|
92
|
+
save!
|
93
|
+
end
|
94
|
+
|
95
|
+
def can_be_deleted?
|
96
|
+
child_categories.count < 1
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param [Course] course
|
100
|
+
def course?(course)
|
101
|
+
course.course_category == self
|
102
|
+
end
|
103
|
+
|
104
|
+
# @param [Integer] delta
|
105
|
+
def change_priority(delta)
|
106
|
+
new_priority = priority + delta
|
107
|
+
criteria = { parent_id: parent_id, priority: new_priority }
|
108
|
+
adjacent = self.class.find_by(criteria)
|
109
|
+
if adjacent.is_a?(self.class) && (adjacent.id != id)
|
110
|
+
adjacent.update!(priority: priority)
|
111
|
+
end
|
112
|
+
self.update(priority: new_priority)
|
113
|
+
|
114
|
+
self.class.for_tree(parent_id).map { |e| [e.id, e.priority] }.to_h
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def set_next_priority
|
120
|
+
if id.nil? && priority == 1
|
121
|
+
self.priority = self.class.siblings(parent_id).maximum(:priority).to_i + 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def normalize_priority
|
126
|
+
self.priority = PRIORITY_RANGE.first if priority < PRIORITY_RANGE.first
|
127
|
+
self.priority = PRIORITY_RANGE.last if priority > PRIORITY_RANGE.last
|
128
|
+
end
|
129
|
+
|
130
|
+
def generate_long_slug
|
131
|
+
if parent.nil?
|
132
|
+
self.long_slug = slug
|
133
|
+
else
|
134
|
+
self.long_slug = "#{parent.long_slug}_#{slug}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CourseCourseTag < ApplicationRecord
|
2
|
+
after_create :update_course_tags_cache
|
3
|
+
after_destroy :update_course_tags_cache
|
4
|
+
|
5
|
+
belongs_to :course
|
6
|
+
belongs_to :course_tag, counter_cache: :courses_count
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def update_course_tags_cache
|
11
|
+
course.update tags_cache: course.course_tags.ordered_by_name.map(&:name)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class CourseTag < ApplicationRecord
|
2
|
+
include RequiredUniqueName
|
3
|
+
include RequiredUniqueSlug
|
4
|
+
include Toggleable
|
5
|
+
|
6
|
+
PER_PAGE = 20
|
7
|
+
NAME_LIMIT = 50
|
8
|
+
SLUG_LIMIT = 50
|
9
|
+
SLUG_PATTERN = /\A[a-z][-0-9a-z]*[0-9a-z]\z/i
|
10
|
+
SLUG_PATTERN_HTML = '^[a-zA-Z][-0-9a-zA-Z]*[0-9a-zA-Z]$'
|
11
|
+
|
12
|
+
toggleable :visible
|
13
|
+
|
14
|
+
mount_uploader :image, CourseImageUploader
|
15
|
+
|
16
|
+
has_many :course_course_tags, dependent: :destroy
|
17
|
+
has_many :courses, through: :course_course_tags
|
18
|
+
|
19
|
+
before_validation { self.slug = Canonizer.transliterate(name.to_s) if slug.blank? }
|
20
|
+
before_validation { self.slug = slug.to_s.downcase }
|
21
|
+
validates_length_of :name, maximum: NAME_LIMIT
|
22
|
+
validates_length_of :slug, maximum: SLUG_LIMIT
|
23
|
+
validates_format_of :slug, with: SLUG_PATTERN
|
24
|
+
|
25
|
+
scope :visible, -> { where(visible: true) }
|
26
|
+
scope :list_for_visitors, -> { visible.ordered_by_name }
|
27
|
+
|
28
|
+
# @param [Integer] page
|
29
|
+
def self.page_for_administration(page = 1)
|
30
|
+
ordered_by_name.page(page).per(PER_PAGE)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [Integer] page
|
34
|
+
def self.page_for_visitors(page = 1)
|
35
|
+
list_for_visitors.page(page).per(PER_PAGE)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.entity_parameters
|
39
|
+
%i(visible image name slug)
|
40
|
+
end
|
41
|
+
|
42
|
+
def image_alt_text
|
43
|
+
name
|
44
|
+
end
|
45
|
+
|
46
|
+
def title
|
47
|
+
name
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class CourseImageUploader < CarrierWave::Uploader::Base
|
2
|
+
include CarrierWave::MiniMagick
|
3
|
+
include CarrierWave::BombShelter
|
4
|
+
|
5
|
+
storage :file
|
6
|
+
|
7
|
+
def max_pixel_dimensions
|
8
|
+
[3840, 3840]
|
9
|
+
end
|
10
|
+
|
11
|
+
def store_dir
|
12
|
+
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id/10000.floor}/#{model.id/100.floor}/#{model.id}"
|
13
|
+
end
|
14
|
+
|
15
|
+
process :auto_orient
|
16
|
+
|
17
|
+
def auto_orient
|
18
|
+
manipulate! do |image|
|
19
|
+
image.tap(&:auto_orient)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
version :big do
|
24
|
+
resize_to_fit(1280, 1280)
|
25
|
+
end
|
26
|
+
|
27
|
+
version :medium, from_version: :big do
|
28
|
+
resize_to_fit(640, 640)
|
29
|
+
end
|
30
|
+
|
31
|
+
version :small, from_version: :medium do
|
32
|
+
resize_to_fit(320, 320)
|
33
|
+
end
|
34
|
+
|
35
|
+
version :preview_2x, from_version: :small do
|
36
|
+
resize_to_fit(160, 160)
|
37
|
+
end
|
38
|
+
|
39
|
+
version :preview, from_version: :preview_2x do
|
40
|
+
resize_to_fit(80, 80)
|
41
|
+
end
|
42
|
+
|
43
|
+
def extension_white_list
|
44
|
+
%w(jpg jpeg png)
|
45
|
+
end
|
46
|
+
end
|