aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZog2018-03-13 09:10:34 +0100
committerJohan Van Ryseghem2018-04-04 11:08:20 +0200
commitf946eb8d72a5601020c420b33225c31b12277be4 (patch)
treeb4d2287a6f72b6d2d34c15a0076d2fe79d3c6312
parentecf0dd6406576f8a63ee056fab73b05171c3e767 (diff)
downloadchouette-core-f946eb8d72a5601020c420b33225c31b12277be4.tar.bz2
Refs #6089; Adds `attachment` Custom Fields
As well as an helper to render them in a partial
-rw-r--r--app/models/concerns/custom_fields_support.rb13
-rw-r--r--app/models/custom_field.rb105
-rw-r--r--app/uploaders/custom_field_attachment_uploader.rb12
-rw-r--r--app/views/shared/custom_fields/_attachment.html.slim1
-rw-r--r--spec/factories/custom_fields.rb2
-rw-r--r--spec/models/custom_field_spec.rb61
6 files changed, 186 insertions, 8 deletions
diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb
index 6c76bd653..4faff694e 100644
--- a/app/models/concerns/custom_fields_support.rb
+++ b/app/models/concerns/custom_fields_support.rb
@@ -3,6 +3,7 @@ module CustomFieldsSupport
included do
validate :custom_fields_values_are_valid
+ after_initialize :initialize_custom_fields
def self.custom_fields
CustomField.where(resource_type: self.name.split("::").last)
@@ -12,6 +13,18 @@ module CustomFieldsSupport
CustomField::Collection.new self
end
+ def custom_field_values= vals
+ out = {}
+ vals.each do |k, val|
+ out[k] = custom_fields[k].preprocess_value_for_assignment(val)
+ end
+ self.write_attribute :custom_field_values, out
+ end
+
+ def initialize_custom_fields
+ custom_fields.values.each &:initialize_custom_field
+ end
+
def custom_field_value key
(custom_field_values || {})[key.to_s]
end
diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb
index 402df7fa9..62c43f1b4 100644
--- a/app/models/custom_field.rb
+++ b/app/models/custom_field.rb
@@ -2,7 +2,7 @@ class CustomField < ActiveRecord::Base
extend Enumerize
belongs_to :workgroup
- enumerize :field_type, in: %i{list}
+ enumerize :field_type, in: %i{list integer string attachment}
validates :name, uniqueness: {scope: [:resource_type, :workgroup_id]}
validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false}
@@ -22,7 +22,7 @@ class CustomField < ActiveRecord::Base
class Value
def self.new owner, custom_field, value
- field_type = custom_field.options["field_type"]
+ field_type = custom_field.field_type
klass_name = field_type && "CustomField::Value::#{field_type.classify}"
klass = klass_name && const_defined?(klass_name) ? klass_name.constantize : CustomField::Value::Base
klass.new owner, custom_field, value
@@ -37,7 +37,8 @@ class CustomField < ActiveRecord::Base
@validated = false
@valid = false
end
-
+ attr_accessor :owner
+
delegate :code, :name, :field_type, to: :@custom_field
def options
@@ -64,22 +65,116 @@ class CustomField < ActiveRecord::Base
def to_hash
HashWithIndifferentAccess[*%w(code name field_type options value).map{|k| [k, send(k)]}.flatten(1)]
end
+
+ def display_value
+ value
+ end
+
+ def initialize_custom_field
+ end
+
+ def preprocess_value_for_assignment
+ end
+
+ def render_partial
+ ActionView::Base.new(Rails.configuration.paths["app/views"].first).render(
+ :partial => "shared/custom_fields/#{field_type}",
+ :locals => { field: self}
+ )
+ end
end
class Integer < Base
def value
- @raw_value.to_i
+ @raw_value&.to_i
end
def validate
@valid = true
- unless @raw_value =~ /\A\d*\Z/
+ return if @raw_value.is_a?(Fixnum) || @raw_value.is_a?(Float)
+ unless @raw_value.to_s =~ /\A\d*\Z/
@owner.errors.add errors_key, "'#{@raw_value}' is not a valid integer"
@valid = false
end
end
end
+ class List < Integer
+ def validate
+ super
+ return unless value.present?
+ unless value >= 0 && value < options["list_values"].size
+ @owner.errors.add errors_key, "'#{@raw_value}' is not a valid value"
+ @valid = false
+ end
+ end
+
+ def display_value
+ options["list_values"][value]
+ end
+ end
+
+ class Attachment < Base
+ def initialize_custom_field
+ custom_field_code = self.code
+ _attr_name = attr_name
+ _uploader_name = uploader_name
+ owner.send :define_singleton_method, "read_uploader" do |attr|
+ if attr.to_s == _attr_name
+ custom_field_values[custom_field_code]
+ else
+ read_attribute attr
+ end
+ end
+
+ owner.send :define_singleton_method, "write_uploader" do |attr, val|
+ if attr.to_s == _attr_name
+ custom_field_values[custom_field_code] = val
+ else
+ write_attribute attr, val
+ end
+ end
+
+ owner.send :define_singleton_method, "#{_attr_name}_will_change!" do
+ custom_field_values_will_change!
+ end
+
+ _extension_whitelist = options["extension_whitelist"]
+
+ owner.send :define_singleton_method, "#{_uploader_name}_extension_whitelist" do
+ _extension_whitelist
+ end
+
+ unless owner.class.uploaders.has_key? _uploader_name.to_sym
+ owner.class.mount_uploader _uploader_name, CustomFieldAttachmentUploader, mount_on: "custom_field_#{code}_raw_value"
+ end
+ end
+
+ def preprocess_value_for_assignment val
+ owner.send "#{uploader_name}=", val
+ end
+
+ def value
+ owner.send "custom_field_#{code}"
+ end
+
+ def raw_value
+ @raw_value
+ end
+
+ def attr_name
+ "custom_field_#{code}_raw_value"
+ end
+
+ def uploader_name
+ "custom_field_#{code}"
+ end
+
+ def display_value
+ render_partial
+ end
+ end
+
class String < Base
def value
"#{@raw_value}"
diff --git a/app/uploaders/custom_field_attachment_uploader.rb b/app/uploaders/custom_field_attachment_uploader.rb
new file mode 100644
index 000000000..411b65bc3
--- /dev/null
+++ b/app/uploaders/custom_field_attachment_uploader.rb
@@ -0,0 +1,12 @@
+class CustomFieldAttachmentUploader < CarrierWave::Uploader::Base
+
+ storage :file
+
+ def store_dir
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ end
+
+ def extension_whitelist
+ model.send "#{mounted_as}_extension_whitelist"
+ end
+end
diff --git a/app/views/shared/custom_fields/_attachment.html.slim b/app/views/shared/custom_fields/_attachment.html.slim
new file mode 100644
index 000000000..2f25b396a
--- /dev/null
+++ b/app/views/shared/custom_fields/_attachment.html.slim
@@ -0,0 +1 @@
+= link_to I18n.t("custom_fields.#{field.owner.class.name.demodulize.underscore}.#{field.code}.link"), field.value.url
diff --git a/spec/factories/custom_fields.rb b/spec/factories/custom_fields.rb
index 7c43a6147..db7a3823e 100644
--- a/spec/factories/custom_fields.rb
+++ b/spec/factories/custom_fields.rb
@@ -3,7 +3,7 @@ FactoryGirl.define do
code "code"
resource_type "VehicleJourney"
sequence(:name){|n| "custom field ##{n}"}
- field_type "list"
+ field_type "integer"
options( { capacity: "0" } )
end
end
diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb
index b92bcfbdb..d52a29fc8 100644
--- a/spec/models/custom_field_spec.rb
+++ b/spec/models/custom_field_spec.rb
@@ -38,8 +38,35 @@ RSpec.describe CustomField, type: :model do
it { expect(vj.custom_field_value("energy")).to eq(99) }
end
+ context "with a 'list' field_type" do
+ let!(:field){ [create(:custom_field, code: :energy, field_type: 'list', options: {list_values: %w(foo bar baz)})] }
+ let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "1"} }
+ it "should cast the value" do
+ expect(vj.custom_fields[:energy].value).to eq 1
+ expect(vj.custom_fields[:energy].display_value).to eq "bar"
+ end
+
+ it "should validate the value" do
+ {
+ "1" => true,
+ 1 => true,
+ "azerty" => false,
+ "10" => false,
+ 10 => false
+ }.each do |val, valid|
+ vj = build :vehicle_journey, custom_field_values: {energy: val}
+ if valid
+ expect(vj.validate).to be_truthy
+ else
+ expect(vj.validate).to be_falsy
+ expect(vj.errors.messages[:"custom_fields.energy"]).to be_present
+ end
+ end
+ end
+ end
+
context "with an 'integer' field_type" do
- let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'integer'})] }
+ let!(:field){ [create(:custom_field, code: :energy, field_type: 'integer')] }
let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "99"} }
it "should cast the value" do
expect(vj.custom_fields[:energy].value).to eq 99
@@ -47,6 +74,7 @@ RSpec.describe CustomField, type: :model do
it "should validate the value" do
{
+ 99 => true,
"99" => true,
"azerty" => false,
"91a" => false,
@@ -64,10 +92,39 @@ RSpec.describe CustomField, type: :model do
end
context "with a 'string' field_type" do
- let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'string'})] }
+ let!(:field){ [create(:custom_field, code: :energy, field_type: 'string')] }
let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: 99} }
it "should cast the value" do
expect(vj.custom_fields[:energy].value).to eq '99'
end
end
+
+ context "with a 'attachment' field_type" do
+ let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment')] }
+ let( :vj ){ create :vehicle_journey, custom_field_values: {energy: File.open(Rails.root.join('spec', 'fixtures', 'users.json'))} }
+ it "should cast the value" do
+ expect(vj.custom_fields[:energy].value.class).to be CustomFieldAttachmentUploader
+ path = vj.custom_fields[:energy].value.path
+ expect(File.exists?(path)).to be_truthy
+ expect(vj).to receive(:remove_custom_field_energy!).and_call_original
+ vj.destroy
+ vj.run_callbacks(:commit)
+ expect(File.exists?(path)).to be_falsy
+ end
+
+ it "should display a link" do
+ val = vj.custom_fields[:energy].value
+ out = vj.custom_fields[:energy].display_value
+ expect(out).to match(val.url)
+ expect(out).to match(/\<a.*\>/)
+ end
+
+ context "with a whitelist" do
+ let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment', options: {extension_whitelist: %w(zip)})] }
+ it "should validate extension" do
+ expect(build(:vehicle_journey, custom_field_values: {energy: File.open(Rails.root.join('spec', 'fixtures', 'users.json'))})).to_not be_valid
+ expect(build(:vehicle_journey, custom_field_values: {energy: File.open(Rails.root.join('spec', 'fixtures', 'nozip.zip'))})).to be_valid
+ end
+ end
+ end
end