aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuc Donnet2017-09-12 14:46:35 +0200
committerGitHub2017-09-12 14:46:35 +0200
commit4aa19d931f8585c061dd3da49e31b2ddbbb1bf6b (patch)
tree49e5b796e86f4430cdbf098229490d4ed98e0abb
parent38befb74289521600564477cdbabd53b372550a5 (diff)
parent9810dd389cff0bd5dbda26e46806f62d28410ff4 (diff)
downloadchouette-core-4aa19d931f8585c061dd3da49e31b2ddbbb1bf6b.tar.bz2
Merge pull request #63 from af83/4189-duplicate-route
4189 duplicate route
-rw-r--r--app/controllers/routes_controller.rb10
-rw-r--r--app/decorators/route_decorator.rb12
-rw-r--r--app/models/chouette/route.rb27
-rw-r--r--app/models/chouette/stop_point.rb15
-rw-r--r--app/models/stop_area_copy.rb2
-rw-r--r--app/policies/route_policy.rb4
-rw-r--r--config/locales/routes.en.yml2
-rw-r--r--config/locales/routes.fr.yml2
-rw-r--r--config/routes.rb1
-rw-r--r--spec/controllers/routes_controller_spec.rb51
-rw-r--r--spec/models/chouette/route/route_duplication_spec.rb52
-rw-r--r--spec/models/chouette/stop_point_spec.rb19
-rw-r--r--spec/policies/route_policy_spec.rb4
-rw-r--r--spec/routing/routes_routing_spec.rb12
-rw-r--r--spec/support/helpers/model_compare_helpers.rb15
15 files changed, 195 insertions, 33 deletions
diff --git a/app/controllers/routes_controller.rb b/app/controllers/routes_controller.rb
index 7ba2c1a58..04f63c112 100644
--- a/app/controllers/routes_controller.rb
+++ b/app/controllers/routes_controller.rb
@@ -69,11 +69,11 @@ class RoutesController < ChouetteController
end
end
- # def update
- # update! do |success, failure|
- # success.html { redirect_to referential_line_path(@referential,@line) }
- # end
- # end
+ def duplicate
+ route = Chouette::Route.find(params[:id]).duplicate
+ redirect_to edit_referential_line_route_path(@referential, route.line, route)
+ end
+
protected
alias_method :route, :resource
diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb
index 484c3db04..46cb6cd5f 100644
--- a/app/decorators/route_decorator.rb
+++ b/app/decorators/route_decorator.rb
@@ -58,6 +58,18 @@ class RouteDecorator < Draper::Decorator
)
end
+ if h.policy(object).duplicate?
+ links << Link.new(
+ content: h.t('routes.duplicate.title'),
+ href: h.duplicate_referential_line_route_path(
+ context[:referential],
+ context[:line],
+ object
+ ),
+ method: :post
+ )
+ end
+
links
end
end
diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb
index e8c7755c7..49493d5b5 100644
--- a/app/models/chouette/route.rb
+++ b/app/models/chouette/route.rb
@@ -16,6 +16,7 @@ class Chouette::Route < Chouette::TridentActiveRecord
end
belongs_to :line
+ belongs_to :opposite_route, :class_name => 'Chouette::Route', :foreign_key => :opposite_route_id
has_many :routing_constraint_zones
has_many :journey_patterns, :dependent => :destroy
@@ -30,7 +31,6 @@ class Chouette::Route < Chouette::TridentActiveRecord
Chouette::Route.vehicle_journeys_timeless(proxy_association.owner.journey_patterns.pluck( :departure_stop_point_id))
end
end
- belongs_to :opposite_route, :class_name => 'Chouette::Route', :foreign_key => :opposite_route_id
has_many :stop_points, -> { order("position") }, :dependent => :destroy do
def find_by_stop_area(stop_area)
stop_area_ids = Integer === stop_area ? [stop_area] : (stop_area.children_in_depth + [stop_area]).map(&:id)
@@ -56,12 +56,13 @@ class Chouette::Route < Chouette::TridentActiveRecord
end
has_many :stop_areas, -> { order('stop_points.position ASC') }, :through => :stop_points do
def between(departure, arrival)
- departure, arrival = [departure, arrival].collect do |endpoint|
+ departure, arrival = [departure, arrival].map do |endpoint|
String === endpoint ? Chouette::StopArea.find_by_objectid(endpoint) : endpoint
end
proxy_owner.stop_points.between(departure, arrival).includes(:stop_area).collect(&:stop_area)
end
end
+
accepts_nested_attributes_for :stop_points, :allow_destroy => :true
validates_presence_of :name
@@ -75,6 +76,28 @@ class Chouette::Route < Chouette::TridentActiveRecord
after_commit :journey_patterns_control_route_sections
+ def duplicate
+ overrides = {
+ 'opposite_route_id' => nil
+ }
+ keys_for_create = attributes.keys - %w{id objectid created_at updated_at}
+ atts_for_create = attributes
+ .slice(*keys_for_create)
+ .merge(overrides)
+ new_route = self.class.create!(atts_for_create)
+ duplicate_stop_points(for_route: new_route)
+ new_route
+ end
+
+ def duplicate_stop_points(for_route:)
+ stop_points.each(&duplicate_stop_point(for_route: for_route))
+ end
+ def duplicate_stop_point(for_route:)
+ -> stop_point do
+ stop_point.duplicate(for_route: for_route)
+ end
+ end
+
def local_id
"IBOO-#{self.referential.id}-#{self.line.objectid.local_id}-#{self.id}"
end
diff --git a/app/models/chouette/stop_point.rb b/app/models/chouette/stop_point.rb
index 8fe79dc0c..89c492b91 100644
--- a/app/models/chouette/stop_point.rb
+++ b/app/models/chouette/stop_point.rb
@@ -20,6 +20,11 @@ module Chouette
validates_presence_of :stop_area
validate :stop_area_id_validation
+ def stop_area_id_validation
+ if stop_area_id.nil?
+ errors.add(:stop_area_id, I18n.t("stop_areas.errors.empty"))
+ end
+ end
scope :default_order, -> { order("position") }
@@ -34,10 +39,12 @@ module Chouette
end
end
- def stop_area_id_validation
- if stop_area_id.nil?
- errors.add(:stop_area_id, I18n.t("stop_areas.errors.empty"))
- end
+ def duplicate(for_route:)
+ keys_for_create = attributes.keys - %w{id objectid created_at updated_at}
+ atts_for_create = attributes
+ .slice(*keys_for_create)
+ .merge('route_id' => for_route.id)
+ self.class.create!(atts_for_create)
end
def self.area_candidates
diff --git a/app/models/stop_area_copy.rb b/app/models/stop_area_copy.rb
index 0fa56ff68..d3eb78557 100644
--- a/app/models/stop_area_copy.rb
+++ b/app/models/stop_area_copy.rb
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
class StopAreaCopy
include ActiveModel::Validations
include ActiveModel::Conversion
diff --git a/app/policies/route_policy.rb b/app/policies/route_policy.rb
index 786b0acf4..7e9fe251a 100644
--- a/app/policies/route_policy.rb
+++ b/app/policies/route_policy.rb
@@ -16,4 +16,8 @@ class RoutePolicy < ApplicationPolicy
def update?
!archived? && organisation_match? && user.has_permission?('routes.update')
end
+
+ def duplicate?
+ create?
+ end
end
diff --git a/config/locales/routes.en.yml b/config/locales/routes.en.yml
index 3099d4ab1..e94adf490 100644
--- a/config/locales/routes.en.yml
+++ b/config/locales/routes.en.yml
@@ -32,6 +32,8 @@ en:
stop_area_name: "Stop area name"
for_boarding: "Boarding"
for_alighting: "Alighting"
+ duplicate:
+ title: "Duplicate route"
route:
no_journey_pattern: "No Journey pattern"
wayback:
diff --git a/config/locales/routes.fr.yml b/config/locales/routes.fr.yml
index 0af2832a2..a494e60ec 100644
--- a/config/locales/routes.fr.yml
+++ b/config/locales/routes.fr.yml
@@ -32,6 +32,8 @@ fr:
stop_area_name: "Nom de l'arrêt"
for_boarding: "Montée"
for_alighting: "Descente"
+ duplicate:
+ title: "Dupliquer l'itinéraire"
route:
no_journey_pattern: "Pas de mission"
wayback:
diff --git a/config/routes.rb b/config/routes.rb
index fa892c908..8f8989cab 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -117,6 +117,7 @@ ChouetteIhm::Application.routes.draw do
member do
get 'edit_boarding_alighting'
put 'save_boarding_alighting'
+ post 'duplicate', to: 'routes#duplicate'
end
resource :journey_patterns_collection, :only => [:show, :update]
resources :journey_patterns do
diff --git a/spec/controllers/routes_controller_spec.rb b/spec/controllers/routes_controller_spec.rb
index 000b799db..336f20945 100644
--- a/spec/controllers/routes_controller_spec.rb
+++ b/spec/controllers/routes_controller_spec.rb
@@ -1,7 +1,9 @@
-RSpec.describe RoutesController, :type => :controller do
+Route = Chouette::Route
+
+RSpec.describe RoutesController, type: :controller do
login_user
- let!(:route) { create(:route) }
+ let(:route) { create(:route) }
it { is_expected.to be_kind_of(ChouetteController) }
@@ -10,6 +12,7 @@ RSpec.describe RoutesController, :type => :controller do
# expect(response).to redirect_to( referential_line_path(referential,route.line) )
end
end
+
shared_examples_for "line and referential linked" do
it "assigns route.line as @line" do
expect(assigns[:line]).to eq(route.line)
@@ -19,6 +22,7 @@ RSpec.describe RoutesController, :type => :controller do
expect(assigns[:referential]).to eq(referential)
end
end
+
shared_examples_for "route, line and referential linked" do
it "assigns route as @route" do
expect(assigns[:route]).to eq(route)
@@ -28,8 +32,8 @@ RSpec.describe RoutesController, :type => :controller do
describe "GET /index" do
before(:each) do
- get :index, :line_id => route.line_id,
- :referential_id => referential.id
+ get :index, line_id: route.line_id,
+ referential_id: referential.id
end
it_behaves_like "line and referential linked"
@@ -38,9 +42,9 @@ RSpec.describe RoutesController, :type => :controller do
describe "POST /create" do
before(:each) do
- post :create, :line_id => route.line_id,
- :referential_id => referential.id,
- :route => { :name => "changed"}
+ post :create, line_id: route.line_id,
+ referential_id: referential.id,
+ route: { name: "changed"}
end
it_behaves_like "line and referential linked"
@@ -49,9 +53,9 @@ RSpec.describe RoutesController, :type => :controller do
describe "PUT /update" do
before(:each) do
- put :update, :id => route.id, :line_id => route.line_id,
- :referential_id => referential.id,
- :route => route.attributes
+ put :update, id: route.id, line_id: route.line_id,
+ referential_id: referential.id,
+ route: route.attributes
end
it_behaves_like "route, line and referential linked"
@@ -60,9 +64,9 @@ RSpec.describe RoutesController, :type => :controller do
describe "GET /show" do
before(:each) do
- get :show, :id => route.id,
- :line_id => route.line_id,
- :referential_id => referential.id
+ get :show, id: route.id,
+ line_id: route.line_id,
+ referential_id: referential.id
end
it_behaves_like "route, line and referential linked"
@@ -71,11 +75,22 @@ RSpec.describe RoutesController, :type => :controller do
expect(assigns[:map]).to be_an_instance_of(RouteMap)
expect(assigns[:map].route).to eq(route)
end
-
- #it "assigns route.stop_points.paginate(:page => nil) as @stop_points" do
- # expect(assigns[:stop_points]).to eq(route.stop_points.paginate(:page => nil))
- #end
end
-end
+ describe "POST /duplicate" do
+ let!( :route_prime ){ route }
+
+ it "creates a new route" do
+ expect do
+ post :duplicate,
+ referential_id: route.line.line_referential_id,
+ line_id: route.line_id,
+ id: route.id
+ end.to change { Route.count }.by(1)
+
+ expect(Route.last.name).to eq(route.name)
+ expect(Route.last.published_name).to eq(route.published_name)
+ end
+ end
+end
diff --git a/spec/models/chouette/route/route_duplication_spec.rb b/spec/models/chouette/route/route_duplication_spec.rb
new file mode 100644
index 000000000..6645b909f
--- /dev/null
+++ b/spec/models/chouette/route/route_duplication_spec.rb
@@ -0,0 +1,52 @@
+# From Chouette import what we need ™
+Route = Chouette::Route
+StopArea = Chouette::StopArea
+StopPoint = Chouette::StopPoint
+
+RSpec.describe Route do
+
+ let!( :route ){ create :route }
+
+ context '#duplicate' do
+ describe 'properties' do
+ it 'same attribute values' do
+ route.duplicate
+ expect( values_for_create(Route.last, except: %w{objectid}) ).to eq( values_for_create( route, except: %w{objectid} ) )
+ end
+ it 'and others cannot' do
+ expect{ route.duplicate name: 'YAN', line_id: 42 }.to raise_error(ArgumentError)
+ end
+ it 'same associated stop_areeas' do
+ expect( route.duplicate.stop_areas.pluck(:id) ).to eq(route.stop_areas.pluck(:id))
+ end
+ end
+
+ describe 'side_effects' do
+ it {
+ expect{ route.duplicate }.to change{Route.count}.by(1)
+ }
+ it 'duplicates its stop points' do
+ expect{ route.duplicate }.to change{StopPoint.count}.by(route.stop_points.count)
+ end
+ it 'does bot duplicate the stop areas' do
+ expect{ route.duplicate }.not_to change{StopArea.count}
+ end
+ end
+
+ describe 'is idempotent, concerning' do
+ let( :first_duplicate ){ route.duplicate }
+ let( :second_duplicate ){ first_duplicate.reload.duplicate }
+
+ it 'the required attributes' do
+ expect( values_for_create(first_duplicate, except: %w{objectid}) ).to eq( values_for_create( second_duplicate, except: %w{objectid} ) )
+ end
+
+ it 'the stop areas' do
+ expect( first_duplicate.stop_areas.pluck(:id) ).to eq( route.stop_areas.pluck(:id) )
+ expect( second_duplicate.stop_areas.pluck(:id) ).to eq( first_duplicate.stop_areas.pluck(:id) )
+ end
+
+ end
+ end
+
+end
diff --git a/spec/models/chouette/stop_point_spec.rb b/spec/models/chouette/stop_point_spec.rb
index 5eae8caf0..329e76a75 100644
--- a/spec/models/chouette/stop_point_spec.rb
+++ b/spec/models/chouette/stop_point_spec.rb
@@ -1,6 +1,7 @@
-require 'spec_helper'
+# From Chouette import what we need ™
+StopPoint = Chouette::StopPoint
-describe Chouette::StopPoint, :type => :model do
+describe StopPoint, :type => :model do
let!(:vehicle_journey) { create(:vehicle_journey)}
subject { Chouette::Route.find( vehicle_journey.route_id).stop_points.first }
@@ -38,4 +39,18 @@ describe Chouette::StopPoint, :type => :model do
expect(jpsp_stop_point_ids(@vehicle.journey_pattern_id)).not_to include(@stop_point.id)
end
end
+
+ describe '#duplicate' do
+ let!( :new_route ){ create :route }
+
+ it 'creates a new instance' do
+ expect{ subject.duplicate(for_route: new_route) }.to change{ StopPoint.count }.by(1)
+ end
+ it 'new instance has a new route' do
+ expect(subject.duplicate(for_route: new_route).route).to eq(new_route)
+ end
+ it 'and old stop_area' do
+ expect(subject.duplicate(for_route: new_route).stop_area).to eq(subject.stop_area)
+ end
+ end
end
diff --git a/spec/policies/route_policy_spec.rb b/spec/policies/route_policy_spec.rb
index 243d85acb..d7edceaef 100644
--- a/spec/policies/route_policy_spec.rb
+++ b/spec/policies/route_policy_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe RoutePolicy, type: :policy do
it_behaves_like 'permitted policy and same organisation', 'routes.create', archived: true
end
+ permissions :duplicate? do
+ it_behaves_like 'permitted policy and same organisation', 'routes.create', archived: true
+ end
+
permissions :destroy? do
it_behaves_like 'permitted policy and same organisation', 'routes.destroy', archived: true
end
diff --git a/spec/routing/routes_routing_spec.rb b/spec/routing/routes_routing_spec.rb
new file mode 100644
index 000000000..311de9f39
--- /dev/null
+++ b/spec/routing/routes_routing_spec.rb
@@ -0,0 +1,12 @@
+RSpec.describe "routes for Routes", type: :routing do
+ context "routes /referentials/:id/lines/:id/routes/:id/duplicate" do
+
+ let( :controller ){ {controller: 'routes', referential_id: ':referential_id', line_id: ':line_id', id: ':id'} }
+
+ it 'with method post to #post_duplicate' do
+ expect(
+ post: '/referentials/:referential_id/lines/:line_id/routes/:id/duplicate'
+ ).to route_to controller.merge(action: 'duplicate')
+ end
+ end
+end
diff --git a/spec/support/helpers/model_compare_helpers.rb b/spec/support/helpers/model_compare_helpers.rb
new file mode 100644
index 000000000..a10892af0
--- /dev/null
+++ b/spec/support/helpers/model_compare_helpers.rb
@@ -0,0 +1,15 @@
+module Support::ModelCompareHelpers
+
+ def values_for_create obj, **overrides
+ except = overrides.delete(:except) || []
+ keys = obj.attributes.keys - except - %w{id created_at updated_at}
+ overrides.inject(obj.attributes.slice(*keys)){ |atts, (k,v)|
+ atts.merge k.to_s => v
+ }
+ end
+
+end
+
+RSpec.configure do | rspec |
+ rspec.include Support::ModelCompareHelpers, type: :model
+end