I have an ActiveRecord model in my rails app which I would like to map to some other Ruby object. These models will hit external apis and the main thing that will change between the classes is the field name and some api specifics. In the example below the Person needs to map to the other two Api specific classes.
class Person
include Virtus
attribute :first_name, String
attribute :last_name, String
attribute :gender, String
end
class PersonApi1
include Virtus
attribute :forename, String
attribute :surname, String
attribute :gender, String
end
class PersonApi2
include Virtus
attribute :firstname, String
attribute :secondname, String
attribute :gender, String
end
Is there a mapping gem available that can do this type of mapping? Has anyone else come across a similar mapping problem and how have you approached it?
If I were to roll my own I would think about some sort of hash to map out each of the fields.
The .net world has Automapper where you can say something like below. Is something similar available in Ruby?
public class CalendarEvent
{
public DateTime Date { get; set; }
public string Title { get; set; }
}
public class CalendarEventForm
{
public DateTime EventDate { get; set; }
public int EventHour { get; set; }
public int EventMinute { get; set; }
public string Title { get; set; }
}
//AutoMapper mapping
Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))
.ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.EventDate.Hour))
.ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.EventDate.Minute));
From the automapper wiki https://github.com/AutoMapper/AutoMapper/wiki/Projection
I don't know of any gem that does this, so I put one together yesterday - give it a try if you want - https://github.com/house9/simple_attribute_mapper
UPDATE: version 0.0.2 now supports the following mappings
default (source and target matching attribute names)
standard source to target attribute
nested source to target attribute
composite (lambda style) source to target attribute
Related
Below is the Automapping profile. An error is thrown on the destination Id being of the wrong input format.
The destination Id property is not even being mapped to.
public class AspGetUserLoginReturnModelToSRSDLUserLoginModel : Profile
{
public AspGetUserLoginReturnModelToSRSDLUserLoginModel()
{
CreateMap<asp_GetUserLoginReturnModel, SRSDLUserLoginModel>()
.ForMember(dest => dest.UserPassword, opt => opt.MapFrom(src => src.PasswordHash))
.ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.Id)
;
}
}
Now the destination class SRSDLUserLoginModel is derived from a base class that has a public accessor named Id which is type of nullable long.
The source Id property used above is of type string.
As the code shown above the source Id is mapped to the destination UserId property.
But Automapper is mapping the source Id to the destination's based classes Id property.
WHY IS THIS HAPPENING?
The code is straight forward and there is nothing new trying to be accomplished.
It Automapper like most everything other packages that I have work with?
In the full/classic .NET framework, the packages worked fine, but in .NET Core those packages just suck?
// source class
public partial class asp_GetUserLoginReturnModel
{
public string Id { get; set; }
}
// destination classes
public class SRSDLUserLoginModel: SRSDLModel, ISRSDLUserLoginModel
{
public string UserId { get; set; }
public string UserPassword { get; set; }
// [...]
}
public class SRSDLModel : ISRSDLModel
{
public long? Id { get; set; }
// [...]
}
AutoMapper will map properties with the same name by default so you don't have to write them all the time. In this case it tries to set the SRSDLModel.Id property with the value from the asp_GetUserLoginReturnModel.Id property. As you already noticed, that doesn't work because of the mismatch between long? and string.
To fix this, use the .Ignore() method in your mapping to specify that this specific property should no be set during mapping. The configuration might look like this:
.ForMember(dest => dest.Id, i => i.Ignore());
I am in the process of a refactor and utilizing AutoMapper (6.2.1) to help us in formatting of API returns that conform to a specific contract. The DTO objects we are using internally are meant to simplify our understanding of the data before returning the data in the more complex type.
The Issue:
I have a DTO with a List<T> where I need one of the properties of <T> to be mapped to the collection in the more complex type. This is actually pretty straight forward but the problem is, what if the collection I am trying to map to in the more complex type is in fact inside another "higher" collection. Essentially I am in a little bit of a collection inside a collection problem.
Ex: DTO
public class ItemDTO
{
List<ItemDescriptionDTO> ItemDescriptions { get; set; }
}
public class ItemDescriptionDTO
{
public string Description { get; set; }
}
More Complex object I need to map to and do not have control over
public class ComplexThing // This is the object I need (It's ugly, I hate it too)
{
public ComplexItemDescriptions { get; set; }
}
public class ComplexItemDescriptions
{
public List<ComplexItemDescription> ComplexItemDescription { get; set; }
}
public class ComplexItemDescription
{
public UnparsedItemDescriptions UnparsedItemDescriptions { get; set; }
}
public class UnparsedItemDescriptions
{
public List<UnparsedItemDescription> UnparsedItemDescription { get; set; }
}
public class UnparsedItemDescription
{
public string UnparsedItemDescription { get; set; }
}
In essence I need to take the Description in my simple ItemDescriptionDTOand map that through this awful chain of nested objects to set the UnparsedItemDescription
I am able to properly map from UnparsedItemDescription all the way to the ComplexItemDescription but going higher than that to the Complex thing is giving me some trouble.
This is the mapping I have so far:
config.CreateMap<ItemDescriptionDTO, UnparsedItemDescription>()
.ForMember(dest => dest.UnparsedItemDescription, map => map.MapFrom(src => src.Description));
config.CreateMap<ItemDTO, UnparsedItemDescriptions>()
.ForMember(dest => dest.UnparsedItemDescription, map => map.MapFrom(src => src.ItemDescritpions));
config.CreateMap<ItemDTO, ComplexItemDescription>()
.ForPath(dest => dest.UnparsedItemDescriptions.UnparsedItemDescription, map => map.MapFrom(src => src.ItemDescriptions));
// This is where we start failing (I think I am just not understanding something fundamental to how Automapper does things or I am up against some silly edge case
config.CreateMap<ItemDTO, ComplexItemDescriptions>()
.ForMember(dest => dest.ComplexItemDescription, map => map.MapFrom(src => src.ItemDescriptions));
I need the ComplexThing because there are other properties in that class that are returned so I can't just get away with returning say a ComplexItemDescription
I would appreciate an assistance you could give. I admittedly have a base understanding on how Automapper works (which I am in the process of trying to get better at) but this is really throwing me at the moment.
I use automapper to map models between each user.
I can also do this to map a property of a specific type to a userId
cfg.CreateMap<Model, Command>()
.ForMember(dest => dest.UserId, opt => ClaimsPrincipal.Current.UserId))
But I can't find a way to make this to some kind of convention so that everytime models are mapped and there is a Property "UserId" it should be set to ClaimsPrincipal.Current.UserId.
Is this now working ?
Any advice?
Have you tried the MapTo Attribute?
public class Foo
{
[MapTo("SourceOfBar")]
public int Bar { get; set; }
}
I am using Dto for data transfer view to domain and use automapper for mapping.
My problem is, the property that not exist dto but i need to set before mapping to domain.
I have tried to use Linq query to get external data from db on before and after mapping methods but linq query giving error.
Sample below
FooDto
public class FooDto
{
public int MyProperty1 {get;set;}
}
FooDomain
public class Foo
{
public int MyProperty1 {get;set;}
public int MyProperty2 {get;set;}
public int Foo2ID {get;set;}
public virtual Foo2 Foo2 {get;set;}
}
Foo2Domain
public class Foo2
{
public int ID {get;set;}
public int MyProperty1 {get;set;}
}
**AutoMapper*
Mapper.Initialize(x =>
{
x.CreateMap<FooDto, Foo>().BeforeMap(
(src, dest) =>dest.MyProperty2 = dest.Foo2.MyProperty1);
}
I want to set Foo2.MyProperty1 to Foo.MyProperty2 using mapping.
This answer might need to be edited if my assumptions are wrong. The assumption I am making is that the source object has the right data. Based on your sample it looks like your source object's MyProperty2 can be set in the destination object so do map this all you would need to do is:
Mapper.Initialize(x =>
{
x.CreateMap<FooDto, Foo>()
.ForMember(dest => dest.MyProperty2, opt => opt.MapFrom(src => src.MyProperty1))
.ForMember(dest => dest.Foo2.MyProperty1, opt => opt.MapFrom(src => src.MyProperty1));
}
What this code does is it tells AutoMapper when I give you an object of Type FooDto and I am requesting an object of Type Foo. For the destination objects property 'Foo2.MyProperty1' and 'MyProperty2', use the options method MapFrom. Go to the source Object get the MyProperty1 and assign it's value to my destination objects MyProperty2 and Foo2.MyProperty1.
I think this would fix you up.
Right sorry I corrected the answer
I have 2 classes and want to configure mapping from one to another
public class Customer
{
public long CustomerId{get;set;}
public string ShippingAddres{get;set;}
public string BillingAddress{get;set;}
public DateTime DateOfBirth {get;set;}
}
public class Item2
{
public long customer_id{get;set;}
public string shipping_addres{get;set;}
public string billing_address{get;set;}
public DateTime date_of_birth {get;set;}
}
I tried to search the answer but all answers that I find tell to map fields manually:
.ForMember(dest => dest.CustomerId, opt => opt.MapFrom(src => src.customer_id));
I don't want to map each property manually, since I have a lot of properties. I want to configure some rule:
to split property name by '_' and join strings to CamelCase
or in pseudo code:
cfg.AddConditionalObjectMapper().Where((source, destination) => s.Name.Replace("(_)([a-z])","\U1") == d.Name );
The question is not about the Regex, I need to know how to configure such rules in Auto-mapper?
I found a way to configure it, but this method is not universal:
cfg.AddMemberConfiguration().AddName< ReplaceName >( _ => _.AddReplace( "_", "" ) );
cfg.AddMemberConfiguration().AddName< CaseInsensitiveName >();