little_mapper 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +88 -0
- data/Rakefile +12 -0
- data/lib/little_mapper.rb +46 -0
- data/lib/little_mapper/dsl_class_methods.rb +72 -0
- data/lib/little_mapper/mapper_instance_methods.rb +52 -0
- data/lib/little_mapper/mappers/AR/one_to_many.rb +34 -0
- data/lib/little_mapper/mappers/AR/one_to_one.rb +39 -0
- data/lib/little_mapper/mappers/active_record.rb +2 -0
- data/lib/little_mapper/mappers/mappers.rb +15 -0
- data/lib/little_mapper/mapping_factory.rb +36 -0
- data/lib/little_mapper/result/repo_failure.rb +11 -0
- data/lib/little_mapper/result/repo_response.rb +14 -0
- data/lib/little_mapper/result/repo_success.rb +7 -0
- data/lib/little_mapper/version.rb +3 -0
- data/little_mapper.gemspec +21 -0
- data/test/dsl_class_methods_test.rb +62 -0
- data/test/helper.rb +6 -0
- data/test/integration/ar_sqlite_test.rb +94 -0
- data/test/integration/helper.rb +114 -0
- data/test/little_mapper_module_test.rb +13 -0
- data/test/mapper_instance_methods_test.rb +22 -0
- data/test/mapping_factory_test.rb +14 -0
- metadata +109 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Simon Robson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# LittleMapper
|
2
|
+
|
3
|
+
Early stage simple datamapper/repository backed by ActiveRecord (only, for the moment). Still working out the best DSL. Currently in use on an internal project.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'little_mapper', :github => 'simonrobson/little-mapper'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install little_mapper
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Define one or more plain ruby classes:
|
22
|
+
|
23
|
+
class Person < OpenStruct
|
24
|
+
attr_accessor :phone_numbers
|
25
|
+
def initialize(*args)
|
26
|
+
super
|
27
|
+
@phone_numbers = []
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class PhoneNumber
|
32
|
+
attr_accessor :id, :code, :number
|
33
|
+
def initialize(opts = {})
|
34
|
+
opts.each_pair {|k, v| self.send("#{k}=", v)}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
Define the mappers:
|
40
|
+
|
41
|
+
class PersonMapper
|
42
|
+
include LittleMapper
|
43
|
+
entity Person
|
44
|
+
persistent_entity Persistent::Person
|
45
|
+
maps :name, :age
|
46
|
+
map :phone_numbers, :as => [PhoneNumber]
|
47
|
+
# see integration test for more complex example
|
48
|
+
# map :all_cats, :as => [Cat], :entity_collection_adder => :receive_cat,
|
49
|
+
# :to => :cats
|
50
|
+
map :spouse, :as => Person
|
51
|
+
end
|
52
|
+
|
53
|
+
class PhoneNumberMapper
|
54
|
+
include LittleMapper
|
55
|
+
entity PhoneNumber
|
56
|
+
persistent_entity Persistent::PhoneNumber
|
57
|
+
maps :code, :number
|
58
|
+
end
|
59
|
+
|
60
|
+
Define the ActiveRecord classes (and their migrations, not shown):
|
61
|
+
|
62
|
+
module Persistent
|
63
|
+
class Person < ActiveRecord::Base
|
64
|
+
has_many :phone_numbers
|
65
|
+
belongs_to :spouse, :class_name => 'Person'
|
66
|
+
end
|
67
|
+
|
68
|
+
class PhoneNumber < ActiveRecord::Base
|
69
|
+
belongs_to :person
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
Exercise the code:
|
74
|
+
|
75
|
+
jane = Person.new(:name => 'Jane', :age => 28)
|
76
|
+
LittleMapper[Person] << jane # stores jane
|
77
|
+
john = Person.new(:name => 'John', :age => 27, :spouse => o)
|
78
|
+
LittleMapper[Person] << john
|
79
|
+
found = LittleMapper[Person].find_by_id(john.id)
|
80
|
+
puts found.spouse.name # Jane - found and spouse are plain Ruby objects
|
81
|
+
|
82
|
+
## Contributing
|
83
|
+
|
84
|
+
1. Fork it
|
85
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
86
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
87
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
88
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "minitest/unit"
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << 'lib/little_helper'
|
7
|
+
t.libs << 'test'
|
8
|
+
t.test_files = FileList['test/*_test.rb', 'test/integration/*_test.rb']
|
9
|
+
t.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => :test
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "little_mapper/version"
|
2
|
+
require "little_mapper/dsl_class_methods"
|
3
|
+
require "little_mapper/mapper_instance_methods"
|
4
|
+
require "little_mapper/mapping_factory"
|
5
|
+
require "little_mapper/mappers/mappers"
|
6
|
+
require "little_mapper/mappers/active_record"
|
7
|
+
require "little_mapper/result/repo_response"
|
8
|
+
require "little_mapper/result/repo_success"
|
9
|
+
require "little_mapper/result/repo_failure"
|
10
|
+
|
11
|
+
module LittleMapper
|
12
|
+
|
13
|
+
# Registers a mapper under a key.
|
14
|
+
#
|
15
|
+
# Will generally be called by Little Mapper itself during inclusion of the
|
16
|
+
# LittleMapper module in your class.
|
17
|
+
#
|
18
|
+
# entity - The key under which the mapper will be held. Generally the
|
19
|
+
# the non-persistent entity class name.
|
20
|
+
# mapper - The class of the mapper.
|
21
|
+
#
|
22
|
+
# Returns the mapper
|
23
|
+
def self.register(entity, mapper)
|
24
|
+
@mappers ||= {}
|
25
|
+
@mappers[entity] = mapper
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns an instantiated mapper for the given key.
|
29
|
+
#
|
30
|
+
# The primary way of interacting with LittleMapper having set up one or more
|
31
|
+
# mappers.
|
32
|
+
#
|
33
|
+
# key - The key under which the mapper was stored. Generally the non-
|
34
|
+
# persistent entity class name.
|
35
|
+
#
|
36
|
+
# Returns a mapper instance for the key
|
37
|
+
def self.[](key)
|
38
|
+
@mappers[key].new
|
39
|
+
end
|
40
|
+
|
41
|
+
# Private: kicks off the action when this module is included
|
42
|
+
def self.included(klass)
|
43
|
+
klass.extend DslClassMethods
|
44
|
+
klass.module_eval { include MapperInstanceMethods}
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module LittleMapper
|
2
|
+
module DslClassMethods
|
3
|
+
|
4
|
+
# Getter/Setter for the Domain Entity to persist.
|
5
|
+
def entity(klass = nil)
|
6
|
+
if klass.nil?
|
7
|
+
@_entity
|
8
|
+
else
|
9
|
+
@_entity = klass
|
10
|
+
LittleMapper.register(klass, self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Getter/Setter for the (Currently ActiveRecord only) class to use for
|
15
|
+
# persistence.
|
16
|
+
def persistent_entity(klass = nil)
|
17
|
+
klass.nil? ? @_persistent_entity : @_persistent_entity = klass
|
18
|
+
end
|
19
|
+
|
20
|
+
def mappings
|
21
|
+
@_mappings ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_mapping(m)
|
25
|
+
mappings << m
|
26
|
+
end
|
27
|
+
|
28
|
+
def mapping_factory=(mf)
|
29
|
+
@_mapping_factory = mf
|
30
|
+
end
|
31
|
+
|
32
|
+
def mapping_factory
|
33
|
+
@_mapping_factory ||= LittleMapper::MappingFactory.new
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add one or more simple one-to-one mappings.
|
37
|
+
#
|
38
|
+
# args - one or more Symbols. The names of the fields to map.
|
39
|
+
#
|
40
|
+
# Examples
|
41
|
+
#
|
42
|
+
# maps :first_name, :last_name
|
43
|
+
def maps(*args)
|
44
|
+
args.each {|f| map(f)}
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Add a single mapping.
|
49
|
+
#
|
50
|
+
# field - A Symbol, the name of the field to map.
|
51
|
+
# opts - A Hash of options to configure the mapper (default: {}):
|
52
|
+
# :as - The Class of Mapper to use to map an entity
|
53
|
+
# value. Can be enclosed in Array notation to
|
54
|
+
# signify a one to many relationship
|
55
|
+
# :to - Name of the field on the persistent entity
|
56
|
+
# to which we will map (default: field)
|
57
|
+
# :entity_setter - a Symbol representing the name of a function
|
58
|
+
# to use when setting the value on the entity
|
59
|
+
# (default: field=)
|
60
|
+
# :entity_collection_adder - in a one-to-many relationship, the name of
|
61
|
+
# of a method to use when adding associated objetcs
|
62
|
+
# (default: to#<<)
|
63
|
+
# Examples
|
64
|
+
#
|
65
|
+
# map :metrics, :as => [Metric]
|
66
|
+
# map :owner, :entity_setter => :assign_owner
|
67
|
+
def map(field, opts = {})
|
68
|
+
add_mapping(mapping_factory.map(self, field, opts))
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module LittleMapper
|
2
|
+
module MapperInstanceMethods
|
3
|
+
def to_persistent(entity)
|
4
|
+
pe = persisted?(entity) ? find_persistent_by_id(entity.id) : self.class.persistent_entity.new
|
5
|
+
self.class.mappings.each do |m|
|
6
|
+
m.from(entity).to_persistent(pe)
|
7
|
+
end
|
8
|
+
pe
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_entity(persistent)
|
12
|
+
e = self.class.entity.new
|
13
|
+
e.id = persistent.id # probably set this only if configured & see duplication below
|
14
|
+
self.class.mappings.each do |m|
|
15
|
+
m.from(persistent).to_entity(e)
|
16
|
+
end
|
17
|
+
e
|
18
|
+
end
|
19
|
+
|
20
|
+
def persisted?(entity)
|
21
|
+
!entity.id.nil? # should maybe check for empty strings too?
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(entity)
|
25
|
+
pe = to_persistent(entity)
|
26
|
+
if pe.save
|
27
|
+
entity.id = pe.id unless entity.id # set this only if configured
|
28
|
+
LittleMapper::Result::RepoSuccess.new(pe)
|
29
|
+
else
|
30
|
+
LittleMapper::Result::RepoFailure.new(pe)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_persistent_by_id(id)
|
35
|
+
self.class.persistent_entity.find_by_id(id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_by_id(id)
|
39
|
+
pe = find_persistent_by_id(id)
|
40
|
+
return nil unless pe
|
41
|
+
to_entity(pe)
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_all
|
45
|
+
self.class.persistent_entity.all.collect { |e| to_entity(e) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def last
|
49
|
+
to_entity(self.class.persistent_entity.last)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module LittleMapper
|
2
|
+
module Mappers
|
3
|
+
module AR
|
4
|
+
class OneToMany < Base
|
5
|
+
attr_accessor :mapper, :entity_field, :persistent_field, :persistent_klass
|
6
|
+
attr_accessor :entity_collection_adder, :reflexive, :reflexive_setter
|
7
|
+
def initialize(mapper, entity_field, persistent_klass, opts = {})
|
8
|
+
@mapper = mapper
|
9
|
+
@entity_field = entity_field
|
10
|
+
@persistent_klass = persistent_klass
|
11
|
+
@persistent_field = opts[:to] || entity_field
|
12
|
+
@entity_collection_adder = opts[:entity_collection_adder]
|
13
|
+
@reflexive = opts.fetch(:reflexive, true)
|
14
|
+
@reflexive_setter = opts[:reflexive_setter] || "#{camel_to_snake(mapper.entity.to_s)}="
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_persistent(target)
|
18
|
+
source.__send__(entity_field).each do |associated|
|
19
|
+
target.__send__(persistent_field).__send__(:<<, LittleMapper[persistent_klass].to_persistent(associated))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
def to_entity(target)
|
23
|
+
source.__send__(persistent_field).each do |associated|
|
24
|
+
assoc_entity = LittleMapper[persistent_klass].to_entity(associated)
|
25
|
+
if reflexive
|
26
|
+
assoc_entity.__send__(reflexive_setter, target)
|
27
|
+
end
|
28
|
+
target.__send__(entity_field).__send__(:<<, assoc_entity)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module LittleMapper
|
2
|
+
module Mappers
|
3
|
+
module AR
|
4
|
+
class OneToOne < Mappers::Base
|
5
|
+
attr_accessor :mapper, :field, :persistent_klass, :persistent_field, :entity_setter, :persistent_entity_setter
|
6
|
+
def initialize(mapper, field, opts = {})
|
7
|
+
@mapper = mapper
|
8
|
+
@field = field
|
9
|
+
@persistent_klass = opts[:as]
|
10
|
+
@persistent_field = opts[:to] || field
|
11
|
+
@entity_setter = opts[:entity_setter] || "#{field}=".to_sym
|
12
|
+
@persistent_entity_setter = opts[:persistent_entity_setter] || "#{persistent_field}=".to_sym
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_persistent(target)
|
16
|
+
if persistent_klass
|
17
|
+
val = source.__send__(field)
|
18
|
+
if val
|
19
|
+
target.__send__(persistent_entity_setter, LittleMapper[persistent_klass].to_persistent(val))
|
20
|
+
end
|
21
|
+
else
|
22
|
+
target.__send__(persistent_entity_setter, source.__send__(field))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_entity(target)
|
27
|
+
if persistent_klass
|
28
|
+
val = source.__send__(persistent_field)
|
29
|
+
if val
|
30
|
+
target.__send__(entity_setter, LittleMapper[persistent_klass].to_entity(val))
|
31
|
+
end
|
32
|
+
else
|
33
|
+
target.__send__(entity_setter, source.__send__(persistent_field))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module LittleMapper
|
2
|
+
class MappingFactory
|
3
|
+
|
4
|
+
def initialize(strategy = :activerecord)
|
5
|
+
@_strategy = strategy
|
6
|
+
end
|
7
|
+
|
8
|
+
def strategy
|
9
|
+
@_strategy
|
10
|
+
end
|
11
|
+
|
12
|
+
def map(mapper, field, opts = {})
|
13
|
+
if opts[:as] && opts[:as].is_a?(Array)
|
14
|
+
as = opts.delete(:as)
|
15
|
+
one_to_many.new(mapper, field, as[0], opts)
|
16
|
+
else
|
17
|
+
one_to_one.new(mapper, field, opts)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def strategy_module
|
22
|
+
case strategy
|
23
|
+
when :activerecord
|
24
|
+
::LittleMapper::Mappers::AR
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def one_to_one
|
29
|
+
strategy_module.const_get('OneToOne')
|
30
|
+
end
|
31
|
+
|
32
|
+
def one_to_many
|
33
|
+
strategy_module.const_get('OneToMany')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module LittleMapper
|
2
|
+
module Result
|
3
|
+
class RepoResult
|
4
|
+
attr_reader :messages, :object
|
5
|
+
def initialize(object = nil)
|
6
|
+
@messages = []
|
7
|
+
@object = object
|
8
|
+
after_initialize
|
9
|
+
end
|
10
|
+
def success?; false; end
|
11
|
+
def after_initialize; end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'little_mapper/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "little_mapper"
|
8
|
+
gem.version = LittleMapper::VERSION
|
9
|
+
gem.authors = ["Simon Robson"]
|
10
|
+
gem.email = ["shrobson@gmail.com"]
|
11
|
+
gem.description = %q{Simple, ActiveRecord-backed data mapper / repository}
|
12
|
+
gem.summary = %q{Simple, ActiveRecord-backed data mapper / repository}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.add_development_dependency('sqlite3')
|
20
|
+
gem.add_development_dependency('activerecord')
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module LittleMapper
|
4
|
+
class DslClassMethodsTest < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
class TestClass
|
7
|
+
extend DslClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
class TheEntity; end
|
11
|
+
|
12
|
+
def test_enables_setting_and_getting_entity
|
13
|
+
TestClass.entity TheEntity
|
14
|
+
assert_equal TheEntity, TestClass.entity
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_setting_the_entity_registers_the_mapper
|
18
|
+
TestClass.entity TheEntity
|
19
|
+
assert LittleMapper[TheEntity].is_a? TestClass
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_enables_setting_and_getting_persistent_entity
|
23
|
+
TestClass.persistent_entity TheEntity
|
24
|
+
assert_equal TheEntity, TestClass.persistent_entity
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_provides_mappings_array
|
28
|
+
assert TestClass.mappings.respond_to?(:each)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_enables_setting_and_getting_a_mapping_factory_instance
|
32
|
+
factory = Object.new
|
33
|
+
TestClass.mapping_factory = factory
|
34
|
+
assert_equal factory, TestClass.mapping_factory
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_defaults_mapping_factory
|
38
|
+
TestClass.mapping_factory = nil
|
39
|
+
assert TestClass.mapping_factory.is_a? LittleMapper::MappingFactory
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_maps_delegates_to_map_and_so_to_mapping_factory
|
43
|
+
factory = MiniTest::Mock.new
|
44
|
+
TestClass.mapping_factory = factory
|
45
|
+
args = [:a, :b, :c]
|
46
|
+
factory.expect(:map, true, [TestClass, :a, {}])
|
47
|
+
factory.expect(:map, true, [TestClass, :b, {}])
|
48
|
+
factory.expect(:map, true, [TestClass, :c, {}])
|
49
|
+
TestClass.maps(*args)
|
50
|
+
factory.verify
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_delegates_map_to_mapping_factory
|
54
|
+
factory = MiniTest::Mock.new
|
55
|
+
TestClass.mapping_factory = factory
|
56
|
+
args = [:field, {:opt1 => :val, :opt2 => :val}]
|
57
|
+
factory.expect(:map, true, args.dup.unshift(TestClass))
|
58
|
+
TestClass.map(*args)
|
59
|
+
factory.verify
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'active_record'
|
3
|
+
require 'integration/helper'
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
class ArSqliteTest < MiniTest::Unit::TestCase
|
8
|
+
DBNAME = 'lmapper.sqlite'
|
9
|
+
|
10
|
+
def init_db
|
11
|
+
if File.directory?('tmp')
|
12
|
+
File.unlink('tmp/#{DBNAME}') if File.exists?('tmp/#{DBNAME}')
|
13
|
+
else
|
14
|
+
Dir.mkdir('tmp')
|
15
|
+
end
|
16
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => 'tmp/test.sqlite')
|
17
|
+
create_tables
|
18
|
+
@_db_initialized = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_tables
|
22
|
+
CreatePeople.new.up
|
23
|
+
CreatePhoneNumbers.new.up
|
24
|
+
CreateCats.new.up
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear_tables
|
28
|
+
Persistent::Person.delete_all
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup
|
32
|
+
init_db unless @_db_initialized
|
33
|
+
clear_tables
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def test_round_trip
|
38
|
+
p = Person.new(:name => 'John', :age => 27)
|
39
|
+
LittleMapper[Person] << p
|
40
|
+
assert_equal 1, LittleMapper[Person].find_all.length
|
41
|
+
found = LittleMapper[Person].last
|
42
|
+
assert_equal p.name, found.name
|
43
|
+
assert_equal p.age, found.age
|
44
|
+
refute_nil found.id
|
45
|
+
assert_equal p.id, found.id
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_find_all
|
49
|
+
LittleMapper[Person] << Person.new(:name => 'John', :age => 27)
|
50
|
+
LittleMapper[Person] << Person.new(:name => 'Jane', :age => 67)
|
51
|
+
assert_equal 2, LittleMapper[Person].find_all.length
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_simple_one_to_many_association
|
55
|
+
p = Person.new(:name => 'John', :age => 27)
|
56
|
+
p.phone_numbers << PhoneNumber.new(:code => '099', :number => '987654321')
|
57
|
+
p.phone_numbers << PhoneNumber.new(:code => '123', :number => '123456789')
|
58
|
+
LittleMapper[Person] << p
|
59
|
+
found = LittleMapper[Person].find_by_id(p.id)
|
60
|
+
assert_equal 2, found.phone_numbers.length
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_one_to_many_with_custom_setters_getters
|
64
|
+
p = Person.new(:name => 'John', :age => 27)
|
65
|
+
p.receive_cat(Cat.new(:moniker => 'Meaw', :color => 'Tabby'))
|
66
|
+
LittleMapper[Person] << p
|
67
|
+
found = LittleMapper[Person].find_by_id(p.id)
|
68
|
+
assert_equal 1, found.all_cats.length
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_one_to_one_with_another_entity
|
72
|
+
o = Person.new(:name => 'Jane', :age => 28)
|
73
|
+
LittleMapper[Person] << o
|
74
|
+
p = Person.new(:name => 'John', :age => 27, :spouse => o)
|
75
|
+
LittleMapper[Person] << p
|
76
|
+
found = LittleMapper[Person].find_by_id(p.id)
|
77
|
+
assert_equal found.spouse, o
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_sets_up_reflexive_mapping_by_default_on_related_entities
|
81
|
+
p = Person.new(:name => 'John', :age => 27)
|
82
|
+
number1 = PhoneNumber.new(:code => '099', :number => '987654321')
|
83
|
+
p.phone_numbers << number1
|
84
|
+
p.phone_numbers << PhoneNumber.new(:code => '123', :number => '123456789')
|
85
|
+
LittleMapper[Person] << p
|
86
|
+
found = LittleMapper[Person].find_by_id(p.id)
|
87
|
+
assert_equal found, found.phone_numbers.first.person
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
class Person < OpenStruct
|
2
|
+
|
3
|
+
attr_accessor :phone_numbers
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
@cats = []
|
7
|
+
@phone_numbers = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def receive_cat(cat)
|
11
|
+
@cats << cat
|
12
|
+
end
|
13
|
+
|
14
|
+
def all_cats
|
15
|
+
@cats
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class PhoneNumber
|
20
|
+
attr_accessor :id, :code, :number, :person
|
21
|
+
def initialize(opts = {})
|
22
|
+
opts.each_pair {|k, v| self.send("#{k}=", v)}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Cat
|
27
|
+
attr_accessor :moniker, :color, :id
|
28
|
+
def initialize(opts = {})
|
29
|
+
opts.each_pair {|k, v| self.send("#{k}=", v)}
|
30
|
+
end
|
31
|
+
|
32
|
+
def owner=(owner)
|
33
|
+
@owner = owner
|
34
|
+
end
|
35
|
+
|
36
|
+
def owner
|
37
|
+
@owner
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
module Persistent
|
43
|
+
class Person < ActiveRecord::Base
|
44
|
+
has_many :phone_numbers
|
45
|
+
has_many :cats
|
46
|
+
belongs_to :spouse, :class_name => 'Person'
|
47
|
+
end
|
48
|
+
|
49
|
+
class PhoneNumber < ActiveRecord::Base
|
50
|
+
belongs_to :person
|
51
|
+
end
|
52
|
+
|
53
|
+
class Cat < ActiveRecord::Base
|
54
|
+
belongs_to :person
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class PersonMapper
|
59
|
+
include LittleMapper
|
60
|
+
entity Person
|
61
|
+
persistent_entity Persistent::Person
|
62
|
+
maps :name, :age
|
63
|
+
map :phone_numbers, :as => [PhoneNumber]
|
64
|
+
map :all_cats, :as => [Cat], :entity_collection_adder => :receive_cat,
|
65
|
+
:to => :cats, :reflexive_setter => :owner=
|
66
|
+
map :spouse, :as => Person
|
67
|
+
end
|
68
|
+
|
69
|
+
class PhoneNumberMapper
|
70
|
+
include LittleMapper
|
71
|
+
entity PhoneNumber
|
72
|
+
persistent_entity Persistent::PhoneNumber
|
73
|
+
maps :code, :number
|
74
|
+
end
|
75
|
+
|
76
|
+
class CatMapper
|
77
|
+
include LittleMapper
|
78
|
+
entity Cat
|
79
|
+
persistent_entity Persistent::Cat
|
80
|
+
map :color
|
81
|
+
map :moniker, :to => :name
|
82
|
+
end
|
83
|
+
|
84
|
+
class CreatePeople < ActiveRecord::Migration
|
85
|
+
def up
|
86
|
+
create_table :people, :force => true do |t|
|
87
|
+
t.string :name
|
88
|
+
t.integer :age
|
89
|
+
t.integer :spouse_id
|
90
|
+
t.timestamps
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class CreatePhoneNumbers < ActiveRecord::Migration
|
96
|
+
def up
|
97
|
+
create_table :phone_numbers, :force => true do |t|
|
98
|
+
t.references :person
|
99
|
+
t.string :code
|
100
|
+
t.string :number
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class CreateCats < ActiveRecord::Migration
|
106
|
+
def up
|
107
|
+
create_table :cats, :force => true do |t|
|
108
|
+
t.references :person
|
109
|
+
t.string :name
|
110
|
+
t.string :color
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module LittleMapper
|
4
|
+
class MapperInstanceMethodsTest < MiniTest::Unit::TestCase
|
5
|
+
class InstanceTestClass
|
6
|
+
include MapperInstanceMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_implements_repo_interface
|
10
|
+
@repo = InstanceTestClass.new
|
11
|
+
assert @repo.respond_to?(:<<), "should respond to <<"
|
12
|
+
assert @repo.respond_to?(:find_by_id), "should respond to find_by_id"
|
13
|
+
assert @repo.respond_to?(:find_all), "should respond to find_by_all"
|
14
|
+
assert @repo.respond_to?(:last), "should respond to last"
|
15
|
+
|
16
|
+
assert @repo.respond_to?(:to_entity), "provides to_entity"
|
17
|
+
assert @repo.respond_to?(:to_persistent), "provides to_persistent"
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module LittleMapper
|
4
|
+
class MappingFactoryTest < MiniTest::Unit::TestCase
|
5
|
+
def test_defaults_to_activerecord_strategy
|
6
|
+
assert MappingFactory.new.strategy == :activerecord
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_one_to_one_constant
|
10
|
+
assert_equal LittleMapper::Mappers::AR::OneToOne, MappingFactory.new.one_to_one
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: little_mapper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Simon Robson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sqlite3
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activerecord
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Simple, ActiveRecord-backed data mapper / repository
|
47
|
+
email:
|
48
|
+
- shrobson@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/little_mapper.rb
|
59
|
+
- lib/little_mapper/dsl_class_methods.rb
|
60
|
+
- lib/little_mapper/mapper_instance_methods.rb
|
61
|
+
- lib/little_mapper/mappers/AR/one_to_many.rb
|
62
|
+
- lib/little_mapper/mappers/AR/one_to_one.rb
|
63
|
+
- lib/little_mapper/mappers/active_record.rb
|
64
|
+
- lib/little_mapper/mappers/mappers.rb
|
65
|
+
- lib/little_mapper/mapping_factory.rb
|
66
|
+
- lib/little_mapper/result/repo_failure.rb
|
67
|
+
- lib/little_mapper/result/repo_response.rb
|
68
|
+
- lib/little_mapper/result/repo_success.rb
|
69
|
+
- lib/little_mapper/version.rb
|
70
|
+
- little_mapper.gemspec
|
71
|
+
- test/dsl_class_methods_test.rb
|
72
|
+
- test/helper.rb
|
73
|
+
- test/integration/ar_sqlite_test.rb
|
74
|
+
- test/integration/helper.rb
|
75
|
+
- test/little_mapper_module_test.rb
|
76
|
+
- test/mapper_instance_methods_test.rb
|
77
|
+
- test/mapping_factory_test.rb
|
78
|
+
homepage: ''
|
79
|
+
licenses: []
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.24
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Simple, ActiveRecord-backed data mapper / repository
|
102
|
+
test_files:
|
103
|
+
- test/dsl_class_methods_test.rb
|
104
|
+
- test/helper.rb
|
105
|
+
- test/integration/ar_sqlite_test.rb
|
106
|
+
- test/integration/helper.rb
|
107
|
+
- test/little_mapper_module_test.rb
|
108
|
+
- test/mapper_instance_methods_test.rb
|
109
|
+
- test/mapping_factory_test.rb
|