Skip to content

Commit 8f5c4ea

Browse files
authored
Merge branch 'master' into feature/15-parameters
2 parents 2266514 + 0525bc0 commit 8f5c4ea

18 files changed

+153
-29
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Maintainability](https://api.codeclimate.com/v1/badges/580a9cc169de1c21c180/maintainability)](https://codeclimate.com/github/ServiceInnovationLab/RapuTure/maintainability)
44
[![AwesomeCode Status for ServiceInnovationLab/RapuTure](https://awesomecode.io/projects/26aa53d4-bece-44c7-81d7-6c2f7648adec/status)](https://awesomecode.io/repos/ServiceInnovationLab/RapuTure)
55
[![Build Status](https://travis-ci.org/ServiceInnovationLab/RapuTure.svg?branch=master)](https://travis-ci.org/ServiceInnovationLab/RapuTure)
6-
6+
[![Test Coverage](https://api.codeclimate.com/v1/badges/580a9cc169de1c21c180/test_coverage)](https://codeclimate.com/github/ServiceInnovationLab/RapuTure/test_coverage)
77
## Overview
88

99
Rapu Ture is a phrase that means "Exploring the rules/law"

app/controllers/scenarios_controller.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
class ScenariosController < ApplicationController
44
def index
5-
@scenarios = Scenario.all.order(:name)
5+
if params[:variable_id].present?
6+
@variable = Variable.find_by(name: params[:variable_id])
7+
@scenarios = @variable.output_scenarios
8+
else
9+
@scenarios = Scenario.all.order(:name)
10+
end
611
end
712

813
def show

app/models/scenario.rb

+32-5
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,43 @@ def other_input_keys
2323
keys - %w[persons families titled_properties]
2424
end
2525

26-
def parse_variables!
27-
input_keys = get_all_keys(inputs)
28-
output_keys = get_all_keys(outputs)
29-
input_output_keys = input_keys + output_keys
26+
def input_variables
27+
variables.merge(ScenarioVariable.input)
28+
end
3029

31-
self.variables = Variable.where(name: input_output_keys)
30+
def output_variables
31+
variables.merge(ScenarioVariable.output)
32+
end
33+
34+
def parse_variables!
35+
Scenario.transaction do
36+
variables = []
37+
%w[input output].each do |direction|
38+
save_variable_links(direction).each do |v|
39+
variables << v
40+
end
41+
end
42+
# remove associating with any variables we no longer refer to
43+
scenario_variables.where.not(variable: variables).destroy_all
44+
end
3245
end
3346

3447
private
3548

49+
def save_variable_links(direction)
50+
hash_keys = if direction == 'input'
51+
get_all_keys(inputs)
52+
elsif direction == 'output'
53+
get_all_keys(outputs)
54+
else
55+
raise format('invalid direction %<direction>', direction: direction)
56+
end
57+
variables = Variable.where(name: hash_keys)
58+
variables.each do |v|
59+
ScenarioVariable.find_or_create_by! scenario: self, variable: v, direction: direction
60+
end
61+
end
62+
3663
# This could use some refinement
3764
# Currently it is returning all the keys
3865
# Can't just user lower level though because of data structures like:

app/models/scenario_variable.rb

+5
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,9 @@
33
class ScenarioVariable < ApplicationRecord
44
belongs_to :scenario
55
belongs_to :variable
6+
validates :direction, presence: true, inclusion: {
7+
in: %w[input output], message: '%<value>s is not a valid direction'
8+
}
9+
scope :input, -> { where(direction: 'input') }
10+
scope :output, -> { where(direction: 'output') }
611
end

app/models/variable.rb

+8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ class Variable < ApplicationRecord
1313
has_many :scenario_variables, dependent: :destroy
1414
has_many :scenarios, through: :scenario_variables
1515

16+
def input_scenarios
17+
scenarios.merge(ScenarioVariable.input)
18+
end
19+
20+
def output_scenarios
21+
scenarios.merge(ScenarioVariable.output)
22+
end
23+
1624
has_and_belongs_to_many(:variables,
1725
class_name: 'Variable',
1826
join_table: 'links',

app/views/scenarios/_outputs.html.haml

+14-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,20 @@
1818
- value.each do |key, item|
1919
%li.list-group-item
2020
%strong= key
21-
.pull-right= item.to_json
21+
- if item.class == Array
22+
%table.pull-right
23+
- item.each_with_index.map do |array_item, i|
24+
%tr
25+
%th{ scope: 'row' }= scenario.input_persons.keys[i]
26+
%td= array_item.to_json
27+
- elsif item.is_a? Enumerable
28+
%table.pull-right
29+
- item.each do |hash_key, hash_item|
30+
%tr
31+
%th= link_if_variable(hash_key)
32+
%td= hash_item.to_json
33+
- else
34+
.pull-right= item.to_json
2235
- else
2336
.card-body
2437
= link_if_variable(name)

app/views/scenarios/index.html.haml

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
.container
2+
- if @variable.present?
3+
.text-center
4+
%h2.text-center Scenarios calculating #{link_to @variable, @variable}
5+
%p= @variable.description
26
.card-columns
37
- @scenarios.each do |scenario|
48
.card

app/views/scenarios/show.html.haml

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
.container
2-
%h1.display-3= @scenario.name
2+
%h1= @scenario.name
3+
%p
4+
%strong Period:
5+
= @scenario.period
6+
- if @scenario.error_margin.present?
7+
%p
8+
%strong Error Margin:
9+
= @scenario.error_margin
310
.row
411
.col-md-6
512
%h2 Inputs

app/views/variables/show.html.haml

+20-13
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,28 @@
4646
4747
- if @variable.has_formula?
4848
%hr/
49-
%h3=t :formulas
50-
%p
51-
= t :formula_explanation, name: @variable.name
52-
- @variable.spec['formulas'].each do |d, formula|
53-
%h3= d
54-
%p
55-
= t(:formula_for_scenarios_from_date_onwards, formula_date: d)
56-
= link_to 'More info on formulas', 'https://openfisca.org/doc/coding-the-legislation/10_basic_example.html'
57-
%pre= display_formula formula['content']
49+
.card
50+
.card-header
51+
%h3=t :formulas
52+
%p= t :formula_explanation, name: @variable.name
53+
.card-body
54+
- @variable.spec['formulas'].each do |d, formula|
55+
%h3= d
56+
%p
57+
= t(:formula_for_scenarios_from_date_onwards, formula_date: d)
58+
= link_to 'More info on formulas', 'https://openfisca.org/doc/coding-the-legislation/10_basic_example.html'
59+
%pre
60+
%code= display_formula formula['content']
5861
59-
- if @variable.scenarios.size.positive?
62+
- if @variable.output_scenarios.size.positive?
6063
%hr/
61-
%h2 Scenarios (tests) for this variables
62-
- @variable.scenarios.each do |scenario|
63-
%li= link_to scenario.name, scenario
64+
.card
65+
.card-header
66+
%h2 Scenarios calculating this variables
67+
%ul.list-group.list-group-flush
68+
- @variable.output_scenarios.each do |scenario|
69+
%li.list-group-item
70+
= link_to scenario.name, scenario
6471
%hr/
6572
%ul.nav
6673
- if @variable.references.present?

config/routes.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
Rails.application.routes.draw do
44
root to: 'entities#index'
55
resources :entities, only: %i[show index]
6-
resources :variables, only: %i[show index]
6+
resources :variables, only: %i[show index] do
7+
resources :scenarios, only: %i[index]
8+
end
79
resources :scenarios, only: %i[show index]
810
resources :parameters, only: %i[show index]
911
# get "/:page" => "pages#show"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class ScenarioVariableInputOutput < ActiveRecord::Migration[5.2]
2+
def change
3+
add_column :scenario_variables, :direction, :string, required: true
4+
add_index :scenario_variables, [:scenario_id, :variable_id, :direction], unique: true, name: 'scenario_variables_key'
5+
end
6+
end

db/schema.rb

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
create_table "scenario_variables", force: :cascade do |t|
5454
t.bigint "scenario_id"
5555
t.bigint "variable_id"
56+
t.string "direction"
57+
t.index ["scenario_id", "variable_id", "direction"], name: "scenario_variables_key", unique: true
5658
t.index ["scenario_id"], name: "index_scenario_variables_on_scenario_id"
5759
t.index ["variable_id"], name: "index_scenario_variables_on_variable_id"
5860
end

lib/tasks/fetch.rake

+2
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ namespace :fetch do
1515
task entities: :environment do
1616
EntitiesFetchService.fetch_all
1717
end
18+
1819
task parameters: :environment do
1920
ParametersFetchService.fetch_all do |p|
2021
puts p.name
2122
end
2223
end
24+
2325
task scenarios: :environment do
2426
ScenariosFetchService.fetch_all
2527
end

spec/features/percy_spec.rb

+2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@
104104
it 'scenarios#index' do
105105
visit scenarios_path
106106
Percy.snapshot(page, name: 'scenarios#index')
107+
visit variable_scenarios_path(variable_id: variable.name)
108+
Percy.snapshot(page, name: 'variables/scenarios#index')
107109
end
108110

109111
it 'scenarios#show' do

spec/features/scenarios/index_spec.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
name: 'a complicated situation',
1111
inputs: {
1212
'persons' => { 'fulltime_uni_student' => { 'age' => 21, 'is_nz_citizen' => true, 'social_security__is_ordinarily_resident_in_new_zealand' => true, 'student_allowance__is_tertiary_student' => true, 'student_allowance__is_enrolled_fulltime' => true, 'student_allowance__meets_attendance_and_performance_requirements' => true }, 'Parttime_student' => { 'age' => 18, 'is_nz_citizen' => true, 'social_security__is_ordinarily_resident_in_new_zealand' => true, 'student_allowance__is_tertiary_student' => true, 'student_allowance__approved_to_study_parttime' => true }, 'Overseas_student' => { 'age' => 27, 'is_nz_citizen' => true, 'social_security__is_ordinarily_resident_in_new_zealand' => true, 'student_allowance__is_tertiary_student' => true, 'student_allowance__approved_to_study_overseas' => true }, 'Refugee' => { 'age' => 25, 'immigration__is_recognised_refugee' => true, 'student_allowance__is_tertiary_student' => true, 'student_allowance__is_enrolled_fulltime' => true, 'student_allowance__meets_attendance_and_performance_requirements' => true }, 'Not_a_student' => { 'age' => 50, 'is_nz_citizen' => true, 'social_security__is_ordinarily_resident_in_new_zealand' => true, 'student_allowance__is_tertiary_student' => false } }, 'families' => { 'Whanau' => { 'others' => %w[fulltime_uni_student Overseas_student Refugee Not_a_student Parttime_student] } }, 'titled_properties' => { 'whare' => { 'others' => %w[
13-
fulltime_uni_student Overseas_student Refugee Not_a_student Parttime_student] } }
13+
fulltime_uni_student Overseas_student Refugee Not_a_student Parttime_student
14+
] } }
1415
},
1516
outputs: { 'student_allowance__eligible_for_basic_grant' => [
1617
true, true, true, true, false

spec/features/scenarios/show_spec.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
let(:input_hash) do
77
{
88
'persons' => { 'fulltime_uni_student' => { 'age' => 21, 'is_nz_citizen' => true, 'social_security__is_ordinarily_resident_in_new_zealand' => true, 'student_allowance__is_tertiary_student' => true, 'student_allowance__is_enrolled_fulltime' => true, 'student_allowance__meets_attendance_and_performance_requirements' => true }, 'Parttime_student' => { 'age' => 18, 'is_nz_citizen' => true, 'social_security__is_ordinarily_resident_in_new_zealand' => true, 'student_allowance__is_tertiary_student' => true, 'student_allowance__approved_to_study_parttime' => true }, 'Overseas_student' => { 'age' => 27, 'is_nz_citizen' => true, 'social_security__is_ordinarily_resident_in_new_zealand' => true, 'student_allowance__is_tertiary_student' => true, 'student_allowance__approved_to_study_overseas' => true }, 'Refugee' => { 'age' => 25, 'immigration__is_recognised_refugee' => true, 'student_allowance__is_tertiary_student' => true, 'student_allowance__is_enrolled_fulltime' => true, 'student_allowance__meets_attendance_and_performance_requirements' => true }, 'Not_a_student' => { 'age' => 50, 'is_nz_citizen' => true, 'social_security__is_ordinarily_resident_in_new_zealand' => true, 'student_allowance__is_tertiary_student' => false } }, 'families' => { 'Whanau' => { 'others' => %w[fulltime_uni_student Overseas_student Refugee Not_a_student Parttime_student] } }, 'titled_properties' => { 'whare' => { 'others' => %w[
9-
fulltime_uni_student Overseas_student Refugee Not_a_student Parttime_student] } }
9+
fulltime_uni_student Overseas_student Refugee Not_a_student Parttime_student
10+
] } }
1011
}
1112
end
1213
let(:output_hash) do
1314
{ 'student_allowance__eligible_for_basic_grant' => [true, true, true,
14-
true, false] }
15+
true, false] }
1516
end
1617
let(:our_scenario) do
1718
FactoryBot.create :variable, name: 'age', description: 'age in years'

spec/models/scenario_spec.rb

+33-1
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,56 @@
2424
}
2525
}
2626
end
27+
let(:output_hash) do
28+
{ 'student_allowance__eligible_for_basic_grant' => [true] }
29+
end
2730
let(:expected_variables) do
31+
expected_input_variables + expected_output_variables
32+
end
33+
let(:expected_input_variables) do
2834
%w[age is_nz_citizen social_security__is_ordinarily_resident_in_new_zealand
2935
student_allowance__is_tertiary_student student_allowance__is_enrolled_fulltime
3036
student_allowance__meets_attendance_and_performance_requirements]
3137
end
38+
let(:expected_output_variables) do
39+
['student_allowance__eligible_for_basic_grant']
40+
end
3241
let!(:complicated_scenario) do
3342
expected_variables.each do |variable_name|
3443
FactoryBot.create :variable, name: variable_name
3544
end
3645
FactoryBot.create :scenario,
3746
name: 'a complicated situation',
3847
inputs: input_hash,
39-
outputs: { 'student_allowance__eligible_for_basic_grant' => [true] },
48+
outputs: output_hash,
4049
period: '2019-05',
4150
error_margin: 1.0,
4251
namespace: 'ghostchips'
4352
end
4453
before { complicated_scenario.parse_variables! }
4554
it { expect(complicated_scenario.variables.pluck(:name)).to eq expected_variables }
55+
it { expect(complicated_scenario.input_variables.pluck(:name)).to eq expected_input_variables }
56+
it { expect(complicated_scenario.output_variables.pluck(:name)).to eq expected_output_variables }
57+
describe 'only allows input/output variables' do
58+
it { expect { ScenarioVariable.create! variable: variable, scenario: scenario }.to raise_error }
59+
it { expect { ScenarioVariable.create! variable: variable, scenario: scenario, direction: 'down' }.to raise_error }
60+
end
61+
describe 'no duplicates allowed' do
62+
subject { complicated_scenario.output_variables.pluck(:name) }
63+
scenario do
64+
complicated_scenario.parse_variables!
65+
expect(subject).to eq ['student_allowance__eligible_for_basic_grant']
66+
complicated_scenario.parse_variables!
67+
expect(subject).to eq ['student_allowance__eligible_for_basic_grant']
68+
end
69+
end
70+
describe 'removes variables we no longer refer to' do
71+
scenario do
72+
FactoryBot.create :variable, name: 'hates_marshmallows'
73+
complicated_scenario.update!(outputs: { "hates_marshmallows": [true] })
74+
complicated_scenario.parse_variables!
75+
expect(complicated_scenario.output_variables.pluck(:name)).to eq ['hates_marshmallows']
76+
end
77+
end
4678
end
4779
end

spec/models/variable_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
end
1111

1212
describe 'links' do
13-
let!(:parent) {
13+
let!(:parent) do
1414
FactoryBot.create :variable, reversed_variables: [child1,
1515
child2]
16-
}
16+
end
1717
let(:child1) { FactoryBot.create :variable }
1818
let(:child2) { FactoryBot.create :variable }
1919

0 commit comments

Comments
 (0)