Git Product home page Git Product logo

datamappify's Introduction

Datamappify is no longer being maintained. It started off with a noble goal, unfortunately due to it being on the critical path of our project, we have decided not to continue developing it given the lack of development time from me.

Feel free to read the README and browse the code, I still believe in the solutions for this particular domain.

For a more active albeit still young project, check out Lotus::Model.


Datamappify Gem Version Build Status Coverage Status Code Climate

Compose, decouple and manage domain logic and data persistence separately. Works particularly great for composing form objects!

Overview

The typical Rails (and ActiveRecord) way of building applications is great for small to medium sized projects, but when projects grow larger and more complex, your models too become larger and more complex - it is not uncommon to have god classes such as a User model.

Datamappify tries to solve two common problems in web applications:

  1. The coupling between domain logic and data persistence.
  2. The coupling between forms and models.

Datamappify is loosely based on the Repository Pattern and Entity Aggregation, and is built on top of Virtus and existing ORMs (ActiveRecord and Sequel, etc).

There are three main design goals:

  1. To utilise the powerfulness of existing ORMs so that using Datamappify doesn't interrupt too much of your current workflow. For example, Devise would still work if you use it with a UserAccount ActiveRecord model that is attached to a User entity managed by Datamappify.
  2. To have a flexible entity model that works great with dealing with form data. For example, SimpleForm would still work with nested attributes from different ORM models if you map entity attributes smartly in your repositories managed by Datamappify.
  3. To have a set of data providers to encapsulate the handling of how the data is persisted. This is especially useful for dealing with external data sources such as a web service. For example, by calling UserRepository.save(user), certain attributes of the user entity are now persisted on a remote web service. Better yet, dirty tracking and lazy loading are supported out of the box!

Datamappify consists of three components:

  • Entity contains models behaviour, think an ActiveRecord model with the persistence specifics removed.
  • Repository is responsible for data retrieval and persistence, e.g. find, save and destroy, etc.
  • Data as the name suggests, holds your model data. It contains ORM objects (e.g. ActiveRecord models).

Below is a high level and somewhat simplified overview of Datamappify's architecture.

Note: Datamappify is NOT affiliated with the Datamapper project.

Built-in ORMs for Persistence

You may implement your own data provider and criteria, but Datamappify comes with build-in support for the following ORMS:

  • ActiveRecord
  • Sequel

Requirements

  • ruby 2.0+
  • ActiveModel 4.0+

Installation

Add this line to your application's Gemfile:

gem 'datamappify'

Usage

Entity

Entity uses Virtus DSL for defining attributes and ActiveModel::Validations DSL for validations.

The cool thing about Virtus is that all your attributes get coercion for free!

Below is an example of a User entity, with inline comments on how some of the DSLs work.

class User
  include Datamappify::Entity

  attribute :first_name,     String
  attribute :last_name,      String
  attribute :age,            Integer
  attribute :passport,       String
  attribute :driver_license, String
  attribute :health_care,    String

  # Nested entity composition - composing the entity with attributes and validations from other entities
  #
  #   class Job
  #     include Datamappify::Entity
  #
  #     attributes :title, String
  #     validates  :title, :presence => true
  #   end
  #
  #   class User
  #     # ...
  #     attributes_from Job
  #   end
  #
  # essentially equals:
  #
  #   class User
  #     # ...
  #     attributes :title, String
  #     validates  :title, :presence => true
  #   end
  attributes_from Job

  # optionally you may prefix the attributes, so that:
  #
  #   class Hobby
  #     include Datamappify::Entity
  #
  #     attributes :name, String
  #     validates  :name, :presence => true
  #   end
  #
  #   class User
  #     # ...
  #     attributes_from Hobby, :prefix_with => :hobby
  #   end
  #
  # becomes:
  #
  #   class User
  #     # ...
  #     attributes :hobby_name, String
  #     validates  :hobby_name, :presence => true
  #   end
  attributes_from Hobby, :prefix_with => :hobby

  # Entity reference
  #
  # `references` is a convenient method for:
  #
  #   attribute :account_id, Integer
  #   attr_accessor :account
  #
  # and it assigns `account_id` the correct value:
  #
  #   user.account = account #=> user.account_id = account.id
  references :account

  validates :first_name, :presence => true,
                         :length   => { :minimum => 2 }
  validates :passport,   :presence => true,
                         :length   => { :minimum => 8 }

  def full_name
    "#{first_name} #{last_name}"
  end
end

Entity inheritance

Inheritance is supported for entities, for example:

class AdminUser < User
  attribute :level, Integer
end

class GuestUser < User
  attribute :expiry, DateTime
end

Lazy loading

Datamappify supports attribute lazy loading via the Lazy module.

class User
  include Datamappify::Entity
  include Datamappify::Lazy
end

When an entity is lazy loaded, only attributes from the primary source (e.g. User entity's primary source would be ActiveRecord::User as specified in the corresponding repository) will be loaded. Other attributes will only be loaded once they are called. This is especially useful if some of your data sources are external web services.

Repository

Repository maps entity attributes to DB columns - better yet, you can even map attributes to different ORMs!

Below is an example of a repository for the User entity, you can have more than one repositories for the same entity.

class UserRepository
  include Datamappify::Repository

  # specify the entity class
  for_entity User

  # specify the default data provider for unmapped attributes
  # optionally you may use `Datamappify.config` to config this globally
  default_provider :ActiveRecord

  # specify any attributes that need to be mapped
  #
  # for attributes mapped from a different source class, a foreign key on the source class is required
  #
  # for example:
  #   - 'last_name' is mapped to the 'User' ActiveRecord class and its 'surname' attribute
  #   - 'driver_license' is mapped to the 'UserDriverLicense' ActiveRecord class and its 'number' attribute
  #   - 'passport' is mapped to the 'UserPassport' Sequel class and its 'number' attribute
  #   - attributes not specified here are mapped automatically to 'User' with provider 'ActiveRecord'
  map_attribute :last_name,      :to => 'User#surname'
  map_attribute :driver_license, :to => 'UserDriverLicense#number'
  map_attribute :passport,       :to => 'UserPassport#number',   :provider => :Sequel
  map_attribute :health_care,    :to => 'UserHealthCare#number', :provider => :Sequel

  # alternatively, you may group attribute mappings if they share certain options:
  group :provider => :Sequel do
    map_attribute :passport,    :to => 'UserPassport#number'
    map_attribute :health_care, :to => 'UserHealthCare#number'
  end

  # attributes can also be reverse mapped by specifying the `via` option
  #
  # for example, the below attribute will look for `hobby_id` on the user object,
  # and map `hobby_name` from the `name` attribute of `ActiveRecord::Hobby`
  #
  # this is useful for mapping form fields (similar to ActiveRecord's nested attributes)
  map_attribute :hobby_name, :to => 'Hobby#name', :via => :hobby_id

  # by default, Datamappify maps attributes using an inferred reference (foreign) key,
  # for example, the first mapping below will look for the `user_id` key in `Bio`,
  # the second mapping below will look for the `person_id` key in `Bio` instead
  map_attribute :bio, :to => 'Bio#body'
  map_attribute :bio, :to => 'Bio#body', :reference_key => :person_id
end

Repository inheritance

Inheritance is supported for repositories when your data structure is based on STI (Single Table Inheritance), for example:

class AdminUserRepository < UserRepository
  for_entity AdminUser
end

class GuestUserRepository < UserRepository
  for_entity GuestUser

  map_attribute :expiry, :to => 'User#expiry_date'
end

In the above example, both repositories deal with the ActiveRecord::User data model.

Override mapped data models

Datamappify repository by default creates the underlying data model classes for you. For example:

map_attribute :driver_license, :to => 'UserData::DriverLicense#number'

In the above example, a Datamppify::Data::Record::ActiveRecord::UserDriverLicense ActiveRecord model will be created. If you would like to customise the data model class, you may do so by creating one either under the default namespace or under the Datamappify::Data::Record::NameOfDataProvider namespace:

module UserData
  class DriverLicense < ActiveRecord::Base
    # your customisation...
  end
end
module Datamappify::Data::Record::ActiveRecord::UserData
  class DriverLicense < ::ActiveRecord::Base
    # your customisation...
  end
end

Repository APIs

More repository APIs are being added, below is a list of the currently implemented APIs.

Retrieving an entity

Accepts an id.

user = UserRepository.find(1)

Checking if an entity exists in the repository

Accepts an entity.

UserRepository.exists?(user)

Retrieving all entities

Returns an array of entities.

users = UserRepository.all

Searching entities

Returns an array of entities.

Simple
users = UserRepository.where(:first_name => 'Fred', :driver_license => 'AABBCCDD')
Match
users = UserRepository.match(:first_name => 'Fre%', :driver_license => '%bbcc%')
Advanced

You may compose search criteria via the criteria method.

users = UserRepository.criteria(
  :where => {
    :first_name => 'Fred'
  },
  :order => {
    :last_name => :asc
  },
  :limit => [10, 20]
)

Currently implemented criteria options:

  • where(Hash)
  • match(Hash)
  • order(Hash)
  • limit(Array<limit(Integer), offset(Integer)>)

Note: it does not currently support searching attributes from different data providers.

Saving/updating entities

Accepts an entity.

There is also save! that raises Datamappify::Data::EntityNotSaved.

UserRepository.save(user)

Datamappify supports attribute dirty tracking - only dirty attributes will be saved.

Mark attributes as dirty

Sometimes it's useful to manually mark the whole entity, or some attributes in the entity to be dirty. In this case, you could:

UserRepository.states.mark_as_dirty(user) # marks the whole entity as dirty

UserRepository.states.find(user).changed?            #=> true
UserRepository.states.find(user).first_name_changed? #=> true
UserRepository.states.find(user).last_name_changed?  #=> true
UserRepository.states.find(user).age_changed?        #=> true

Or:

UserRepository.states.mark_as_dirty(user, :first_name, :last_name) # marks only first_name and last_name as dirty

UserRepository.states.find(user).changed?            #=> true
UserRepository.states.find(user).first_name_changed? #=> true
UserRepository.states.find(user).last_name_changed?  #=> true
UserRepository.states.find(user).age_changed?        #=> false

Destroying an entity

Accepts an entity.

There is also destroy! that raises Datamappify::Data::EntityNotDestroyed.

Note that due to the attributes mapping, any data found in mapped records are not touched. For example the corresponding ActiveRecord::User record will be destroyed, but ActiveRecord::Hobby that is associated will not.

UserRepository.destroy(user)

Initialising an entity

Accepts an entity class and returns a new entity.

This is useful for using before_init and after_init callbacks to set up the entity.

UserRepository.init(user_class) #=> user

Callbacks

Datamappify supports the following callbacks via Hooks:

  • before_init
  • before_load
  • before_find
  • before_create
  • before_update
  • before_save
  • before_destroy
  • after_init
  • after_load
  • after_find
  • after_create
  • after_update
  • after_save
  • after_destroy

Callbacks are defined in repositories, and they have access to the entity. For example:

class UserRepository
  include Datamappify::Repository

  before_create :make_me_admin
  before_create :make_me_awesome
  after_save    :make_me_smile

  private

  def make_me_admin(entity)
    # ...
  end

  def make_me_awesome(entity)
    # ...
  end

  def make_me_smile(entity)
    # ...
  end

  # ...
end

Note: Returning either nil or false from the callback will cancel all subsequent callbacks (and the action itself, if it's a before_ callback).

Association

Datamappify also supports entity association. It is experimental and it currently supports the following association types:

  • belongs_to (partially implemented)
  • has_one
  • has_many

Set up your entities and repositories:

# entities

class User
  include Datamappify::Entity

  has_one  :title, :via => Title
  has_many :posts, :via => Post
end

class Title
  include Datamappify::Entity

  belongs_to :user
end

class Post
  include Datamappify::Entity

  belongs_to :user
end

# repositories

class UserRepository
  include Datamappify::Repository

  for_entity User

  references :title, :via => TitleRepository
  references :posts, :via => PostRepository
end

class TitleRepository
  include Datamappify::Repository

  for_entity Title
end

class PostRepository
  include Datamappify::Repository

  for_entity Post
end

Usage examples:

new_post         = Post.new(post_attributes)
another_new_post = Post.new(post_attributes)
user             = UserRepository.find(1)
user.title       = Title.new(title_attributes)
user.posts       = [new_post, another_new_post]

persisted_user   = UserRepository.save!(user)

persisted_user.title #=> associated title
persisted_user.posts #=> an array of associated posts

Nested attributes in forms

Like ActiveRecord and ActionView, Datamappify also supports nested attributes via fields_for or simple_fields_for.

# slim template

= simple_form_for @post do |f|
  = f.input :title
  = f.input :body

  = f.simple_fields_for :comment do |fp|
    = fp.input :author_name
    = fp.input :comment_body

Default configuration

You may configure Datamappify's default behaviour. In Rails you would put it in an initializer file.

Datamappify.config do |c|
  c.default_provider = :ActiveRecord
end

Built-in extensions

Datamappify ships with a few extensions to make certain tasks easier.

Kaminari

Use Criteria with page and per.

UserRepository.criteria(
  :where => {
    :gender => 'male',
    :age    => 42
  },
  :page => 1,
  :per  => 10
)

API Documentation

More Reading

You may check out this article for more examples.

Changelog

Refer to CHANGELOG.

Todo

  • Performance tuning and query optimisation
  • Authoritative source.
  • Support for configurable primary keys and reference (foreign) keys.

Similar Projects

Credits

License

Licensed under MIT

Bitdeli Badge

datamappify's People

Contributors

bitdeli-chef avatar fredwu avatar garrow avatar jamesmoriarty avatar mattbeedle avatar solnic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

datamappify's Issues

An alternate map_attribute API

Upon seeing it, and even after understanding what it does, I find the map_attribute API surprising. Specifically the second argument.

map_attribute :account_username,  'ActiveRecord::UserAccount#username'

That string looks like a namespace. And what if my UserAccount object is namespaced under an Admin module? Does it become

map_attribute :account_username,  'ActiveRecord::Admin::UserAccount#username'

What about the following API:

map_attribute :account_username,  to: 'UserAccount#username', with: 'ActiveRecord'

The :with could be optional, defaulting the the default_provider specified elsewhere. This also reduces some unnecessary duplication. Also, the :to argument is now more intention revealing, and a more direct mapping to a Ruby object and method name.

In the case of a namespaced object you could say to: 'Admin::UserAccount#username'. And in the case of an ambiguous constant, you could fully qualify via to: '::UserAccount#username'.

Thoughts?

Does it work for Rails3?

datamappify-0.2.2/vendor/auto_migrations/lib/auto_migrations.rb:12:in `run'

undefined method `drop_unused_tables' for #ActiveRecord::ConnectionAdapters::Mysql2Adapter:0x7fba4adeda58

Hi Fred!

This isn't an issue. I just wanted to reach out to you and ask you a couple of questions regarding your blog post. Awesome post by the way.

I'm a new engineer and have been hearing a lot of the pain points with ActiveRecord you mention in your blog post at work. I haven't had enough experience yet to have the issues you have. Some of the engineers here even mentioned creating something like you have done in this repo, where you implemented the Repository pattern.

I have a couple of questions for you, if you don't mind:

1 - Did this project help you at work?
2 - Did you use this project in production/professionally?
3 - How was it creating a gem and having to maintain it?
4 - What were your takeaways from this project?

Thank you so much @fredwu!

NoMethodError: undefined method `split' for nil:NilClass

Is this a bug, or did I configure something wrong?

Entity

class Deployment
  include Datamappify::Entity

  attribute :user_id,         Integer
  attribute :branch_name,     String
  attribute :revision,        String
  attribute :heroku_app_name, String

  validates :user_id, :heroku_app_name, :branch_name, presence: true
end

Repository

class DeploymentRepository
  include Datamappify::Repository

  for_entity Deployment

  default_provider :ActiveRecord

  # Not sure what model to map this onto yet
  map_attribute :user_id,         'DeploymentData#user_id'
  map_attribute :branch_name,     'DeploymentData#branch_name'
  map_attribute :repository,      'DeploymentData#repository'
  map_attribute :heroku_app_name, 'DeploymentData#heroku_app_name'

  after_create :deploy!

  private

  def deploy!
    # Do some deployment stuff here...
  end
end

Model

class DeploymentData < ActiveRecord::Base
end

Error

2.0.0p247 :001 > d = Deployment.new(branch_name: 'test', heroku_app_name: 'my-test-app', user_id: 1)
 => #<Deployment> 
2.0.0p247 :002 > DeploymentRepository.save(d)
   (0.1ms)  BEGIN
   (0.1ms)  ROLLBACK
NoMethodError: undefined method `split' for nil:NilClass
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper/attribute.rb:156:in `parse_source'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper/attribute.rb:46:in `initialize'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:100:in `new'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:100:in `map_custom_attribute'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:88:in `block in custom_attributes'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:87:in `each'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:87:in `collect'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:87:in `custom_attributes'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:73:in `custom_attribute_names'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:67:in `default_attribute_names'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:80:in `default_attributes'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:49:in `attributes'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/data/mapper.rb:55:in `classified_attributes'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/query_method/method.rb:110:in `block in attributes_walker'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:202:in `block in transaction'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:210:in `within_new_transaction'
... 9 levels...
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/query_method/save.rb:36:in `create_or_update'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/query_method/save.rb:8:in `block in perform'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/unit_of_work/persistent_states.rb:47:in `call'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/unit_of_work/persistent_states.rb:47:in `update'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/query_method/save.rb:7:in `perform'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/query_methods.rb:48:in `block in create'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/query_method/callbacks.rb:29:in `call'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/query_method/callbacks.rb:29:in `run_callbacks'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/query_methods.rb:47:in `create'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.1/lib/datamappify/repository/query_methods.rb:85:in `save'
    from (irb):2
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.0/lib/rails/commands/console.rb:90:in `start'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.0/lib/rails/commands/console.rb:9:in `start'
    from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.0/lib/rails/commands.rb:64:in `<top (required)>'
    from bin/rails:4:in `require'
    from bin/rails:4:in `<main>'2.0.0p247 :003 > 

UUID primary keys

Using Postgres UUID primary keys isn't supported right now.

create_table :users, id: false do |t|
  t.primary_key :id, :uuid
end


UserRepository.find("4224109b-a9c8-4d8b-9f0b-f2a8f2a73174")

NoMethodError: undefined method `keys' for "4224109b-a9c8-4d8b-9f0b-f2a8f2a73174":String
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.2/lib/datamappify/data/criteria/relational/find_multiple.rb:35:in `updated_attributes'
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.2/lib/datamappify/data/criteria/relational/find_multiple.rb:16:in `initialize'
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.2/lib/datamappify/data/provider/common_provider.rb:94:in `new'
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.2/lib/datamappify/data/provider/common_provider.rb:94:in `build_criteria'
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.2/lib/datamappify/repository/query_method/method.rb:64:in `dispatch_criteria_to_default_source'
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.2/lib/datamappify/repository/query_method/find_multiple.rb:16:in `perform'
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/datamappify-0.53.2/lib/datamappify/repository/query_methods.rb:31:in `find'
        from (irb):2
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.0/lib/rails/commands/console.rb:90:in `start'
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.0/lib/rails/commands/console.rb:9:in `start'
        from /Users/hansolo/.rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.0/lib/rails/commands.rb:64:in `<top (required)>'
        from bin/rails:4:in `require'
        from bin/rails:4:in `<main>'

I'm a bit busy at the moment, but I'll try to get a pull request sorted out for this as soon as I have the time, unless someone else gets there first.

for_entity namespace

why does not for_entity support namespace?
for example :

for_entity Hero::Wang

error message :

wrong constant name Hero::Wang

Add support for Her

It would be nice with support for her. It should be fairly similar to ActiveRecord implementation.

Change behaviour of default_provider

I've come across a scenario where default_provider is not behaving the way I'd expect it to. I have an entity like this, with some attributes I'd like to persist, and some that I'd prefer not to.

class Quote
  include Datamappify::Entity

  # Attributes I want to persist
  attribute :departs_on, Date
  attribute :returns_on, Date

  # Attributes I don't want to persist
  attribute :num_travellers, Integer
  attribute :policy_holder, PolicyHolder
end

With a repository like this:

class QuoteRepository
  include Datamappify::Repository

  for_entity Quote

  default_provider :ActiveRecord

  map_attribute :departs_on, :to => 'Quote#departs_on'
  map_attribute :returns_on, :to => 'Quote#returns_on'
end

When I persist the entity via the repository, it tries to persist num_travellers and policy_holder. After some digging I figured this was due to the default_provider.

If I remove the default_provider like so:

class QuoteRepository
  include Datamappify::Repository

  for_entity Quote

  group :provider => :ActiveRecord do
    map_attribute :departs_on, :to => 'Quote#departs_on'
    map_attribute :returns_on, :to => 'Quote#returns_on'
  end
end

I think persistence works as I'm expecting, with only the mapped attributes being persisted.

However the repository finder doesn't work. If I do something like this:

QuoteRepository.find(1)

I get the following exception:

TypeError: wrong argument type nil (expected String)
    from /Users/benaskins/.rvm/gems/ruby-2.0.0-p195/bundler/gems/datamappify-b4646c3a4b88/lib/datamappify/data/mapper.rb:33:in `const_get'
    from /Users/benaskins/.rvm/gems/ruby-2.0.0-p195/bundler/gems/datamappify-b4646c3a4b88/lib/datamappify/data/mapper.rb:33:in `default_provider'
    from /Users/benaskins/.rvm/gems/ruby-2.0.0-p195/bundler/gems/datamappify-b4646c3a4b88/lib/datamappify/repository/query_method/method.rb:69:in `dispatch_criteria_to_default_source'
    from /Users/benaskins/.rvm/gems/ruby-2.0.0-p195/bundler/gems/datamappify-b4646c3a4b88/lib/datamappify/repository/query_method/find.rb:10:in `perform'
    from /Users/benaskins/.rvm/gems/ruby-2.0.0-p195/bundler/gems/datamappify-b4646c3a4b88/lib/datamappify/repository/query_methods.rb:28:in `find'

I'd like to either go down the path of removing find's dependency on default_provider, or the way persistence works to only map the attributes that contain a mapping in the repository. I can see the convenience of auto-mapping unmapped attributes in the repo, but it feels like I'm then tightly coupling my entities to the persistence layer.

Before I head down this path, what are your thoughts? Would you be open to datamappify being changed in this way? I don't want to invest the time in making these changes if they're unlikely to be rolled in.

change implicit table

Hi @fredwu

I'm trying to break apart a giant table into separate entities. In the code below I'd like it to still reference the "teams" table, even though the for_entity is TeamPreferences. I thought telling it the id was on the teams table would work, but no such luck. Any suggestions?

class TeamPreferencesRepository
  include Datamappify::Repository

  for_entity TeamPreferences

  map_attribute :id, :to => "Team#id"
  map_attribute :refreshments_show_tab, :to => "Team#show_refreshments"
  map_attribute :refreshments_enable_for, :to => "Team#refreshments_filter"
end

I'm doing this to break apart a giant teams table in the api, before breaking it apart in the database.

ActiveRecord - match mapped attribute missing joins

This works.

:where=>{"traveller_first_name"=>"Emil"}

This fails.

:match=>{"traveller_first_name"=>"%Emil%"}
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "travellers"
LINE 1: ...* FROM "trips"  WHERE "trips"."agency_id" = 1 AND "traveller...
                                                             ^
: SELECT "trips".* FROM "trips"  WHERE "trips"."agency_id" = 1 AND "travellers"."first_name" ILIKE '%Emil%'  ORDER BY "trips"."depart_date" ASC

Why isn't this gem 1.0.0?

I've noticed you're using semantic versioning, but the gem isn't 1.0.0? You have people who depend on this gem, have a stable public API, and it does at least the basics of what it set out to do.

Don't worry about needing to change the public API in the future, semantic versioning takes care of this by requiring a major version bump. Let people use the intelligent optimistic versioning for your gem in a meaningful way.

Devise

How would I use this with devise? I tried creating a SignupEntity, a SignupRepository and a devise User, but when I try to save the signup with:

SignupRepository.save(signup)

I get this error:

ActiveRecord::UnknownAttributeError: unknown attribute: password

Timestamps

Right now once an entity is saved, you have to reload it to populate the timestamps:

user = User.new(name: 'test')
UserRepository.save(user)
user.created_at #=> nil
UserRepository.find(user.id).created_at #=> 2013-07-13 21:17:35 +0200

Question: Do I need a Database table for each Entity

To use the example in the README,

If I have a User entity with a UserAccount model it seems according to my experience that I would also need a users table.

Is this meant to be the case, and if so how can I get around it?

Load entity relations

Hey,

When you load an entity it doesn't populate any related entities. Is this a bug or is it intended functionality?

class Deployment
  include Datamappify::Entity

  attribute :name

  references :user
end

class User
  include Datamappify::Entity

  attribute :email
end


DeploymentRepository.find(1) # => deployment

deployment.user_id # => 1
deployment.user # => nil

Maintained?

This looks like something I'd use. Is it maintained and is anyone using it in production?
T

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.