diff options
13 files changed, 236 insertions, 175 deletions
| diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js b/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js index 43c40a4d5..18ed5f889 100644 --- a/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js +++ b/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js @@ -43,75 +43,79 @@ class JourneyPattern extends Component{      )    } +  getErrors(errors) { +    let err = Object.keys(errors).map((key, index) => { +      return ( +        <li key={index} style={{listStyleType: 'disc'}}> +          <strong>{key}</strong> { errors[key] } +        </li> +      ) +    }) + +    return ( +      <ul className="alert alert-danger">{err}</ul> +    ) +  } +    render() {      this.previousCity = undefined      return ( -      <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record')}> +      <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '')}>          {/* Errors */} -        {(this.props.value.errors) && ( -          <ul className='alert alert-danger small' style={{paddingLeft: 30}}> -            {Object.keys(this.props.value.errors).map(function(key, i) { -              return ( -                <li key={i} style={{listStyleType: 'disc'}}> -                  <strong>'{key}'</strong> {this.props.value.errors[key]} -                  </li> -                ) -              })} -            </ul> -          )} +        {/* this.props.value.errors ? this.getErrors(this.props.value.errors) : '' */} -          <div className='th'> -            <div className='strong mb-xs'>{this.props.value.object_id ? actions.humanOID(this.props.value.object_id) : '-'}</div> -            <div>{this.props.value.registration_number}</div> -            <div>{actions.getChecked(this.props.value.stop_points).length} arrêt(s)</div> +        <div className='th'> +          <div className='strong mb-xs'>{this.props.value.object_id ? actions.humanOID(this.props.value.object_id) : '-'}</div> +          <div>{this.props.value.registration_number}</div> +          <div>{actions.getChecked(this.props.value.stop_points).length} arrêt(s)</div> -            <div className={this.props.value.deletable ? 'btn-group disabled' : 'btn-group'}> -              <div -                className={this.props.value.deletable ? 'btn dropdown-toggle disabled' : 'btn dropdown-toggle'} -                data-toggle='dropdown' -                > -                <span className='fa fa-cog'></span> -              </div> -              <ul className='dropdown-menu'> -                <li className={(this.props.value.deletable || this.props.status.policy['journey_patterns.edit'] == false) ? 'disabled' : ''}> -                  <button -                    type='button' -                    onClick={this.props.onOpenEditModal} -                    data-toggle='modal' -                    data-target='#JourneyPatternModal' -                    > -                    Editer +          <div className={this.props.value.deletable ? 'btn-group disabled' : 'btn-group'}> +            <div +              className={this.props.value.deletable ? 'btn dropdown-toggle disabled' : 'btn dropdown-toggle'} +              data-toggle='dropdown' +              > +              <span className='fa fa-cog'></span> +            </div> +            <ul className='dropdown-menu'> +              <li className={(this.props.value.deletable || this.props.status.policy['journey_patterns.edit'] == false) ? 'disabled' : ''}> +                <button +                  type='button' +                  onClick={this.props.onOpenEditModal} +                  data-toggle='modal' +                  data-target='#JourneyPatternModal' +                  > +                  Editer +                </button> +              </li> +              <li className={this.props.value.object_id ? '' : 'disabled'}> +                {this.vehicleJourneyURL(this.props.value.object_id)} +              </li> +              <li className={'delete-action' + ((this.props.status.policy['journey_patterns.edit'] == false)? ' disabled' : '')}> +                <button +                  type='button' +                  disabled={(this.props.status.policy['journey_patterns.edit'] == false)? 'disabled' : ''} +                  onClick={(e) => { +                    e.preventDefault() +                    this.props.onDeleteJourneyPattern(this.props.index)} +                  } +                  > +                    <span className='fa fa-trash'></span>Supprimer                    </button>                  </li> -                <li className={this.props.value.object_id ? '' : 'disabled'}> -                  {this.vehicleJourneyURL(this.props.value.object_id)} -                </li> -                <li className={'delete-action' + ((this.props.status.policy['journey_patterns.edit'] == false)? ' disabled' : '')}> -                  <button -                    type='button' -                    disabled={(this.props.status.policy['journey_patterns.edit'] == false)? 'disabled' : ''} -                    onClick={(e) => { -                      e.preventDefault() -                      this.props.onDeleteJourneyPattern(this.props.index)} -                    } -                    > -                      <span className='fa fa-trash'></span>Supprimer -                    </button> -                  </li> -                </ul> -              </div> +              </ul>              </div> - -            {this.props.value.stop_points.map((stopPoint, i) =>{ -              return ( -                <div key={i} className='td'> -                  {this.cityNameChecker(stopPoint)} -                </div> -              ) -            })}            </div> -        ) + +          {this.props.value.stop_points.map((stopPoint, i) =>{ +            return ( +              <div key={i} className='td'> +                {this.cityNameChecker(stopPoint)} +              </div> +            ) +          })} +        </div> +      )    }  } diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/reducers/status.js b/app/assets/javascripts/es6_browserified/journey_patterns/reducers/status.js index 6241777da..d7ef12d0b 100644 --- a/app/assets/javascripts/es6_browserified/journey_patterns/reducers/status.js +++ b/app/assets/javascripts/es6_browserified/journey_patterns/reducers/status.js @@ -9,6 +9,8 @@ const status = (state = {}, action) => {        return _.assign({}, state, {isFetching: true})      case 'RECEIVE_JOURNEY_PATTERNS':        return _.assign({}, state, {fetchSuccess: true, isFetching: false}) +    case 'RECEIVE_ERRORS': +      return _.assign({}, state, {isFetching: false})      default:        return state    } diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js index a8a92c522..a54131984 100644 --- a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js +++ b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js @@ -61,7 +61,7 @@ const PeriodForm = ({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriod                <div className="nested-fields">                  <div className="wrapper">                    <div> -                    <div className={'form-group date smart_date' + (modal.modalProps.error ? ' has-error' : '')}> +                    <div className={'form-group date ' + (modal.modalProps.error ? ' has-error' : '')}>                        <div className="form-inline">                          <select value={formatNumber(modal.modalProps.begin.day)} onChange={(e) => onUpdatePeriodForm(e, 'begin', 'day')} id="q_validity_period_begin_gteq_3i" className="date required form-control">                            {makeDaysOptions(modal.modalProps.begin.day)} @@ -76,7 +76,7 @@ const PeriodForm = ({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriod                      </div>                    </div>                    <div> -                    <div className={'form-group date smart_date' + (modal.modalProps.error ? ' has-error' : '')}> +                    <div className={'form-group date ' + (modal.modalProps.error ? ' has-error' : '')}>                        <div className="form-inline">                          <select value={formatNumber(modal.modalProps.end.day)} onChange={(e) => onUpdatePeriodForm(e, 'end', 'day')} id="q_validity_period_end_gteq_3i" className="date required form-control">                            {makeDaysOptions(modal.modalProps.end.day)} diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js index e90d2d307..c30f460d8 100644 --- a/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js +++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js @@ -449,12 +449,18 @@ const actions = {      if (parseInt(schedule.departure_time.minute) < 0){        hours = Math.floor(parseInt(schedule.departure_time.minute) / 60)        minutes = (parseInt(schedule.departure_time.minute) % 60) + 60 +      if(minutes == 60){ +        minutes = 0 +      }        schedule.departure_time.minute = actions.simplePad(minutes, 'minute')        schedule.departure_time.hour = parseInt(schedule.departure_time.hour) + hours      }      if (parseInt(schedule.arrival_time.minute) < 0){        hours = Math.floor(parseInt(schedule.arrival_time.minute) / 60)        minutes = (parseInt(schedule.arrival_time.minute) % 60) + 60 +      if(minutes == 60){ +        minutes = 0 +      }        schedule.arrival_time.minute = actions.simplePad(minutes, 'minute')        schedule.arrival_time.hour = parseInt(schedule.arrival_time.hour) + hours      } diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass index 20679a3ba..b991e7b8d 100644 --- a/app/assets/stylesheets/components/_tables.sass +++ b/app/assets/stylesheets/components/_tables.sass @@ -296,7 +296,7 @@          border-right: 1px solid rgba($grey, 0.5)        .th -        > div +        > div:not(.btn-group)            min-height: 19px          > *:first-child diff --git a/app/assets/stylesheets/modules/_jp_collection.sass b/app/assets/stylesheets/modules/_jp_collection.sass index c109fc71a..f579cf87b 100644 --- a/app/assets/stylesheets/modules/_jp_collection.sass +++ b/app/assets/stylesheets/modules/_jp_collection.sass @@ -98,3 +98,33 @@          left: -23px          top: 50%          margin-top: -8px + +  // Errors +  .table-2entries .t2e-item-list +    .t2e-item +      position: relative + +      .th .vj_tt +        display: inline-block +        vertical-align: top + +        + .vj_tt +          margin-left: 5px + +      &.has-error +        &:before +          content: '' +          position: absolute +          top: 0 +          left: 0 +          right: 0 +          bottom: 0 +          border: 2px solid $red + +        > .th +          > div:first-child, > div:first-child + div +            color: $red + +        // Reset default behaviour +        .form-control +          border-color: #ccc diff --git a/app/controllers/routing_constraint_zones_controller.rb b/app/controllers/routing_constraint_zones_controller.rb index 9d2fd712c..6fb5348f0 100644 --- a/app/controllers/routing_constraint_zones_controller.rb +++ b/app/controllers/routing_constraint_zones_controller.rb @@ -10,29 +10,39 @@ class RoutingConstraintZonesController < ChouetteController      belongs_to :line, parent_class: Chouette::Line    end -  def index -    @routing_constraint_zones = collection -  end - -  def show -    @routing_constraint_zone = collection.find(params[:id]) -    @routing_constraint_zone = @routing_constraint_zone.decorate(context: { -      referential: @referential, -      line: @line -    }) -  end -    protected    def collection -    @q = resource.routing_constraint_zones.search(params[:q]) +    @q = current_referential.routing_constraint_zones.search(params[:q]) -    if sort_column && sort_direction -      @routing_constraint_zones ||= @q.result(distinct: true).order(sort_column + ' ' + sort_direction) -    else -      @routing_constraint_zones ||= @q.result(distinct: true).order(:name) +    @routing_constraint_zones ||= begin +      if sort_column && sort_direction +        routing_constraint_zones = @q.result(distinct: true).order(sort_column + ' ' + sort_direction) +      else +        routing_constraint_zones = @q.result(distinct: true).order(:name) +      end +      routing_constraint_zones = routing_constraint_zones.paginate(page: params[:page], per_page: 10)      end -    @routing_constraint_zones = @routing_constraint_zones.paginate(page: params[:page], per_page: 10) +  end + +  def resource +    @routing_constraint_zone ||= begin +      routing_constraint_zone = current_referential.routing_constraint_zones.find(params[:id]) +      routing_constraint_zone = routing_constraint_zone.decorate(context: { +        referential: current_referential, +        line: parent.id +        }) +      end +  end + +  def build_resource +    @routing_constraint_zone ||= begin +      routing_constraint_zone = current_referential.routing_constraint_zones.new +      routing_constraint_zone = routing_constraint_zone.decorate(context: { +        referential: current_referential, +        line: parent.id +        }) +      end    end    private @@ -43,11 +53,6 @@ class RoutingConstraintZonesController < ChouetteController      %w[asc desc].include?(params[:direction]) ?  params[:direction] : 'asc'    end -  def resource -    @referential = Referential.find params[:referential_id] -    @line = @referential.lines.find params[:line_id] -  end -    def routing_constraint_zone_params      params.require(:routing_constraint_zone).permit(        :name, diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index c566452f4..cb1d0c5da 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -496,16 +496,25 @@ class Chouette::TimeTable < Chouette::TridentActiveRecord      self.convert_continuous_dates_to_periods    end +  def included_days_in_dates_and_periods +    in_day  = self.dates.select {|d| d.in_out }.map(&:date) +    out_day = self.dates.select {|d| !d.in_out }.map(&:date) + +    in_periods = self.periods.map{|p| (p.period_start..p.period_end).to_a }.flatten +    days = in_periods + in_day +    days -= out_day +    days +  end +    # remove dates form tt which aren't in another_tt    def intersect!(another_tt)      transaction do - -      # transform tt as effective dates and get common ones -      days = another_tt.intersects(self.effective_days) & self.intersects(another_tt.effective_days) +      days = self.included_days_in_dates_and_periods & another_tt.included_days_in_dates_and_periods        self.dates.clear -      days.each {|d| self.dates << Chouette::TimeTableDate.new( :date =>d, :in_out => true)}        self.periods.clear -      self.dates.to_a.sort! { |a,b| a.date <=> b.date} +      days.sort.each do |d| +        self.dates << Chouette::TimeTableDate.new(:date => d, :in_out => true) +      end        self.save!      end      self.convert_continuous_dates_to_periods diff --git a/app/models/referential.rb b/app/models/referential.rb index 0ce325bd6..ed23e2e51 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -114,6 +114,10 @@ class Referential < ActiveRecord::Base      Chouette::RouteSection.all    end +  def routing_constraint_zones +    Chouette::RoutingConstraintZone.all +  end +    after_initialize :define_default_attributes    def define_default_attributes diff --git a/app/views/routing_constraint_zones/_form.html.slim b/app/views/routing_constraint_zones/_form.html.slim index 3d4764ef7..5e5f44269 100644 --- a/app/views/routing_constraint_zones/_form.html.slim +++ b/app/views/routing_constraint_zones/_form.html.slim @@ -3,7 +3,7 @@    .row      .col-lg-12        = form.input :name -      = form.input :route_id, collection: @line.routes.select { |route| route.stop_points.count > 2 }, include_blank: false +      = form.input :route_id, collection: @line.routes, include_blank: false        .separator diff --git a/app/views/routing_constraint_zones/edit.html.slim b/app/views/routing_constraint_zones/edit.html.slim index d81a347e0..589083927 100644 --- a/app/views/routing_constraint_zones/edit.html.slim +++ b/app/views/routing_constraint_zones/edit.html.slim @@ -1,6 +1,6 @@  / PageHeader  = pageheader 'map-marker', -             t('.title'), +             t('.title', routing_constraint_zone: @routing_constraint_zone.name),               '',               t('last_update', time: l(@routing_constraint_zone.updated_at, format: :short)) diff --git a/lib/tasks/referential.rake b/lib/tasks/referential.rake index ce1ded4fc..d27354a40 100644 --- a/lib/tasks/referential.rake +++ b/lib/tasks/referential.rake @@ -39,6 +39,8 @@ namespace :referential do          print "  ✓ Created Route ".green, route.name, "(#{route.id}), ".blue, "Line (#{line.id}) has #{line.routes.count} routes\n"          journey_pattern = Chouette::JourneyPattern.create!(route: route, name: "Journey Pattern #{Faker::Name.unique.name}") +        print "✓ Created JourneyPattern ".green, journey_pattern.name, "(#{journey_pattern.id})".blue, "\n" +          journey_pattern.stop_points = stop_areas.inject([]) { |stop_points, stop_area| stop_points += stop_area.stop_points }          time_tables = [] @@ -46,11 +48,14 @@ namespace :referential do            name = "Test #{Faker::Name.unique.name}"            time_table = Chouette::TimeTable.create!(comment: name, start_date: Date.parse(args[:start_date]) + j.days,                                                     end_date: Date.parse(args[:end_date]) - j.days) +          print "✓ Created TimeTable ".green, time_table.comment, "(#{time_table.id})".blue, "\n"            time_tables << time_table          end          25.times do |j|            vehicle_journey = Chouette::VehicleJourney.create!(journey_pattern: journey_pattern, route: route, number: Faker::Number.unique.number(4), time_tables: time_tables) +          print "✓ Created VehicleJourney ".green, vehicle_journey.number, "(#{vehicle_journey.id})".blue, "\n" +            time = Time.current.at_noon + j.minutes            journey_pattern.stop_points.each_with_index do |stop_point, k|              vehicle_journey.vehicle_journey_at_stops.create!(stop_point: stop_point, arrival_time: time + k.minutes, departure_time: time + k.minutes + 30.seconds) diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb index 76c5def5c..536de873a 100644 --- a/spec/models/chouette/time_table_spec.rb +++ b/spec/models/chouette/time_table_spec.rb @@ -72,6 +72,86 @@ describe Chouette::TimeTable, :type => :model do      end    end +  describe '#intersect! with time_table' do +    let(:another_tt) { create(:time_table) } + +    context 'dates' do +      # Clear periods as we are testing dates +      before do +        subject.periods.clear +        another_tt.periods.clear +      end + +      it 'should keep common dates' do +        days = subject.dates.map(&:date) +        subject.intersect!(another_tt) +        expect(subject.included_days_in_dates_and_periods).to include(*days) +      end + +      it 'should not keep dates who are not in common' do +        # Add 1 year interval, to make sur we have not dates in common +        another_tt.dates.map{|d| d.date = d.date + 1.year } +        subject.intersect!(another_tt) + +        expect(subject.reload.dates).to be_empty +      end +    end + +    context 'periods' do +      let(:another_tt_periods_to_range) { another_tt.periods.map{|p| p.period_start..p.period_end } } +      # Clear dates as we are testing periods +      before do +        subject.dates.clear +        another_tt.dates.clear +      end + +      def create_time_table_periode time_table, start_date, end_date +        create(:time_table_period, time_table: time_table, :period_start => start_date, :period_end => end_date) +      end + +      it 'should keep common dates in periods' do +        subject.intersect!(another_tt) +        expect(subject_periods_to_range).to include(*another_tt_periods_to_range) +      end + +      it 'should build new period with common dates in periods' do +        subject.periods.clear +        another_tt.periods.clear + +        subject.periods << create_time_table_periode(subject, Date.today, Date.today + 10.day) +        another_tt.periods << create_time_table_periode(another_tt, Date.tomorrow, Date.today + 3.day) + +        subject.intersect!(another_tt) +        expected_range = Date.tomorrow..Date.today + 3.day + +        expect(subject_periods_to_range).to include(expected_range) +        expect(subject.periods.count).to eq 1 +      end + +      it 'should not keep dates in periods who are not in common' do +        another_tt.periods.map do |p| +          p.period_start = p.period_start + 1.year +          p.period_end   = p.period_end + 1.year +        end + +        subject.intersect!(another_tt) +        expect(subject.periods).to be_empty +      end + +      context 'with calendar' do +        let(:period_start) { subject.periods[0].period_start } +        let(:period_end)   { subject.periods[0].period_end } +        let(:another_tt)   { create(:calendar, date_ranges: [period_start..period_end]).convert_to_time_table } + +        it 'should keep common dates in periods' do +          subject.intersect!(another_tt) +          expect(subject.reload.periods.count).to eq 1 +          expect(subject_periods_to_range).to include(*another_tt_periods_to_range) +        end +      end +    end +  end +    describe "actualize" do      let(:calendar) { create(:calendar) }      let(:int_day_types) { 508 } @@ -1037,90 +1117,6 @@ end        end    end -  describe "#intersect!" do -    context "timetables have periods with common day_types " do -      before do -        subject.periods.clear -        subject.dates.clear -        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,6)) -        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,6,30), :period_end => Date.new(2014,7,20)) -        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) -        subject.int_day_types = 4|16|32|128 -        another_tt = create(:time_table , :int_day_types => (4|16|64|128) ) -        another_tt.periods.clear -        another_tt.dates.clear -        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,6), :period_end => Date.new(2014,8,12)) -        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,7,15), :period_end => Date.new(2014,7,25)) -        subject.intersect! another_tt -        subject.reload -      end -      it "should have no period" do -        expect(subject.periods.size).to eq(0) -       end -      it "should have date all common days" do -        expect(subject.dates.size).to eq(3) -        expect(subject.dates[0].date).to eq(Date.new(2014,7,16)) -        expect(subject.dates[1].date).to eq(Date.new(2014,7,19)) -        expect(subject.dates[2].date).to eq(Date.new(2014,8,6)) -      end -    end -    context "timetables have periods or dates " do -      before do -        subject.periods.clear -        subject.dates.clear -        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) -        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,17), :in_out => true) -        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => true) -        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,19), :in_out => true) -        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,20), :in_out => true) -        subject.int_day_types = 0 -        another_tt = create(:time_table , :int_day_types => (4|16|64|128) ) -        another_tt.periods.clear -        another_tt.dates.clear -        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,6), :period_end => Date.new(2014,8,12)) -        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,7,17), :period_end => Date.new(2014,7,25)) -        subject.intersect! another_tt -        subject.reload -      end -      it "should have 0 period" do -        expect(subject.periods.size).to eq(0) -      end -      it "should not modify day_types" do -        expect(subject.int_day_types).to eq(0) -      end -      it "should have date reduced for period" do -        expect(subject.dates.size).to eq(2) -        expect(subject.dates[0].date).to eq(Date.new(2014,7,18)) -        expect(subject.dates[1].date).to eq(Date.new(2014,7,19)) -      end -    end -    context "with only periods : intersect timetable have no one day period" do -      before do -        subject.periods.clear -        subject.dates.clear -        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,6)) -        subject.int_day_types = 4|8|16 -        another_tt = create(:time_table , :int_day_types => (4|8|16) ) -        another_tt.periods.clear -        another_tt.dates.clear -        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,6), :period_end => Date.new(2014,8,12)) -        subject.intersect! another_tt -        subject.reload -      end -      it "should have 0 result periods" do -        expect(subject.periods.size).to eq(0) -      end -      it "should not modify day_types" do -        expect(subject.int_day_types).to eq(4|8|16) -      end -      it "should have 1 date " do -        expect(subject.dates.size).to eq(1) -        expect(subject.dates[0].date).to eq(Date.new(2014,8,6)) -      end -    end - -  end -    describe "#disjoin!" do      context "timetables have periods with common day_types " do        before do | 
