diff options
| author | Zog | 2018-03-13 09:10:34 +0100 |
|---|---|---|
| committer | Johan Van Ryseghem | 2018-04-04 11:08:20 +0200 |
| commit | f946eb8d72a5601020c420b33225c31b12277be4 (patch) | |
| tree | b4d2287a6f72b6d2d34c15a0076d2fe79d3c6312 | |
| parent | ecf0dd6406576f8a63ee056fab73b05171c3e767 (diff) | |
| download | chouette-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.rb | 13 | ||||
| -rw-r--r-- | app/models/custom_field.rb | 105 | ||||
| -rw-r--r-- | app/uploaders/custom_field_attachment_uploader.rb | 12 | ||||
| -rw-r--r-- | app/views/shared/custom_fields/_attachment.html.slim | 1 | ||||
| -rw-r--r-- | spec/factories/custom_fields.rb | 2 | ||||
| -rw-r--r-- | spec/models/custom_field_spec.rb | 61 |
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 |
