func NewMeta(model Model) *Meta {
	// acquire mutex
	metaCacheMutex.Lock()
	defer metaCacheMutex.Unlock()

	// get type and name
	modelType := reflect.TypeOf(model).Elem()
	modelName := modelType.String()

	// check if meta has already been cached
	meta, ok := metaCache[modelName]
	if ok {
		return meta
	}

	// create new meta
	meta = &Meta{
		Name:           modelName,
		model:          model,
		Fields:         make(map[string]*Field),
		DatabaseFields: make(map[string]*Field),
		Attributes:     make(map[string]*Field),
		Relationships:  make(map[string]*Field),
		FlaggedFields:  make(map[string][]*Field),
	}

	// iterate through all fields
	for i := 0; i < modelType.NumField(); i++ {
		// get field
		field := modelType.Field(i)

		// get coal tag
		coalTag := field.Tag.Get("coal")

		// check for first field
		if i == 0 {
			// assert first field to be the base
			if field.Type != baseType {
				panic(`coal: expected an embedded "coal.Base" as the first struct field`)
			}

			// split tag
			baseTag := strings.Split(coalTag, ":")

			// check json tag
			if field.Tag.Get("json") != "-" {
				panic(`coal: expected to find a tag of the form 'json:"-"' on "coal.Base""`)
			}

			// check bson tag
			if field.Tag.Get("bson") != ",inline" {
				panic(`coal: expected to find a tag of the form 'bson:",inline"' on "coal.Base""`)
			}

			// check tag
			if len(baseTag) > 2 || baseTag[0] == "" {
				panic(`coal: expected to find a tag of the form 'coal:"plural-name[:collection]"' on "coal.Base""`)
			}

			// infer plural and collection names
			meta.PluralName = baseTag[0]
			meta.Collection = baseTag[0]

			// infer collection
			if len(baseTag) == 2 {
				meta.Collection = baseTag[1]
			}

			continue
		}

		// parse individual tags
		coalTags := strings.Split(coalTag, ",")
		if len(coalTag) == 0 {
			coalTags = nil
		}

		// get field type
		fieldKind := field.Type.Kind()
		if fieldKind == reflect.Ptr {
			fieldKind = field.Type.Elem().Kind()
		}

		// prepare field
		metaField := &Field{
			Name:      field.Name,
			Type:      field.Type,
			Kind:      fieldKind,
			JSONKey:   getJSONFieldName(&field),
			BSONField: getBSONFieldName(&field),
			Optional:  field.Type.Kind() == reflect.Ptr,
			index:     i,
		}

		// check if field is a valid to-one relationship
		if field.Type == toOneType || field.Type == optionalToOneType {
			if len(coalTags) > 0 && strings.Count(coalTags[0], ":") > 0 {
				// check tag
				if strings.Count(coalTags[0], ":") > 1 {
					panic(`coal: expected to find a tag of the form 'coal:"name:type"' on to-one relationship`)
				}

				// parse special to-one relationship tag
				toOneTag := strings.Split(coalTags[0], ":")

				// set relationship data
				metaField.ToOne = true
				metaField.RelName = toOneTag[0]
				metaField.RelType = toOneTag[1]

				// remove tag
				coalTags = coalTags[1:]
			}
		}

		// check if field is a valid to-many relationship
		if field.Type == toManyType {
			if len(coalTags) > 0 && strings.Count(coalTags[0], ":") > 0 {
				// check tag
				if strings.Count(coalTags[0], ":") > 1 {
					panic(`coal: expected to find a tag of the form 'coal:"name:type"' on to-many relationship`)
				}

				// parse special to-many relationship tag
				toManyTag := strings.Split(coalTags[0], ":")

				// set relationship data
				metaField.ToMany = true
				metaField.RelName = toManyTag[0]
				metaField.RelType = toManyTag[1]

				// remove tag
				coalTags = coalTags[1:]
			}
		}

		// check if field is a valid has-one relationship
		if field.Type == hasOneType {
			// check tag
			if len(coalTags) != 1 || strings.Count(coalTags[0], ":") != 2 {
				panic(`coal: expected to find a tag of the form 'coal:"name:type:inverse"' on has-one relationship`)
			}

			// parse special has-one relationship tag
			hasOneTag := strings.Split(coalTags[0], ":")

			// set relationship data
			metaField.HasOne = true
			metaField.RelName = hasOneTag[0]
			metaField.RelType = hasOneTag[1]
			metaField.RelInverse = hasOneTag[2]

			// remove tag
			coalTags = coalTags[1:]
		}

		// check if field is a valid has-many relationship
		if field.Type == hasManyType {
			// check tag
			if len(coalTags) != 1 || strings.Count(coalTags[0], ":") != 2 {
				panic(`coal: expected to find a tag of the form 'coal:"name:type:inverse"' on has-many relationship`)
			}

			// parse special has-many relationship tag
			hasManyTag := strings.Split(coalTags[0], ":")

			// set relationship data
			metaField.HasMany = true
			metaField.RelName = hasManyTag[0]
			metaField.RelType = hasManyTag[1]
			metaField.RelInverse = hasManyTag[2]

			// remove tag
			coalTags = coalTags[1:]
		}

		// save additional tags as flags
		metaField.Flags = coalTags
		if metaField.Flags == nil {
			metaField.Flags = []string{}
		}

		// add field
		meta.Fields[metaField.Name] = metaField
		meta.OrderedFields = append(meta.OrderedFields, metaField)

		// add db fields
		if metaField.BSONField != "" {
			// check existence
			if meta.DatabaseFields[metaField.BSONField] != nil {
				panic(fmt.Sprintf(`coal: duplicate BSON field "%s"`, metaField.BSONField))
			}

			// add field
			meta.DatabaseFields[metaField.BSONField] = metaField
		}

		// add attributes
		if metaField.JSONKey != "" {
			// check existence
			if meta.Attributes[metaField.JSONKey] != nil {
				panic(fmt.Sprintf(`coal: duplicate JSON key "%s"`, metaField.JSONKey))
			}

			// add field
			meta.Attributes[metaField.JSONKey] = metaField
		}

		// add relationships
		if metaField.RelName != "" {
			// check existence
			if meta.Relationships[metaField.RelName] != nil {
				panic(fmt.Sprintf(`coal: duplicate relationship "%s"`, metaField.RelName))
			}

			// add field
			meta.Relationships[metaField.RelName] = metaField
		}

		// add flagged fields
		for _, flag := range metaField.Flags {
			// get list
			list, _ := meta.FlaggedFields[flag]

			// add field
			list = append(list, metaField)

			// save list
			meta.FlaggedFields[flag] = list
		}
	}

	// cache meta
	metaCache[modelName] = meta

	return meta
}
func NewMeta(model Model) *Meta {
	
	metaCacheMutex.Lock()
	defer metaCacheMutex.Unlock()

	
	modelType := reflect.TypeOf(model).Elem()
	modelName := modelType.String()

	
	meta, ok := metaCache[modelName]
	if ok {
		return meta
	}

	
	meta = &Meta{
		Name:           modelName,
		model:          model,
		Fields:         make(map[string]*Field),
		DatabaseFields: make(map[string]*Field),
		Attributes:     make(map[string]*Field),
		Relationships:  make(map[string]*Field),
		FlaggedFields:  make(map[string][]*Field),
	}

	
	for i := 0; i < modelType.NumField(); i++ {
		
		field := modelType.Field(i)

		
		coalTag := field.Tag.Get("coal")

		
		if i == 0 {
			
			if field.Type != baseType {
				panic(`coal: expected an embedded "coal.Base" as the first struct field`)
			}

			
			baseTag := strings.Split(coalTag, ":")

			
			if field.Tag.Get("json") != "-" {
				panic(`coal: expected to find a tag of the form 'json:"-"' on "coal.Base""`)
			}

			
			if field.Tag.Get("bson") != ",inline" {
				panic(`coal: expected to find a tag of the form 'bson:",inline"' on "coal.Base""`)
			}

			
			if len(baseTag) > 2 || baseTag[0] == "" {
				panic(`coal: expected to find a tag of the form 'coal:"plural-name[:collection]"' on "coal.Base""`)
			}

			
			meta.PluralName = baseTag[0]
			meta.Collection = baseTag[0]

			
			if len(baseTag) == 2 {
				meta.Collection = baseTag[1]
			}

			continue
		}

		
		coalTags := strings.Split(coalTag, ",")
		if len(coalTag) == 0 {
			coalTags = nil
		}

		
		fieldKind := field.Type.Kind()
		if fieldKind == reflect.Ptr {
			fieldKind = field.Type.Elem().Kind()
		}

		
		metaField := &Field{
			Name:      field.Name,
			Type:      field.Type,
			Kind:      fieldKind,
			JSONKey:   getJSONFieldName(&field),
			BSONField: getBSONFieldName(&field),
			Optional:  field.Type.Kind() == reflect.Ptr,
			index:     i,
		}

		
		if field.Type == toOneType || field.Type == optionalToOneType {
			if len(coalTags) > 0 && strings.Count(coalTags[0], ":") > 0 {
				
				if strings.Count(coalTags[0], ":") > 1 {
					panic(`coal: expected to find a tag of the form 'coal:"name:type"' on to-one relationship`)
				}

				
				toOneTag := strings.Split(coalTags[0], ":")

				
				metaField.ToOne = true
				metaField.RelName = toOneTag[0]
				metaField.RelType = toOneTag[1]

				
				coalTags = coalTags[1:]
			}
		}

		
		if field.Type == toManyType {
			if len(coalTags) > 0 && strings.Count(coalTags[0], ":") > 0 {
				
				if strings.Count(coalTags[0], ":") > 1 {
					panic(`coal: expected to find a tag of the form 'coal:"name:type"' on to-many relationship`)
				}

				
				toManyTag := strings.Split(coalTags[0], ":")

				
				metaField.ToMany = true
				metaField.RelName = toManyTag[0]
				metaField.RelType = toManyTag[1]

				
				coalTags = coalTags[1:]
			}
		}

		
		if field.Type == hasOneType {
			
			if len(coalTags) != 1 || strings.Count(coalTags[0], ":") != 2 {
				panic(`coal: expected to find a tag of the form 'coal:"name:type:inverse"' on has-one relationship`)
			}

			
			hasOneTag := strings.Split(coalTags[0], ":")

			
			metaField.HasOne = true
			metaField.RelName = hasOneTag[0]
			metaField.RelType = hasOneTag[1]
			metaField.RelInverse = hasOneTag[2]

			
			coalTags = coalTags[1:]
		}

		
		if field.Type == hasManyType {
			
			if len(coalTags) != 1 || strings.Count(coalTags[0], ":") != 2 {
				panic(`coal: expected to find a tag of the form 'coal:"name:type:inverse"' on has-many relationship`)
			}

			
			hasManyTag := strings.Split(coalTags[0], ":")

			
			metaField.HasMany = true
			metaField.RelName = hasManyTag[0]
			metaField.RelType = hasManyTag[1]
			metaField.RelInverse = hasManyTag[2]

			
			coalTags = coalTags[1:]
		}

		
		metaField.Flags = coalTags
		if metaField.Flags == nil {
			metaField.Flags = []string{}
		}

		
		meta.Fields[metaField.Name] = metaField
		meta.OrderedFields = append(meta.OrderedFields, metaField)

		
		if metaField.BSONField != "" {
			
			if meta.DatabaseFields[metaField.BSONField] != nil {
				panic(fmt.Sprintf(`coal: duplicate BSON field "%s"`, metaField.BSONField))
			}

			
			meta.DatabaseFields[metaField.BSONField] = metaField
		}

		
		if metaField.JSONKey != "" {
			
			if meta.Attributes[metaField.JSONKey] != nil {
				panic(fmt.Sprintf(`coal: duplicate JSON key "%s"`, metaField.JSONKey))
			}

			
			meta.Attributes[metaField.JSONKey] = metaField
		}

		
		if metaField.RelName != "" {
			
			if meta.Relationships[metaField.RelName] != nil {
				panic(fmt.Sprintf(`coal: duplicate relationship "%s"`, metaField.RelName))
			}

			
			meta.Relationships[metaField.RelName] = metaField
		}

		
		for _, flag := range metaField.Flags {
			
			list, _ := meta.FlaggedFields[flag]

			
			list = append(list, metaField)

			
			meta.FlaggedFields[flag] = list
		}
	}

	
	metaCache[modelName] = meta

	return meta
}
func NewMeta ( model Model ) * Meta { // acquire mutex metaCacheMutex . Lock ( ) defer metaCacheMutex . Unlock ( ) // get type and name modelType := reflect . TypeOf ( model ) . Elem ( ) modelName := modelType . String ( ) // check if meta has already been cached meta , ok := metaCache [ modelName ] if ok { return meta } // create new meta meta = & Meta { Name : modelName , model : model , Fields : make ( map [ string ] * Field ) , DatabaseFields : make ( map [ string ] * Field ) , Attributes : make ( map [ string ] * Field ) , Relationships : make ( map [ string ] * Field ) , FlaggedFields : make ( map [ string ] [ ] * Field ) , } // iterate through all fields for i := 0 ; i < modelType . NumField ( ) ; i ++ { // get field field := modelType . Field ( i ) // get coal tag coalTag := field . Tag . Get ( " " ) // check for first field if i == 0 { // assert first field to be the base if field . Type != baseType { panic ( `coal: expected an embedded "coal.Base" as the first struct field` ) } // split tag baseTag := strings . Split ( coalTag , " " ) // check json tag if field . Tag . Get ( " " ) != " " { panic ( `coal: expected to find a tag of the form 'json:"-"' on "coal.Base""` ) } // check bson tag if field . Tag . Get ( " " ) != " " { panic ( `coal: expected to find a tag of the form 'bson:",inline"' on "coal.Base""` ) } // check tag if len ( baseTag ) > 2 || baseTag [ 0 ] == " " { panic ( `coal: expected to find a tag of the form 'coal:"plural-name[:collection]"' on "coal.Base""` ) } // infer plural and collection names meta . PluralName = baseTag [ 0 ] meta . Collection = baseTag [ 0 ] // infer collection if len ( baseTag ) == 2 { meta . Collection = baseTag [ 1 ] } continue } // parse individual tags coalTags := strings . Split ( coalTag , " " ) if len ( coalTag ) == 0 { coalTags = nil } // get field type fieldKind := field . Type . Kind ( ) if fieldKind == reflect . Ptr { fieldKind = field . Type . Elem ( ) . Kind ( ) } // prepare field metaField := & Field { Name : field . Name , Type : field . Type , Kind : fieldKind , JSONKey : getJSONFieldName ( & field ) , BSONField : getBSONFieldName ( & field ) , Optional : field . Type . Kind ( ) == reflect . Ptr , index : i , } // check if field is a valid to-one relationship if field . Type == toOneType || field . Type == optionalToOneType { if len ( coalTags ) > 0 && strings . Count ( coalTags [ 0 ] , " " ) > 0 { // check tag if strings . Count ( coalTags [ 0 ] , " " ) > 1 { panic ( `coal: expected to find a tag of the form 'coal:"name:type"' on to-one relationship` ) } // parse special to-one relationship tag toOneTag := strings . Split ( coalTags [ 0 ] , " " ) // set relationship data metaField . ToOne = true metaField . RelName = toOneTag [ 0 ] metaField . RelType = toOneTag [ 1 ] // remove tag coalTags = coalTags [ 1 : ] } } // check if field is a valid to-many relationship if field . Type == toManyType { if len ( coalTags ) > 0 && strings . Count ( coalTags [ 0 ] , " " ) > 0 { // check tag if strings . Count ( coalTags [ 0 ] , " " ) > 1 { panic ( `coal: expected to find a tag of the form 'coal:"name:type"' on to-many relationship` ) } // parse special to-many relationship tag toManyTag := strings . Split ( coalTags [ 0 ] , " " ) // set relationship data metaField . ToMany = true metaField . RelName = toManyTag [ 0 ] metaField . RelType = toManyTag [ 1 ] // remove tag coalTags = coalTags [ 1 : ] } } // check if field is a valid has-one relationship if field . Type == hasOneType { // check tag if len ( coalTags ) != 1 || strings . Count ( coalTags [ 0 ] , " " ) != 2 { panic ( `coal: expected to find a tag of the form 'coal:"name:type:inverse"' on has-one relationship` ) } // parse special has-one relationship tag hasOneTag := strings . Split ( coalTags [ 0 ] , " " ) // set relationship data metaField . HasOne = true metaField . RelName = hasOneTag [ 0 ] metaField . RelType = hasOneTag [ 1 ] metaField . RelInverse = hasOneTag [ 2 ] // remove tag coalTags = coalTags [ 1 : ] } // check if field is a valid has-many relationship if field . Type == hasManyType { // check tag if len ( coalTags ) != 1 || strings . Count ( coalTags [ 0 ] , " " ) != 2 { panic ( `coal: expected to find a tag of the form 'coal:"name:type:inverse"' on has-many relationship` ) } // parse special has-many relationship tag hasManyTag := strings . Split ( coalTags [ 0 ] , " " ) // set relationship data metaField . HasMany = true metaField . RelName = hasManyTag [ 0 ] metaField . RelType = hasManyTag [ 1 ] metaField . RelInverse = hasManyTag [ 2 ] // remove tag coalTags = coalTags [ 1 : ] } // save additional tags as flags metaField . Flags = coalTags if metaField . Flags == nil { metaField . Flags = [ ] string { } } // add field meta . Fields [ metaField . Name ] = metaField meta . OrderedFields = append ( meta . OrderedFields , metaField ) // add db fields if metaField . BSONField != " " { // check existence if meta . DatabaseFields [ metaField . BSONField ] != nil { panic ( fmt . Sprintf ( `coal: duplicate BSON field "%s"` , metaField . BSONField ) ) } // add field meta . DatabaseFields [ metaField . BSONField ] = metaField } // add attributes if metaField . JSONKey != " " { // check existence if meta . Attributes [ metaField . JSONKey ] != nil { panic ( fmt . Sprintf ( `coal: duplicate JSON key "%s"` , metaField . JSONKey ) ) } // add field meta . Attributes [ metaField . JSONKey ] = metaField } // add relationships if metaField . RelName != " " { // check existence if meta . Relationships [ metaField . RelName ] != nil { panic ( fmt . Sprintf ( `coal: duplicate relationship "%s"` , metaField . RelName ) ) } // add field meta . Relationships [ metaField . RelName ] = metaField } // add flagged fields for _ , flag := range metaField . Flags { // get list list , _ := meta . FlaggedFields [ flag ] // add field list = append ( list , metaField ) // save list meta . FlaggedFields [ flag ] = list } } // cache meta metaCache [ modelName ] = meta return meta }
// NewMeta returns the Meta structure for the passed Model.
//
// Note: This method panics if the passed Model has invalid fields and tags.
NewMeta returns the Meta structure for the passed Model . Note : This method panics if the passed Model has invalid fields and tags .