3

I have two objects, a UserData object and a User object. At the moment they look like this:

user_data.rb

class UserData < ActiveRecord::Base
  self.table = "users"
end

user.rb

class User
  attr_accessor :first_name, :last_name, :email, :password
end

What I'm trying to do now is map the UserData object to the User object and the other way around. For the first scenario I have the following code:

module Mappers
  module UserMapper
   def map user
     @user = UserData.new
     @user.first_name = user.first_name
     @user.last_name = user.last_name
     @user.password = user.password
     @user.email = user.email
   end
  end
end

However, the problem is that whenever I add a new column to the users table, I have to add the code in the mapper as well. And when I have tons of objects inside my code that need to be mapped, it's going to be really messy. My question is: Is there a better / dynamic way for doing this? The ideal situation would be when I don't have to touch the Mapper code anymore.

Frank Levering
  • 401
  • 3
  • 16
  • Why don't you inherit your `User` class from `ActiveRecord::Base`. Why do you want to keep two separate classes and map the attributes? – Rohit Jangid Mar 14 '16 at 07:41
  • 1
    I want to isolate my data models and and don't be aware of the existence of Active Record anywhere inside my application, besides the data models itself. – Frank Levering Mar 14 '16 at 07:56
  • The data should be kept with the model. That why they exist. If you want to keep the logic isolated, I would suggest using service layer in your application. But keep the data where it actually belongs. For reference, you could read [this article](https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services) – Rohit Jangid Mar 14 '16 at 08:11

3 Answers3

3

You can get a list of attribute names from an ActiveRecord model by calling the attribute_names class method. Armed with that, it's easy to iterate over them and fetch and assign each attribute in turn:

module UserMapper
  def map(user)
    @user = UserData.new do |user_data|
      UserData.attribute_names.each do |attr_name|
        next unless user.respond_to?(attr_name)
        user_data[attr_name] = user.public_send(attr_name)
      end
    end
  end
end

You could use the same method to dynamically create attr_accessors in your User class, if you wanted:

class User
  attr_accessor *UserData.attribute_names
end
Jordan Running
  • 102,619
  • 17
  • 182
  • 182
2

You can use a bit ruby meta programming to complete this:

class User
  ATTRS = [:first_name, :last_name, :email, :password]
  attr_accessor *ATTRS
end

module Mappers
  module UserMapper
   def map(user)
     @user = UserData.new
     User::ATTRS.each do |attr|
       @user.send("#{attr}=", user.send(attr))
     end
   end
  end
end

Then you just modify ATTRS and everything will work automatically!

Hieu Pham
  • 6,577
  • 2
  • 30
  • 50
1

You could do something based on this question: Fastest/One-liner way to list attr_accessors in Ruby?

module Mappers
  module UserMapper
    def map user
      @user = UserData.new
      user.attributes.inspect.each do |attr|
        @user.public_send("#{attr}=", user.public_send(attr))
      end
    end
  end
end
Community
  • 1
  • 1
Albin
  • 2,912
  • 1
  • 21
  • 31