# frozen_string_literal: truerequire'rails/generators'require_relative'generated_attribute'require_relative'secondary_index'moduleAwsRecordmoduleGeneratorsclassBase<Rails::Generators::NamedBaseargument:attributes,type: :array,default: [],banner: 'field[:type][:opts]...',desc: 'Describes the fields in the model'check_class_collisionclass_option:disable_mutation_tracking,type: :boolean,desc: 'Disables dirty tracking'class_option:timestamps,type: :boolean,desc: 'Adds created, updated timestamps to the model'class_option:table_config,type: :hash,default: {},banner: 'primary:R-W [SecondaryIndex1:R-W]...',desc: 'Declares the r/w units for the model as well as any secondary indexes',required: trueclass_option:gsi,type: :array,default: [],banner: 'name:hkey{field_name}[,rkey{field_name},proj_type{ALL|KEYS_ONLY|INCLUDE}]...',desc: 'Allows for the declaration of secondary indexes'class_option:table_name,type: :string,banner: 'model_table_name'class_option:password_digest,type: :boolean,desc: 'Whether to add a password_digest field to the model'class_option:required,type: :string,banner: 'field1...',desc: 'A list of attributes that are required for an instance of the model'class_option:length_validations,type: :hash,default: {},banner: 'field1:MIN-MAX...',desc: 'Validations on the length of attributes in a model'attr_accessor:primary_read_units,:primary_write_units,:gsi_rw_units,:gsis,:required_attrs,:length_validationsprivatedefinitialize(args,*options)options[0]<<'--skip-table-config'ifoptions[1][:behavior]==:revoke@parse_errors=[]superensure_unique_fieldsensure_hkeyparse_gsis!parse_table_config!parse_validations!returnif@parse_errors.empty?warn'The following errors were encountered while trying to parse the given attributes'$stderr.putswarn@parse_errors$stderr.putsabort('Please fix the errors before proceeding.')enddefparse_attributes!self.attributes=(attributes||[]).mapdo|attr|GeneratedAttribute.parse(attr)rescueArgumentError=>e@parse_errors<<enextendself.attributes=attributes.compactifoptions['password_digest']attributes<<GeneratedAttribute.new('password_digest',:string_attr,digest: true)endreturnunlessoptions['timestamps']attributes<<GeneratedAttribute.parse('created:datetime:default_value{Time.now}')attributes<<GeneratedAttribute.parse('updated:datetime:default_value{Time.now}')enddefensure_unique_fieldsused_names=Set.newduplicate_fields=[]attributes.eachdo|attr|duplicate_fields<<[:attribute,attr.name]ifused_names.include?attr.nameused_names.addattr.namenextunlessattr.options.key?:database_attribute_nameraw_db_attr_name=attr.options[:database_attribute_name].delete('"')# db attribute names are wrapped with " to make template generation easierduplicate_fields<<[:database_attribute_name,raw_db_attr_name]ifused_names.include?raw_db_attr_nameused_names.addraw_db_attr_nameendreturnifduplicate_fields.empty?duplicate_fields.eachdo|invalid_attr|@parse_errors<<ArgumentError.new("Found duplicated field name: #{invalid_attr[1]}, in attribute#{invalid_attr[0]}")endenddefensure_hkeyuuid_member=nilhkey_member=nilrkey_member=nilattributes.eachdo|attr|ifattr.options.key?:hash_keyifhkey_member@parse_errors<<ArgumentError.new("Redefinition of hash_key attr: #{attr.name}, original declaration of hash_key on: #{hkey_member.name}")nextendhkey_member=attrelsifattr.options.key?:range_keyifrkey_member@parse_errors<<ArgumentError.new("Redefinition of range_key attr: #{attr.name}, original declaration of range_key on: #{hkey_member.name}")nextendrkey_member=attrenduuid_member=attrifattr.name.include?'uuid'endreturnifhkey_memberifuuid_memberuuid_member.options[:hash_key]=trueelseattributes.unshiftGeneratedAttribute.parse('uuid:hkey')endenddefmutation_tracking_disabled?options['disable_mutation_tracking']enddefhas_validations?!@required_attrs.empty?||!@length_validations.empty?enddefparse_table_config!returnunlessoptions['table_config']@primary_read_units,@primary_write_units=parse_rw_units('primary')@gsi_rw_units=@gsis.to_hdo|idx|[idx.name,parse_rw_units(idx.name)]endoptions['table_config'].each_keydo|config|nextifconfig=='primary'gsi=@gsis.select{|idx|idx.name==config}@parse_errors<<ArgumentError.new("Could not find a gsi declaration for #{config}")ifgsi.empty?endenddefparse_rw_units(name)ifoptions['table_config'].key?namerw_units=options['table_config'][name]rw_units.gsub(/[,.-]/,':').split(':').reject(&:empty?)else@parse_errors<<ArgumentError.new("Please provide a table_config definition for #{name}")endenddefparse_gsis!@gsis=(options['gsi']||[]).mapdo|raw_idx|idx=SecondaryIndex.parse(raw_idx)attributes=self.attributes.select{|attr|attr.name==idx.hash_key}ifattributes.empty?@parse_errors<<ArgumentError.new("Could not find attribute #{idx.hash_key} for gsi #{idx.name} hkey")nextendifidx.range_keyattributes=self.attributes.select{|attr|attr.name==idx.range_key}ifattributes.empty?@parse_errors<<ArgumentError.new("Could not find attribute #{idx.range_key} for gsi #{idx.name} rkey")nextendendidxrescueArgumentError=>e@parse_errors<<enextend@gsis=@gsis.compactenddefparse_validations!@required_attrs=options['required']?options['required'].split(','):[]@required_attrs.eachdo|val_attr|@parse_errors<<ArgumentError.new("No such field #{val_attr} in required validations")ifattributes.none?do|attr|attr.name==val_attrendend@length_validations=options['length_validations'].mapdo|val_attr,bounds|@parse_errors<<ArgumentError.new("No such field #{val_attr} in required validations")ifattributes.none?do|attr|attr.name==val_attrendbounds=bounds.gsub(/[,.-]/,':').split(':').reject(&:empty?)[val_attr,"#{bounds[0]}..#{bounds[1]}"]end@length_validations=@length_validations.to_hendendendend