diff options
| -rw-r--r-- | app/assets/stylesheets/main/calendars.sass | 4 | ||||
| -rw-r--r-- | app/controllers/calendars_controller.rb | 6 | ||||
| -rw-r--r-- | app/helpers/breadcrumb_helper.rb | 2 | ||||
| -rw-r--r-- | app/models/calendar.rb | 117 | ||||
| -rw-r--r-- | app/views/calendars/_date_fields.html.slim | 4 | ||||
| -rw-r--r-- | app/views/calendars/_date_value_fields.html.slim | 14 | ||||
| -rw-r--r-- | app/views/calendars/_form.html.slim | 29 | ||||
| -rw-r--r-- | app/views/calendars/_period_fields.html.slim | 18 | ||||
| -rw-r--r-- | app/views/calendars/index.html.slim | 7 | ||||
| -rw-r--r-- | app/views/calendars/new.html.slim | 2 | ||||
| -rw-r--r-- | app/views/calendars/show.html.slim | 28 | ||||
| -rw-r--r-- | app/views/referentials/_period_fields.html.slim | 1 | ||||
| -rw-r--r-- | config/locales/calendars.en.yml | 3 | ||||
| -rw-r--r-- | config/locales/calendars.fr.yml | 3 | ||||
| -rw-r--r-- | spec/models/calendar_spec.rb | 36 | 
15 files changed, 218 insertions, 56 deletions
| diff --git a/app/assets/stylesheets/main/calendars.sass b/app/assets/stylesheets/main/calendars.sass index 214cff1b1..298ce2a62 100644 --- a/app/assets/stylesheets/main/calendars.sass +++ b/app/assets/stylesheets/main/calendars.sass @@ -1,6 +1,10 @@  #calendar_form    .btn      margin: 5px +  #delete-btn +    margin-top: 23px +  .well +    padding-left: 50px  #calendar_search_form    button diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index 697d8a507..579a48e1d 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -7,7 +7,7 @@ class CalendarsController < BreadcrumbController    private    def calendar_params -    params.require(:calendar).permit(:id, :name, :short_name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], dates: []) +    params.require(:calendar).permit(:id, :name, :short_name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy])    end    def sort_column @@ -31,10 +31,6 @@ class CalendarsController < BreadcrumbController    def collection      return @calendars if @calendars -    if params[:q] -      params[:q].delete(:shared_eq) if params[:q][:shared_eq] == '' -      params[:q].delete(:short_name_cont) if params[:q][:short_name_cont] == '' -    end      @q = current_organisation.calendars.search(params[:q])      calendars = @q.result diff --git a/app/helpers/breadcrumb_helper.rb b/app/helpers/breadcrumb_helper.rb index da8cf8425..6ff4845f9 100644 --- a/app/helpers/breadcrumb_helper.rb +++ b/app/helpers/breadcrumb_helper.rb @@ -73,7 +73,7 @@ module BreadcrumbHelper    end    def calendar_breadcrumb(action) -    add_breadcrumb I18n.t('calendars.index.title') +    add_breadcrumb I18n.t('calendars.index.title'), calendars_path      add_breadcrumb @calendar.name if %i(show edit).include? action    end diff --git a/app/models/calendar.rb b/app/models/calendar.rb index 9c228ad69..d616bce10 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -3,18 +3,23 @@ class Calendar < ActiveRecord::Base    validates_presence_of :name, :short_name, :organisation    validates_uniqueness_of :short_name -  validate :date_not_in_date_ranges, :dates_uniqueness    after_initialize :init_dates_and_date_ranges    scope :contains_date, ->(date) { where('date ? = any (dates) OR date ? <@ any (date_ranges)', date, date) } +  scope :shared, -> { where(shared: true) } +  scope :by_organisation(org_id), -> { where(organisation_id: org_id) }    def init_dates_and_date_ranges      self.dates ||= []      self.date_ranges ||= []    end +  def self.ransackable_scopes(auth_object = nil) +    [:contains_date] +  end +  private_class_method :ransackable_scopes    class Period      include ActiveAttr::Model @@ -23,7 +28,7 @@ class Calendar < ActiveRecord::Base      attribute :begin, type: Date      attribute :end, type: Date -    validates :begin, :end, presence: true +    validates_presence_of :begin, :end      validate :check_end_greather_than_begin      def check_end_greather_than_begin @@ -105,7 +110,7 @@ class Calendar < ActiveRecord::Base      periods.each do |period|        if period.intersect?(periods) -        period.errors.add(:base, I18n.t('calendars.errors.overlapped_period')) +        period.errors.add(:base, I18n.t('calendars.errors.overlapped_periods'))          periods_are_valid = false        end      end @@ -141,34 +146,110 @@ class Calendar < ActiveRecord::Base    private :clear_periods -  def self.new_from from -    from.dup.tap do |metadata| -      metadata.referential_id = nil +### dates + +  class DateValue +    include ActiveAttr::Model + +    attribute :id, type: Integer +    attribute :value, type: Date + +    validates_presence_of :value + +    def self.from_date(index, date) +      DateValue.new id: index, value: date +    end + +    # Stuff required for coocon +    def new_record? +      !persisted?      end + +    def persisted? +      id.present? +    end + +    def mark_for_destruction +      self._destroy = true +    end + +    attribute :_destroy, type: Boolean +    alias_method :marked_for_destruction?, :_destroy    end -  private -  def date_not_in_date_ranges -    errors.add(:dates, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')) if dates && date_ranges && dates_and_date_ranges_overlap? +  # Required by coocon +  def build_date_value +    DateValue.new    end -  def dates_and_date_ranges_overlap? -    overlap = false -    dates.each do |date| +  def date_values +    @date_values ||= init_date_values +  end + +  def init_date_values +    if dates +      dates.each_with_index.map { |d, index| DateValue.from_date(index, d) } +    else +      [] +    end +  end +  private :init_date_values + +  validate :validate_date_values + +  def validate_date_values +    date_values_are_valid = true + +    unless date_values.all?(&:valid?) +      date_values_are_valid = false +    end + +    date_values.each do |date_value| +      if date_values.count { |d| d.value == date_value.value } > 1 +        date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates')) +        date_values_are_valid = false +      end        date_ranges.each do |date_range| -        overlap = true if date_range.cover? date +        if date_range.cover? date_value.value +          date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')) +          date_values_are_valid = false +        end        end      end -    overlap + +    unless date_values_are_valid +      errors.add(:date_values, :invalid) +    end    end -  def dates_uniqueness -    errors.add(:dates, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates')) if dates && dates.length > dates.uniq.length +  def date_values_attributes=(attributes = {}) +    @date_values = [] +    attributes.each do |index, date_value_attribute| +      date_value = DateValue.new(date_value_attribute.merge(id: index)) +      @date_values << date_value unless date_value.marked_for_destruction? +    end + +    dates_will_change!    end -  def self.ransackable_scopes(auth_object = nil) -    [:contains_date] +  before_validation :fill_dates + +  def fill_dates +    if @date_values +      self.dates = @date_values.map(&:value).compact.sort +    end    end + +  after_save :clear_date_values + +  def clear_date_values +    @date_values = nil +  end + +  private :clear_date_values + +### +  end  class Range diff --git a/app/views/calendars/_date_fields.html.slim b/app/views/calendars/_date_fields.html.slim deleted file mode 100644 index 8fd1b5f14..000000000 --- a/app/views/calendars/_date_fields.html.slim +++ /dev/null @@ -1,4 +0,0 @@ -.nested-fields -  = f.input :date, as: :date, html5: true -  /= link_to_remove_association f, data: { confirm: t('are_you_sure') } do -    span.fa.fa-trash diff --git a/app/views/calendars/_date_value_fields.html.slim b/app/views/calendars/_date_value_fields.html.slim new file mode 100644 index 000000000..3a9cdb7bd --- /dev/null +++ b/app/views/calendars/_date_value_fields.html.slim @@ -0,0 +1,14 @@ +.nested-fields +  - if f.object.errors.has_key? :base +    .row +      .col-lg-12 +        .alert.alert-danger +          - f.object.errors[:base].each do |message| +            p.small = message +  .row +    .col-xs-3 +      = f.input :value, as: :date, html5: true, label: t('simple_form.labels.calendar.date_value') +    .col-xs-1.end.text-right#delete-btn +      = link_to_remove_association f, class: 'btn btn-danger', data: { confirm: t('are_you_sure') } do +        span.fa.fa-trash + diff --git a/app/views/calendars/_form.html.slim b/app/views/calendars/_form.html.slim index e490441ed..1a1172a7b 100644 --- a/app/views/calendars/_form.html.slim +++ b/app/views/calendars/_form.html.slim @@ -4,22 +4,23 @@        = simple_form_for @calendar, html: { class: 'form-horizontal' } do |f|          = f.input :name          = f.input :short_name -        / = f.label :dates -        / = f.simple_fields_for :dates do |date| -        /   = render 'date_fields', f: date -        /   .links -        /     = link_to_add_association '+', f, :dates -        = f.label :date_ranges -        = f.simple_fields_for :periods do |period| -          = render 'period_fields', f: period -        .links -          = link_to_add_association '+', f, :periods - +        .form-group +          = f.label :dates +        .well +          = f.simple_fields_for :date_values do |date_value| +            = render 'date_value_fields', f: date_value +          .links +            = link_to_add_association t('simple_form.labels.calendar.add_a_date'), f, :date_values, class: 'btn btn-primary btn-xs' +        .form-group +          = f.label :date_ranges +        .well +          = f.simple_fields_for :periods do |period| +            = render 'period_fields', f: period +          .links +            = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-primary btn-xs' +        = f.input :shared          .form-actions            = f.button :submit, as: :button, class: 'btn btn-info'            = link_to t('cancel'), calendars_path, class: 'btn btn-default' -        = f.input :shared - -/ TODO : cocoon diff --git a/app/views/calendars/_period_fields.html.slim b/app/views/calendars/_period_fields.html.slim index 50413ef20..024d09de2 100644 --- a/app/views/calendars/_period_fields.html.slim +++ b/app/views/calendars/_period_fields.html.slim @@ -1,6 +1,16 @@  .nested-fields -  = f.input :begin, as: :date, html5: true, label: t('simple_form.labels.calendar.ranges.begin') -  = f.input :end, as: :date, html5: true, label: t('simple_form.labels.calendar.ranges.end') -  = link_to_remove_association f, data: { confirm: t('are_you_sure') } do -    span.fa.fa-trash +  - if f.object.errors.has_key? :base +    .row +      .col-lg-12 +        .alert.alert-danger +          - f.object.errors[:base].each do |message| +            p.small = message +  .row +    .col-xs-4 +      = f.input :begin, as: :date, html5: true, label: t('simple_form.labels.calendar.ranges.begin') +    .col-xs-3 +      = f.input :end, as: :date, html5: true, label: t('simple_form.labels.calendar.ranges.end') +    .col-xs-1.text-right#delete-btn +      = link_to_remove_association f, class: 'btn btn-danger', data: { confirm: t('are_you_sure') } do +        span.fa.fa-trash diff --git a/app/views/calendars/index.html.slim b/app/views/calendars/index.html.slim index a857078a1..9696e3c0b 100644 --- a/app/views/calendars/index.html.slim +++ b/app/views/calendars/index.html.slim @@ -21,3 +21,10 @@  #calendars    = render 'calendars' + +- content_for :sidebar do +  ul.actions +    li +    - if policy(Calendar).create? +      = link_to t('calendars.actions.new'), new_calendar_path, class: 'add' +    br diff --git a/app/views/calendars/new.html.slim b/app/views/calendars/new.html.slim index ccae7ce89..f827e2eb6 100644 --- a/app/views/calendars/new.html.slim +++ b/app/views/calendars/new.html.slim @@ -1,5 +1,3 @@  = title_tag t('.title') -= @calendar.inspect -  = render 'form' diff --git a/app/views/calendars/show.html.slim b/app/views/calendars/show.html.slim index 419cec78e..169d59f57 100644 --- a/app/views/calendars/show.html.slim +++ b/app/views/calendars/show.html.slim @@ -6,12 +6,28 @@ p  p    label => "#{Calendar.human_attribute_name('short_name')} : "    = @calendar.short_name -p -  label => "#{Calendar.human_attribute_name('date_ranges')} : " -  = @calendar.date_ranges -p -  label => "#{Calendar.human_attribute_name('dates')} : " -  = @calendar.dates + +.row +  .col-xs-4 +    p +      label => "#{Calendar.human_attribute_name('dates')} : " +      table.table.table-condensed +        tbody +          - @calendar.dates.each do |date| +            tr +              td= l date +    p +      label => "#{Calendar.human_attribute_name('date_ranges')} : " +      table.table.table-condensed +        thead +          th= t('simple_form.labels.calendar.ranges.begin') +          th= t('simple_form.labels.calendar.ranges.end') +        tbody +          - @calendar.date_ranges.each do |date_range| +            tr +              td= l date_range.begin +              td= l date_range.end +  p    label => "#{Calendar.human_attribute_name('shared')} : "    = @calendar.shared diff --git a/app/views/referentials/_period_fields.html.slim b/app/views/referentials/_period_fields.html.slim index 9d92f92ce..6658cd4aa 100644 --- a/app/views/referentials/_period_fields.html.slim +++ b/app/views/referentials/_period_fields.html.slim @@ -14,3 +14,4 @@      .col-lg-2.col-md-2.col-sm-2.col-xs-2.text-right style='margin-top:23px'        = link_to_remove_association f, class: 'btn btn-danger', data: { confirm: 'Etes-vous sûr(e) ?' } do          span.fa.fa-trash + diff --git a/config/locales/calendars.en.yml b/config/locales/calendars.en.yml index b7ad5df26..fe35f3dc4 100644 --- a/config/locales/calendars.en.yml +++ b/config/locales/calendars.en.yml @@ -38,6 +38,9 @@ en:          dates: Dates          shared: Shared          date_ranges: Date ranges +        date_value: Date +        add_a_date: Add a date +        add_a_date_range: Add a date range          ranges:            begin: Beginning            end: End diff --git a/config/locales/calendars.fr.yml b/config/locales/calendars.fr.yml index 358ae0d46..1f96972c2 100644 --- a/config/locales/calendars.fr.yml +++ b/config/locales/calendars.fr.yml @@ -38,6 +38,9 @@ fr:          dates: Dates          shared: Partagé          date_ranges: Intervalles de dates +        date_value: Date +        add_a_date: Ajouter une date +        add_a_date_range: Ajouter un intervalle de dates          ranges:            begin: Début            end: Fin diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb index 2557cdb93..36981961f 100644 --- a/spec/models/calendar_spec.rb +++ b/spec/models/calendar_spec.rb @@ -15,7 +15,6 @@ RSpec.describe Calendar, :type => :model do        expect {          calendar.save!        }.to raise_error(ActiveRecord::RecordInvalid) -      expect(calendar.errors.messages[:dates]).to eq([I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')])      end      it 'validates that there are no duplicates in dates' do @@ -23,7 +22,6 @@ RSpec.describe Calendar, :type => :model do        expect {          calendar.save!        }.to raise_error(ActiveRecord::RecordInvalid) -      expect(calendar.errors.messages[:dates]).to eq([I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates')])      end    end @@ -112,5 +110,39 @@ RSpec.describe Calendar, :type => :model do      end    end +  describe 'DateValue' do + +    subject { date_value } + +    def date_value(attributes = {}) +      return @date_value if attributes.empty? and @date_value +      Calendar::DateValue.new(attributes).tap do |date_value| +        @date_value = date_value if attributes.empty? +      end +    end + +    it 'should support mark_for_destruction (required by cocoon)' do +      date_value.mark_for_destruction +      expect(date_value).to be_marked_for_destruction +    end + +    it 'should support _destroy attribute (required by coocon)' do +      date_value._destroy = true +      expect(date_value).to be_marked_for_destruction +    end + +    it 'should support new_record? (required by cocoon)' do +      expect(Calendar::DateValue.new).to be_new_record +      expect(date_value(id: 42)).not_to be_new_record +    end + +    it 'should cast value as date attribute' do +      expect(date_value(value: '2017-01-03').value).to eq(Date.new(2017,01,03)) +    end + +    it { is_expected.to validate_presence_of(:value) } + +  end +  end | 
