diff --git a/app/models/account.rb b/app/models/account.rb index 80e9eb8..2da750c 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -2,5 +2,5 @@ class Account < ApplicationRecord include Joinable has_one_attached :logo - has_settings restrict_room_creation_to_administrators: :boolean, max_invites: 10, name: "hero" + has_settings restrict_room_creation_to_administrators: false end diff --git a/lib/rails_ext/active_record_has_json.rb b/lib/rails_ext/active_record_has_json.rb index 7a5540b..01245cf 100644 --- a/lib/rails_ext/active_record_has_json.rb +++ b/lib/rails_ext/active_record_has_json.rb @@ -1,23 +1,24 @@ module ActiveRecord - class TypedJson + class SchematizedJson def initialize(schema, data:) @schema, @data = schema, data + update_data_with_schema_defaults end def assign_data_with_type_casting(new_data) - new_data.each { |key, value| send("#{key}=", value) } + new_data.each { |key, value| send "#{key}=", value } end private def method_missing(method_name, *args, **kwargs) key = method_name.to_s.remove(/(\?|=)/) - if key_type = @schema[key.to_sym] + if @schema.key? key.to_sym if method_name.ends_with?("?") @data[key].present? elsif method_name.ends_with?("=") value = args.first - @data[key] = ActiveModel::Type.lookup(key_type).cast(value) + @data[key] = lookup_schema_type_for(key).cast(value) else @data[key] end @@ -29,13 +30,34 @@ module ActiveRecord def respond_to_missing?(method_name, include_private = false) @schema.key?(method_name.to_s.remove(/[?=]/).to_sym) || super end + + def lookup_schema_type_for(key) + type_or_default_value = @schema[key.to_sym] + + case type_or_default_value + when :boolean, :integer, :string + ActiveModel::Type.lookup(type_or_default_value) + when TrueClass, FalseClass + ActiveModel::Type.lookup(:boolean) + when Integer + ActiveModel::Type.lookup(:integer) + when String + ActiveModel::Type.lookup(:string) + else + raise "Only boolean, integer, or strings are allowed as JSON schema types" + end + end + + def update_data_with_schema_defaults + @data.reverse_merge!(@schema.to_h { |k, v| [ k.to_s, v.is_a?(Symbol) ? nil : v ] }) + end end class Base class << self def has_json(delegate: false, **schemas) schemas.each do |name, schema| - define_method(name) { ActiveRecord::TypedJson.new(schema, data: self[name]) } + define_method(name) { ActiveRecord::SchematizedJson.new(schema, data: self[name]) } define_method("#{name}=") { |data| send(name).assign_data_with_type_casting(data) } schema.keys.each do |schema_key| @@ -43,6 +65,9 @@ module ActiveRecord define_method("#{schema_key}?") { send(name).send("#{schema_key}?") } define_method("#{schema_key}=") { |value| send(name).send("#{schema_key}=", value) } end if delegate + + # Ensures default values are set before saving + before_save -> { send(name) } end end diff --git a/test/models/account_test.rb b/test/models/account_test.rb index e4e3673..fd6be9a 100644 --- a/test/models/account_test.rb +++ b/test/models/account_test.rb @@ -15,4 +15,9 @@ class AccountTest < ActiveSupport::TestCase accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "false" }) assert_not accounts(:signal).reload.restrict_room_creation_to_administrators? end + + test "default settings" do + a = Account.create! name: "New account" + assert_equal({ "restrict_room_creation_to_administrators" => false }, a[:settings]) + end end