dscf-banking 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0086738f163c88e967b2850c3b0c2e29804725e059996dd2edfd152a98c874b5'
4
- data.tar.gz: 2bc163e912db1cb78a491610f88efc8d99f8455f034ed90f64031628bce1375c
3
+ metadata.gz: e91b4944ed5da04e89193cc1e06db5150ea053f3f4cd4a8098a576f25585c8c9
4
+ data.tar.gz: 0c1ef04c3785327d68b9d87d6a62ad228329a9fde73e68b6893253614b31284a
5
5
  SHA512:
6
- metadata.gz: 970cd3f5b838bcd3a5c74a9b2511569cd4ec7eeb7ecf28d90e32d9efda437707214514088d562a2e35bff1a43f7dc8b89a5abff66c56071a5ff3f2e2f3abc832
7
- data.tar.gz: 29f3c5f3299b0b1dc1da0cfb11594f22a0c2970865482701097bd0492552165a90a76817b8acef394f63e4de15a5758571635236edd482f633ed700117af684f
6
+ metadata.gz: a9f07e8d19d474514261bb9189ed89b804525f34839b3ea55704861647a37aa69b840a5ad995b4d3e53c5d6d89237384db92f262173a5021f312257d3abd392e
7
+ data.tar.gz: af083b3874ca0b5e8161125409ed7c06974b8ccf3404f615d0e044bfe434e66022e4f691e6450f7f63a5cd5e15b49061151f4fa27c28ba0acb83aceb330c0a70
@@ -4,16 +4,21 @@ module Dscf::Banking
4
4
  belongs_to :virtual_account_product, class_name: "Dscf::Banking::VirtualAccountProduct"
5
5
  has_many :interest_rate_tiers, class_name: "Dscf::Banking::InterestRateTier", dependent: :destroy
6
6
 
7
- validates :annual_interest_rate, presence: true, numericality: { greater_than: 0, less_than_or_equal_to: 1 }
8
- validates :income_tax_rate, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }
9
- validates :minimum_balance_for_interest, presence: true, numericality: { greater_than_or_equal_to: 0 }
10
- validates :calculation_method, presence: true
11
-
12
- validates :interest_basis, presence: true
13
- validates :accrual_frequency, presence: true
14
- validates :rounding_rule, presence: true
15
- validates :calculation_timing, presence: true
16
- validates :is_active, inclusion: { in: [ true, false ] }
7
+ before_validation :normalize_rate_attributes
8
+ validate :interest_free_configuration_guidance
9
+
10
+ with_options unless: :interest_free_configuration_request? do
11
+ validates :annual_interest_rate, presence: true, numericality: { greater_than: 0, less_than_or_equal_to: 1 }
12
+ validates :income_tax_rate, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }
13
+ validates :minimum_balance_for_interest, presence: true, numericality: { greater_than_or_equal_to: 0 }
14
+ validates :calculation_method, presence: true
15
+
16
+ validates :interest_basis, presence: true
17
+ validates :accrual_frequency, presence: true
18
+ validates :rounding_rule, presence: true
19
+ validates :calculation_timing, presence: true
20
+ validates :is_active, inclusion: { in: [ true, false ] }
21
+ end
17
22
  validate :promotional_dates_consistency
18
23
 
19
24
  enum :calculation_method, {
@@ -59,6 +64,49 @@ module Dscf::Banking
59
64
 
60
65
  private
61
66
 
67
+ def interest_free_configuration_guidance
68
+ return unless no_interest_requested?
69
+ return unless required_calculation_fields_missing?
70
+
71
+ errors.add(
72
+ :base,
73
+ "Interest-free products should not have an interest configuration. Skip interest configuration creation for interest-free products."
74
+ )
75
+ end
76
+
77
+ def interest_free_configuration_request?
78
+ no_interest_requested? && required_calculation_fields_missing?
79
+ end
80
+
81
+ def normalize_rate_attributes
82
+ self.annual_interest_rate = normalize_rate_value(annual_interest_rate_before_type_cast)
83
+ self.income_tax_rate = normalize_rate_value(income_tax_rate_before_type_cast)
84
+ end
85
+
86
+ def normalize_rate_value(value)
87
+ return value if value.blank?
88
+
89
+ sanitized = value.to_s.strip
90
+ return value if sanitized.blank?
91
+
92
+ percentage_input = sanitized.end_with?("%")
93
+ decimal = BigDecimal(sanitized.delete("%").tr(",", "."))
94
+
95
+ percentage_input || decimal > 1 ? decimal / 100 : decimal
96
+ rescue ArgumentError
97
+ value
98
+ end
99
+
100
+ def no_interest_requested?
101
+ annual_interest_rate.blank? || BigDecimal(annual_interest_rate.to_s) <= 0
102
+ rescue ArgumentError
103
+ false
104
+ end
105
+
106
+ def required_calculation_fields_missing?
107
+ calculation_method.blank? || interest_basis.blank? || accrual_frequency.blank? || rounding_rule.blank? || calculation_timing.blank?
108
+ end
109
+
62
110
  def promotional_dates_consistency
63
111
  return unless promotional_start_date.present? || promotional_end_date.present?
64
112
 
@@ -3,9 +3,8 @@ require "date"
3
3
 
4
4
  module Dscf::Banking
5
5
  class InterestSimulationService
6
- DEFAULT_ANNUAL_INTEREST_RATE = BigDecimal("0.05")
7
- # Fallback tax rate when no product-level tax is configured.
8
- DEFAULT_TAX_RATE = BigDecimal("0.15")
6
+ NO_INTEREST_RATE = BigDecimal("0")
7
+ NO_TAX_RATE = BigDecimal("0")
9
8
 
10
9
  def initialize(account:, initial_balance:, start_date:, end_date:, transactions: [])
11
10
  @account = account
@@ -72,17 +71,23 @@ module Dscf::Banking
72
71
  end
73
72
 
74
73
  def annual_interest_rate
74
+ return NO_INTEREST_RATE unless interest_configuration
75
+
75
76
  configured = interest_configuration&.annual_interest_rate
76
- configured.present? ? decimal(configured) : DEFAULT_ANNUAL_INTEREST_RATE
77
+ configured.present? ? decimal(configured) : NO_INTEREST_RATE
77
78
  end
78
79
 
79
80
  def annual_interest_rate_for(balance)
81
+ return annual_interest_rate unless interest_configuration
82
+
80
83
  interest_rate_tier_for(balance)&.yield_self { |tier| decimal(tier.interest_rate) } || annual_interest_rate
81
84
  end
82
85
 
83
86
  def tax_rate
87
+ return NO_TAX_RATE unless interest_configuration
88
+
84
89
  configured = interest_configuration&.income_tax_rate
85
- configured.present? ? decimal(configured) : DEFAULT_TAX_RATE
90
+ configured.present? ? decimal(configured) : NO_TAX_RATE
86
91
  end
87
92
 
88
93
  def day_basis
data/db/seeds.rb CHANGED
@@ -1,115 +1,225 @@
1
- # db/seeds.rb
2
- # This file should contain all the record creation needed to seed the database with its default values.
3
- # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
4
- #
5
- # Examples:
6
- #
7
- # movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }])
8
- # Character.create(name: "Luke", movie: movies.first)
9
-
10
- # Seed roles based on RBAC documentation
11
- puts "Seeding roles..."
12
- user_role = Dscf::Core::Role.find_or_create_by(name: "User")
13
- officer_role = Dscf::Core::Role.find_or_create_by(name: "Virtual Account Officer")
14
- manager_role = Dscf::Core::Role.find_or_create_by(name: "Virtual Account Manager")
15
- kyc_officer_role = Dscf::Core::Role.find_or_create_by(name: "KYC Officer")
16
- branch_manager_role = Dscf::Core::Role.find_or_create_by(name: "Branch Manager")
17
- puts "Roles seeded successfully."
18
-
19
- # Seed users with roles
20
- puts "Seeding users..."
21
- # Create users and assign roles
22
- user1 = Dscf::Core::User.create(name: "Abebe Kebede", email: "abebe@example.com", password: "SecurePassword123!")
23
- user1.roles << user_role
24
- puts "Created user: #{user1.name}"
25
-
26
- user2 = Dscf::Core::User.create(name: "Tigist Haile", email: "tigist@example.com", password: "SecurePassword123!")
27
- user2.roles << officer_role
28
- puts "Created user: #{user2.name}"
29
-
30
- user3 = Dscf::Core::User.create(name: "Dawit Mekonnen", email: "dawit@example.com", password: "SecurePassword123!")
31
- user3.roles << manager_role
32
- puts "Created user: #{user3.name}"
33
-
34
- # Additional users for testing
35
- user4 = Dscf::Core::User.create(name: "Hirut Assefa", email: "hirut@example.com", password: "SecurePassword123!")
36
- user4.roles << [ user_role, officer_role ] # User with multiple roles if supported
37
- puts "Created user: #{user4.name}"
38
-
39
- user5 = Dscf::Core::User.create(name: "Solomon Tesfaye", email: "solomon@example.com", password: "SecurePassword123!")
40
- user5.roles << manager_role
41
- puts "Created user: #{user5.name}"
42
-
43
- user6 = Dscf::Core::User.create(name: "Mulugeta Bekele", email: "mulugeta@example.com", password: "SecurePassword123!")
44
- user6.roles << kyc_officer_role
45
- puts "Created user: #{user6.name}"
46
-
47
- user7 = Dscf::Core::User.create(name: "Fikirte Alemayehu", email: "fikirte@example.com", password: "SecurePassword123!")
48
- user7.roles << branch_manager_role
49
- puts "Created user: #{user7.name}"
1
+ # This file should contain all the record creation needed to seed the banking engine
2
+ # with deterministic, idempotent sample data.
3
+
4
+ puts "Seeding DSCF Banking data..."
5
+
6
+ seed_timestamp = Time.current
7
+
8
+ roles_data = [
9
+ { code: "USER", name: "User" },
10
+ { code: "ADMIN", name: "Administrator" },
11
+ { code: "KYC_OFFICER", name: "KYC Officer" },
12
+ { code: "BRANCH_MANAGER", name: "Branch Manager" },
13
+ { code: "VIRTUAL_ACCOUNT_OFFICER", name: "Virtual Account Officer" },
14
+ { code: "VIRTUAL_ACCOUNT_MANAGER", name: "Virtual Account Manager" },
15
+ { code: "REGULAR_USER", name: "Regular User" }
16
+ ].freeze
17
+
18
+ users_data = [
19
+ { email: "admin@dscf.com", phone: "+251911000001", roles: %w[ADMIN], password: "password123" },
20
+ { email: "kyc1@dscf.com", phone: "+251911000002", roles: %w[KYC_OFFICER], password: "password123" },
21
+ { email: "kyc2@dscf.com", phone: "+251911000003", roles: %w[KYC_OFFICER], password: "password123" },
22
+ { email: "kyc3@dscf.com", phone: "+251911000004", roles: %w[KYC_OFFICER], password: "password123" },
23
+ { email: "manager1@dscf.com", phone: "+251911000005", roles: %w[BRANCH_MANAGER], password: "password123" },
24
+ { email: "manager2@dscf.com", phone: "+251911000006", roles: %w[BRANCH_MANAGER], password: "password123" },
25
+ { email: "va_officer1@dscf.com", phone: "+251911000007", roles: %w[VIRTUAL_ACCOUNT_OFFICER], password: "password123" },
26
+ { email: "va_officer2@dscf.com", phone: "+251911000008", roles: %w[VIRTUAL_ACCOUNT_OFFICER], password: "password123" },
27
+ { email: "va_manager1@dscf.com", phone: "+251911000009", roles: %w[VIRTUAL_ACCOUNT_MANAGER], password: "password123" },
28
+ { email: "va_manager2@dscf.com", phone: "+251911000010", roles: %w[VIRTUAL_ACCOUNT_MANAGER], password: "password123" },
29
+ { email: "user1@example.com", phone: "+251911000011", roles: %w[USER REGULAR_USER], password: "password123" },
30
+ { email: "user2@example.com", phone: "+251911000012", roles: %w[USER REGULAR_USER], password: "password123" },
31
+ { email: "user3@example.com", phone: "+251911000013", roles: %w[USER REGULAR_USER], password: "password123" },
32
+ { email: "user4@example.com", phone: "+251911000014", roles: %w[USER REGULAR_USER], password: "password123" },
33
+ { email: "user5@example.com", phone: "+251911000015", roles: %w[USER REGULAR_USER], password: "password123" },
34
+ { email: "user6@example.com", phone: "+251911000016", roles: %w[USER REGULAR_USER], password: "password123" },
35
+ { email: "user7@example.com", phone: "+251911000017", roles: %w[USER REGULAR_USER], password: "password123" },
36
+ { email: "user8@example.com", phone: "+251911000018", roles: %w[USER REGULAR_USER], password: "password123" },
37
+ { email: "user9@example.com", phone: "+251911000019", roles: %w[USER REGULAR_USER], password: "password123" },
38
+ { email: "user10@example.com", phone: "+251911000020", roles: %w[USER REGULAR_USER], password: "password123" }
39
+ ].freeze
40
+
41
+ transaction_types_data = [
42
+ { code: "DEPOSIT", name: "Deposit", description: "Deposit transaction - money coming into account" },
43
+ { code: "WITHDRAWAL", name: "Withdrawal", description: "Withdrawal transaction - money going out of account" },
44
+ { code: "TRANSFER", name: "Transfer", description: "Transfer transaction - money moving between accounts" },
45
+ { code: "VOUCHER", name: "Voucher", description: "Voucher issuance and redemption transactions" }
46
+ ].freeze
47
+
48
+ system_accounts_data = [
49
+ { name: "System Deposit Account" },
50
+ { name: "System Withdrawal Account" },
51
+ { name: "Voucher Settlement Account" }
52
+ ].freeze
53
+
54
+ product_categories_data = [
55
+ { name: "Checking Account", description: "Day-to-day transaction accounts" },
56
+ { name: "Savings Account", description: "Standard savings accounts for retail customers" },
57
+ { name: "Fixed Deposit", description: "Time-bound deposit products with fixed returns" },
58
+ { name: "Business Account", description: "Accounts for small and medium business operations" }
59
+ ].freeze
60
+
61
+ interest_rate_types_data = [
62
+ { code: "STANDARD", name: "Standard Rate", description: "Default interest rate for standard products" },
63
+ { code: "PROMOTIONAL", name: "Promotional Rate", description: "Temporary promotional rate for campaigns" },
64
+ { code: "TIERED", name: "Tiered Rate", description: "Interest rate that varies by product tier" }
65
+ ].freeze
66
+
67
+ virtual_account_products_data = [
68
+ {
69
+ product_code: "CHK-001",
70
+ product_name: "Standard Checking",
71
+ category_name: "Checking Account",
72
+ description: "Everyday checking account for regular transactions."
73
+ },
74
+ {
75
+ product_code: "SVG-001",
76
+ product_name: "Standard Savings",
77
+ category_name: "Savings Account",
78
+ description: "Savings account for customers who want interest on available balances."
79
+ },
80
+ {
81
+ product_code: "FD-001",
82
+ product_name: "12-Month Fixed Deposit",
83
+ category_name: "Fixed Deposit",
84
+ description: "Fixed-term deposit product with a one-year investment period."
85
+ },
86
+ {
87
+ product_code: "BUS-001",
88
+ product_name: "SME Business Account",
89
+ category_name: "Business Account",
90
+ description: "Business operating account designed for small and medium enterprises."
91
+ }
92
+ ].freeze
93
+
94
+ interest_rates_by_category = {
95
+ "Savings Account" => 0.035,
96
+ "Fixed Deposit" => 0.06,
97
+ "Business Account" => 0.025
98
+ }.freeze
50
99
 
51
- # Seed transaction types
52
- puts "Seeding transaction types..."
53
- deposit_type = Dscf::Banking::TransactionType.find_or_create_by(code: "DEPOSIT") do |tt|
54
- tt.name = "Deposit"
55
- tt.description = "Deposit transaction - money coming into account"
100
+ puts "Seeding roles..."
101
+ roles = roles_data.each_with_object({}) do |role_data, memo|
102
+ role = Dscf::Core::Role.find_or_initialize_by(code: role_data[:code])
103
+ role.name = role_data[:name]
104
+ role.active = true
105
+ role.save!
106
+
107
+ memo[role_data[:code]] = role
108
+ puts " Seeded role: #{role.code}"
56
109
  end
57
- puts "Created transaction type: #{deposit_type.name}"
58
110
 
59
- withdrawal_type = Dscf::Banking::TransactionType.find_or_create_by(code: "WITHDRAWAL") do |tt|
60
- tt.name = "Withdrawal"
61
- tt.description = "Withdrawal transaction - money going out of account"
111
+ puts "Seeding users..."
112
+ users = users_data.each_with_object({}) do |user_data, memo|
113
+ user = Dscf::Core::User.find_or_initialize_by(email: user_data[:email])
114
+ user.phone = user_data[:phone]
115
+ user.password = user_data[:password] if user.new_record? || user.password_digest.blank?
116
+ user.temp_password = false
117
+ user.verified_at ||= seed_timestamp
118
+ user.save!
119
+
120
+ user_data[:roles].each do |role_code|
121
+ Dscf::Core::UserRole.find_or_create_by!(user: user, role: roles.fetch(role_code))
122
+ end
123
+
124
+ memo[user_data[:email]] = user
125
+ puts " Seeded user: #{user.email}"
62
126
  end
63
- puts "Created transaction type: #{withdrawal_type.name}"
64
127
 
65
- transfer_type = Dscf::Banking::TransactionType.find_or_create_by(code: "TRANSFER") do |tt|
66
- tt.name = "Transfer"
67
- tt.description = "Transfer transaction - money moving between accounts"
68
- end
69
- puts "Created transaction type: #{transfer_type.name}"
128
+ admin_user = users.fetch("admin@dscf.com")
70
129
 
71
- voucher_type = Dscf::Banking::TransactionType.find_or_create_by(code: "VOUCHER") do |tt|
72
- tt.name = "Voucher"
73
- tt.description = "Voucher issuance and redemption transactions"
130
+ puts "Seeding transaction types..."
131
+ transaction_types_data.each do |transaction_type_data|
132
+ transaction_type = Dscf::Banking::TransactionType.find_or_initialize_by(code: transaction_type_data[:code])
133
+ transaction_type.name = transaction_type_data[:name]
134
+ transaction_type.description = transaction_type_data[:description]
135
+ transaction_type.save!
136
+
137
+ puts " Seeded transaction type: #{transaction_type.code}"
74
138
  end
75
- puts "Created transaction type: #{voucher_type.name}"
76
139
 
77
- # Seed system accounts for transaction processing
78
140
  puts "Seeding system accounts..."
79
- system_deposit_account = Dscf::Banking::Account.find_or_create_by(
80
- name: "System Deposit Account",
81
- system_account: true
82
- ) do |account|
141
+ system_accounts_data.each do |account_data|
142
+ account = Dscf::Banking::Account.find_or_initialize_by(
143
+ name: account_data[:name],
144
+ system_account: true
145
+ )
83
146
  account.currency = "ETB"
84
147
  account.status = :active
85
148
  account.current_balance = 0
86
149
  account.available_balance = 0
87
150
  account.minimum_balance = 0
151
+ account.active = true
152
+ account.save!
153
+
154
+ puts " Seeded system account: #{account.name}"
88
155
  end
89
- puts "Created system deposit account: #{system_deposit_account.name}"
90
156
 
91
- system_withdrawal_account = Dscf::Banking::Account.find_or_create_by(
92
- name: "System Withdrawal Account",
93
- system_account: true
94
- ) do |account|
95
- account.currency = "ETB"
96
- account.status = :active
97
- account.current_balance = 0
98
- account.available_balance = 0
99
- account.minimum_balance = 0
157
+ puts "Seeding product categories..."
158
+ product_categories = product_categories_data.each_with_object({}) do |category_data, memo|
159
+ category = Dscf::Banking::ProductCategory.find_or_initialize_by(name: category_data[:name])
160
+ category.description = category_data[:description]
161
+ category.is_active = true
162
+ category.save!
163
+
164
+ memo[category_data[:name]] = category
165
+ puts " Seeded product category: #{category.name}"
100
166
  end
101
- puts "Created system withdrawal account: #{system_withdrawal_account.name}"
102
167
 
103
- voucher_settlement_account = Dscf::Banking::Account.find_or_create_by(
104
- name: "Voucher Settlement Account",
105
- system_account: true
106
- ) do |account|
107
- account.currency = "ETB"
108
- account.status = :active
109
- account.current_balance = 0
110
- account.available_balance = 0
111
- account.minimum_balance = 0
168
+ puts "Seeding interest rate types..."
169
+ interest_rate_types = interest_rate_types_data.each_with_object([]) do |rate_type_data, memo|
170
+ rate_type = Dscf::Banking::InterestRateType.find_or_initialize_by(code: rate_type_data[:code])
171
+ rate_type.name = rate_type_data[:name]
172
+ rate_type.description = rate_type_data[:description]
173
+ rate_type.save!
174
+
175
+ memo << rate_type
176
+ puts " Seeded interest rate type: #{rate_type.code}"
177
+ end
178
+
179
+ puts "Seeding virtual account products..."
180
+ virtual_account_products = virtual_account_products_data.each_with_object([]) do |product_data, memo|
181
+ product = Dscf::Banking::VirtualAccountProduct.find_or_initialize_by(product_code: product_data[:product_code])
182
+ product.product_name = product_data[:product_name]
183
+ product.product_category = product_categories.fetch(product_data[:category_name])
184
+ product.description = product_data[:description]
185
+ product.document_reference = "DOC-#{product_data[:product_code]}"
186
+ product.status = :approved
187
+ product.created_by = admin_user
188
+ product.approved_by = admin_user
189
+ product.approved_at ||= seed_timestamp
190
+ product.is_active = true
191
+ product.save!
192
+
193
+ memo << product
194
+ puts " Seeded virtual account product: #{product.product_code}"
195
+ end
196
+
197
+ puts "Seeding interest configurations..."
198
+ virtual_account_products.each_with_index do |product, index|
199
+ category_name = product.product_category&.name
200
+
201
+ if category_name == "Checking Account"
202
+ puts " Skipped interest configuration for: #{product.product_code}"
203
+ next
204
+ end
205
+
206
+ interest_configuration = Dscf::Banking::InterestConfiguration.find_or_initialize_by(
207
+ virtual_account_product: product,
208
+ interest_rate_type: interest_rate_types[index % interest_rate_types.length]
209
+ )
210
+ interest_configuration.annual_interest_rate = interest_rates_by_category.fetch(category_name, 0.02)
211
+ interest_configuration.income_tax_rate = 0.05
212
+ interest_configuration.minimum_balance_for_interest = 1000.0
213
+ interest_configuration.calculation_method = :simple
214
+ interest_configuration.compounding_period = :monthly
215
+ interest_configuration.interest_basis = :actual_365
216
+ interest_configuration.accrual_frequency = :monthly
217
+ interest_configuration.rounding_rule = :nearest_cent
218
+ interest_configuration.calculation_timing ||= seed_timestamp
219
+ interest_configuration.is_active = true
220
+ interest_configuration.save!
221
+
222
+ puts " Seeded interest configuration for: #{product.product_code}"
112
223
  end
113
- puts "Created voucher settlement account: #{voucher_settlement_account.name}"
114
224
 
115
- puts "Seeding completed successfully."
225
+ puts "DSCF Banking seed completed successfully."
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Banking
3
- VERSION = "0.5.3"
3
+ VERSION = "0.5.5"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dscf-banking
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat Efrem
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-03-16 00:00:00.000000000 Z
10
+ date: 2026-04-22 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails