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 | 
