activerecord_to_poro 0.0.2 → 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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +12 -10
- data/lib/activerecord_to_poro/mapper_extension.rb +50 -19
- data/lib/activerecord_to_poro/metadata.rb +41 -6
- data/lib/activerecord_to_poro/metadata_enabled.rb +0 -8
- data/lib/activerecord_to_poro/metadata_enabled_ar.rb +32 -0
- data/lib/activerecord_to_poro/object_mapper.rb +103 -0
- data/lib/activerecord_to_poro/version.rb +1 -1
- data/lib/activerecord_to_poro.rb +3 -1
- data/spec/acceptance/map_ar_associations_spec.rb +22 -10
- data/spec/acceptance/map_ar_objects_spec.rb +5 -5
- data/spec/ar_spec_helper.rb +3 -1
- data/spec/integration/lib/activerecord_to_poro/mapper_extension_spec.rb +89 -0
- data/spec/integration/lib/activerecord_to_poro/metadata_enabled_ar_spec.rb +32 -0
- data/spec/integration/lib/activerecord_to_poro/{converter_spec.rb → object_mapper_spec.rb} +19 -14
- data/spec/unit/lib/activerecord_to_poro/metadata_spec.rb +40 -10
- metadata +10 -5
- data/lib/activerecord_to_poro/converter.rb +0 -112
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22303b2e1fdea401dba91cc9d58cd87f13d3f711
|
4
|
+
data.tar.gz: 83b8bf5c084aa93fb5b5614369a049aaa763dd24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 906060ece82669221ba6883bd97dfeb3ee0a862b634025896cfe97b5ddb4731fdf4c169e34bfd5d1dc8b6abb7551a50a0d3bcf16b5c08fd40b765a69032b9b53
|
7
|
+
data.tar.gz: 9b59fdd62577e984c6d0fdfc4a84cc8e31b12c53c29ada165f30de83302765c79e652b036c9551e511c3f1a42cca71f875e8cea640018d0c0b75b8c97ab9d4d2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -48,27 +48,29 @@ end
|
|
48
48
|
|
49
49
|
# You can convert them to poro's like this (automatic genereation of a poro class out of you'r AR class)
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
user_converter = ActiverecordToPoro::
|
51
|
+
ActiverecordToPoro::ObjectMapper.create(Role, name: :roles_converter)
|
52
|
+
ActiverecordToPoro::ObjectMapper.create(Salutation, name: :salutation_converter)
|
53
|
+
user_converter = ActiverecordToPoro::ObjectMapper.create(User,
|
54
|
+
name: :user_converter,
|
55
|
+
convert_associations: {roles: :roles_converter, salutation: :salutation_converter})
|
54
56
|
|
55
57
|
|
56
58
|
poro = user_converter.load(User.first)
|
57
59
|
|
58
|
-
# Or with you'r custom class
|
60
|
+
# Or with you'r custom poro class
|
59
61
|
|
60
|
-
roles_converter = ActiverecordToPoro::
|
61
|
-
|
62
|
-
|
62
|
+
roles_converter = ActiverecordToPoro::ObjectMapper.create(Role,
|
63
|
+
load_source: YourPoroClass
|
64
|
+
)
|
63
65
|
|
64
66
|
|
65
67
|
# default 1:1 mapping only or except
|
66
68
|
|
67
|
-
roles_converter = ActiverecordToPoro::
|
69
|
+
roles_converter = ActiverecordToPoro::ObjectMapper.create(Role,
|
68
70
|
except: [:lock_version]
|
69
71
|
)
|
70
72
|
|
71
|
-
roles_converter = ActiverecordToPoro::
|
73
|
+
roles_converter = ActiverecordToPoro::ObjectMapper.create(Role,
|
72
74
|
only: [:name]
|
73
75
|
)
|
74
76
|
|
@@ -83,7 +85,7 @@ end
|
|
83
85
|
roles_converter.extend_mapping do
|
84
86
|
association_rule to: association_name,
|
85
87
|
lazy_loading: true,
|
86
|
-
converter: association_converter
|
88
|
+
converter: association_converter #or :association_converter when you'r converter has a name
|
87
89
|
# optional
|
88
90
|
# from: ...,
|
89
91
|
# reverse_to: ...,
|
@@ -2,26 +2,57 @@ module ActiverecordToPoro
|
|
2
2
|
module MapperExtension
|
3
3
|
|
4
4
|
def association_rule(to: nil,
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
5
|
+
from: to,
|
6
|
+
reverse_to: from,
|
7
|
+
reverse_from: to,
|
8
|
+
converter: nil,
|
9
|
+
reverse_converter: converter,
|
10
|
+
is_collection: false,
|
11
|
+
lazy_loading: nil
|
12
|
+
)
|
13
|
+
|
14
|
+
map_collection = ActiverecordToPoro::MapperExtension.is_an_ar_collection?(self.dump_result_source, from) || is_collection
|
15
|
+
|
16
|
+
options ={
|
17
|
+
to: to,
|
18
|
+
from: from,
|
19
|
+
reverse_to: reverse_to,
|
20
|
+
reverse_from: reverse_from,
|
21
|
+
reverse_lazy_loading: false, #AR doesn't like ToProcDelegator
|
22
|
+
is_collection: map_collection,
|
23
|
+
lazy_loading: lazy_loading
|
24
|
+
}
|
25
|
+
|
26
|
+
if converter.nil?
|
27
|
+
options[:converter] = noop
|
28
|
+
options[:object_converter] = nil
|
29
|
+
else
|
30
|
+
options[:object_converter] = converter#.mapper
|
31
|
+
end
|
32
|
+
|
33
|
+
if reverse_converter.nil? || ActiverecordToPoro::MapperExtension.is_an_has_many_through(self.dump_result_source, from)
|
34
|
+
options[:reverse_converter] = noop
|
35
|
+
options[:reverse_object_converter] = nil
|
36
|
+
else
|
37
|
+
options[:reverse_object_converter] = reverse_converter#.mapper
|
38
|
+
end
|
39
|
+
|
40
|
+
rule options
|
24
41
|
|
25
42
|
end
|
43
|
+
|
44
|
+
module_function
|
45
|
+
|
46
|
+
def is_an_ar_collection?(ar_class, association_name)
|
47
|
+
(ar_class.reflections[association_name] &&
|
48
|
+
ar_class.reflections[association_name].collection?)
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_an_has_many_through(ar_class, association_name)
|
52
|
+
ar_class.reflections[association_name] &&
|
53
|
+
ar_class.reflections[association_name].macro == :has_many &&
|
54
|
+
ar_class.reflections[association_name].options.has_key?(:through)
|
55
|
+
end
|
56
|
+
|
26
57
|
end
|
27
58
|
end
|
@@ -1,21 +1,56 @@
|
|
1
1
|
module ActiverecordToPoro
|
2
|
+
SourceObjectInfo = Yaoc::Helper::StructH(:class_name, :column, :value, :lock_version, :object_id) do
|
3
|
+
|
4
|
+
include Equalizer.new(:class_name, :column, :value)
|
5
|
+
|
6
|
+
def to_hash
|
7
|
+
{
|
8
|
+
class_name: class_name,
|
9
|
+
primary_key: {column: column, value: value},
|
10
|
+
object_id: object_id,
|
11
|
+
lock_version: lock_version
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_scope_hash
|
16
|
+
result = column.nil? ? {} : {column => value}
|
17
|
+
result[:lock_version] = lock_version unless lock_version.nil?
|
18
|
+
|
19
|
+
result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
2
23
|
class Metadata
|
3
|
-
attr_accessor :
|
24
|
+
attr_accessor :source_object_info
|
4
25
|
|
26
|
+
def initialize()
|
27
|
+
self.source_object_info = Set.new()
|
28
|
+
end
|
5
29
|
|
6
30
|
def initialize_from_ar(ar_object=nil)
|
7
31
|
unless ar_object.nil?
|
8
|
-
|
32
|
+
set_source_info(ar_object)
|
9
33
|
end
|
10
34
|
end
|
11
35
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
36
|
+
def for_ar_class(ar_class_name)
|
37
|
+
Set.new.find
|
38
|
+
source_object_info.find(->{SourceObjectInfo.new}){|data|
|
39
|
+
data.class_name == ar_class_name
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_source_info(ar_object)
|
44
|
+
self.source_object_info << SourceObjectInfo.new(class_name: ar_object.class.name,
|
45
|
+
column: ar_object.class.primary_key,
|
46
|
+
value: ar_object.send(ar_object.class.primary_key),
|
47
|
+
object_id: ar_object.object_id,
|
48
|
+
lock_version: ar_object.respond_to?(:lock_version) ? ar_object.lock_version : nil
|
49
|
+
)
|
15
50
|
end
|
16
51
|
|
17
52
|
def to_hash
|
18
|
-
{
|
53
|
+
{ source_objects_info: source_object_info.map(&:to_hash) }
|
19
54
|
end
|
20
55
|
|
21
56
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiverecordToPoro
|
2
|
+
|
3
|
+
module MetadataEnabledAr
|
4
|
+
|
5
|
+
def _from_attrs_with_metadata(attrs={}, pre_created_object= nil)
|
6
|
+
record_by_primary_key = _record_from_metadata!(attrs)
|
7
|
+
|
8
|
+
record = pre_created_object || record_by_primary_key || new
|
9
|
+
|
10
|
+
record.tap do |new_obj|
|
11
|
+
new_obj.attributes = attrs
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def _as_scope(attr)
|
17
|
+
attr.empty? ? none : where(attr)
|
18
|
+
end
|
19
|
+
|
20
|
+
def _extract_metadata!(attrs)
|
21
|
+
metadata = (attrs || {}).delete(:_set_metadata_to_ar) {|*| Metadata.new}
|
22
|
+
metadata.for_ar_class(self.name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def _record_from_metadata!(attrs)
|
26
|
+
specific_metadata = _extract_metadata!(attrs)
|
27
|
+
_as_scope(specific_metadata.as_scope_hash).first
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module ActiverecordToPoro
|
2
|
+
|
3
|
+
module MappingToArClass
|
4
|
+
def call(pre_created_object=nil)
|
5
|
+
self.target_source._from_attrs_with_metadata(to_hash_or_array(), pre_created_object)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class ObjectMapper < Yaoc::ObjectMapper
|
10
|
+
include ColumnHelper
|
11
|
+
include ActiverecordToPoro::MapperExtension
|
12
|
+
|
13
|
+
attr_accessor :association_converters,
|
14
|
+
:use_lazy_loading,
|
15
|
+
:except_attributes,
|
16
|
+
:only_attributes
|
17
|
+
|
18
|
+
class << self
|
19
|
+
private :new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.create(ar_class,
|
23
|
+
use_lazy_loading=true,
|
24
|
+
except: nil,
|
25
|
+
only: nil,
|
26
|
+
name: nil,
|
27
|
+
load_source: DefaultPoroClassBuilder.new(ar_class).(),
|
28
|
+
convert_associations: {})
|
29
|
+
|
30
|
+
new(load_source, ar_class).tap do|new_mapper|
|
31
|
+
new_mapper.fetcher(:public_send)
|
32
|
+
|
33
|
+
new_mapper.association_converters = convert_associations
|
34
|
+
new_mapper.use_lazy_loading = use_lazy_loading
|
35
|
+
|
36
|
+
new_mapper.except_attributes = Array(except)
|
37
|
+
new_mapper.only_attributes = only.nil? ? nil : Array(only) # an empty array can be wanted, so that there is no default mapping @ all
|
38
|
+
|
39
|
+
new_mapper.add_default_mapping_for_current_class
|
40
|
+
new_mapper.add_mapping_for_associations
|
41
|
+
|
42
|
+
new_mapper.register_as(name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_method :extend_mapping, :add_mapping
|
47
|
+
|
48
|
+
def attributes_for_default_mapping
|
49
|
+
self.only_attributes || (
|
50
|
+
columns(self.dump_result_source) -
|
51
|
+
primary_keys(self.dump_result_source) -
|
52
|
+
association_specific_columns(self.dump_result_source) -
|
53
|
+
associated_object_accessors(self.dump_result_source) -
|
54
|
+
self.except_attributes
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def load_result_source=(new_load_result)
|
59
|
+
@load_result_source = new_load_result.tap do |source|
|
60
|
+
unless source.instance_methods.include?(:_metadata)
|
61
|
+
source.send(:include, MetadataEnabled)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def dump_result_source=(new_dump_result)
|
67
|
+
@dump_result_source = new_dump_result.tap do |source|
|
68
|
+
unless source.respond_to? :_from_attrs_with_metadata
|
69
|
+
source.send(:extend, MetadataEnabledAr)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_default_mapping_for_current_class
|
75
|
+
add_mapping do
|
76
|
+
rule to: attributes_for_default_mapping
|
77
|
+
|
78
|
+
rule to: :_set_metadata,
|
79
|
+
converter: ->(source, result){ self.class.fill_result_with_value(result, :_set_metadata_from_ar, source) },
|
80
|
+
reverse_converter: ->(source, result){ self.class.fill_result_with_value(result, :_set_metadata_to_ar, source._metadata) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_mapping_for_associations
|
85
|
+
association_converters.each_pair do |association_name, association_converter|
|
86
|
+
|
87
|
+
add_mapping do
|
88
|
+
association_rule to: association_name,
|
89
|
+
lazy_loading: use_lazy_loading,
|
90
|
+
converter: association_converter
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def reverse_converter(fetch_able=nil)
|
96
|
+
reverse_converter_builder.converter(fetch_able, dump_result_source).tap do |new_converter|
|
97
|
+
new_converter.extend(MappingToArClass)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/lib/activerecord_to_poro.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'yaoc'
|
2
|
+
require 'equalizer'
|
2
3
|
require 'activerecord_to_poro/version'
|
3
4
|
require 'activerecord_to_poro/metadata_enabled'
|
5
|
+
require 'activerecord_to_poro/metadata_enabled_ar'
|
4
6
|
require 'activerecord_to_poro/metadata'
|
5
7
|
require 'activerecord_to_poro/default_poro_class_builder'
|
6
8
|
require 'activerecord_to_poro/mapper_extension'
|
7
|
-
require 'activerecord_to_poro/
|
9
|
+
require 'activerecord_to_poro/object_mapper'
|
8
10
|
|
9
11
|
module ActiverecordToPoro
|
10
12
|
|
@@ -7,19 +7,29 @@ feature 'Map active record associations', %q{
|
|
7
7
|
} do
|
8
8
|
|
9
9
|
given!(:mapper){
|
10
|
-
ActiverecordToPoro::
|
10
|
+
ActiverecordToPoro::ObjectMapper.create(a_active_record_class, convert_associations: {roles: roles_converter,
|
11
|
+
salutation: salutation_converter}).tap do |m|
|
12
|
+
quirk_converter = permissions_converter
|
13
|
+
|
14
|
+
m.extend_mapping do
|
15
|
+
association_rule to: :permissions,
|
16
|
+
converter: quirk_converter,
|
17
|
+
reverse_converter: nil,
|
18
|
+
lazy_loading: true
|
19
|
+
end
|
20
|
+
end
|
11
21
|
}
|
12
22
|
|
13
23
|
given!(:roles_converter){
|
14
|
-
ActiverecordToPoro::
|
24
|
+
ActiverecordToPoro::ObjectMapper.create(Role, convert_associations: {permissions: permissions_converter})
|
15
25
|
}
|
16
26
|
|
17
27
|
given!(:permissions_converter){
|
18
|
-
ActiverecordToPoro::
|
28
|
+
ActiverecordToPoro::ObjectMapper.create(Permission, name: :permissions_converter)
|
19
29
|
}
|
20
30
|
|
21
31
|
given!(:salutation_converter){
|
22
|
-
ActiverecordToPoro::
|
32
|
+
ActiverecordToPoro::ObjectMapper.create(Salutation)
|
23
33
|
}
|
24
34
|
|
25
35
|
given!(:a_active_record_class){
|
@@ -53,22 +63,25 @@ feature 'Map active record associations', %q{
|
|
53
63
|
}
|
54
64
|
|
55
65
|
given(:mapper_with_custom_source){
|
56
|
-
ActiverecordToPoro::
|
57
|
-
|
58
|
-
|
66
|
+
ActiverecordToPoro::ObjectMapper.create(a_active_record_class,
|
67
|
+
load_source: a_custom_poro_class,
|
68
|
+
except: [:lock_version]
|
59
69
|
)
|
60
70
|
}
|
61
71
|
|
62
72
|
|
63
73
|
scenario "creates a poro out of an ActiveRecord object with associations set" do
|
64
74
|
expect(mapper.load(a_active_record_object).roles.size).to eq 2
|
75
|
+
expect(mapper.load(a_active_record_object).permissions.size).to eq 3
|
65
76
|
expect(mapper.load(a_active_record_object).salutation.name).to eq "Mister"
|
66
77
|
end
|
67
78
|
|
68
79
|
scenario "creates an ActiveRecord object from a poro object with associations set" do
|
69
80
|
poro = mapper.load(a_active_record_object)
|
70
81
|
expect(mapper.dump(poro).roles.size).to eq 2
|
71
|
-
|
82
|
+
|
83
|
+
expect(mapper.dump(poro).roles.first.permissions.size).to eq 2
|
84
|
+
expect(mapper.dump(poro).roles.last.permissions.size).to eq 1
|
72
85
|
end
|
73
86
|
|
74
87
|
scenario "lazy loads associated objects" do
|
@@ -79,13 +92,12 @@ feature 'Map active record associations', %q{
|
|
79
92
|
end
|
80
93
|
|
81
94
|
scenario 'add custom association mappings' do
|
82
|
-
quirk_converter = permissions_converter
|
83
95
|
|
84
96
|
mapper_with_custom_source.extend_mapping do
|
85
97
|
|
86
98
|
association_rule to: :some_other_name,
|
87
99
|
from: :permissions,
|
88
|
-
converter:
|
100
|
+
converter: :permissions_converter,
|
89
101
|
lazy_loading: true
|
90
102
|
|
91
103
|
end
|
@@ -7,13 +7,13 @@ feature "Map active record objects", %q{
|
|
7
7
|
} do
|
8
8
|
|
9
9
|
given(:mapper){
|
10
|
-
ActiverecordToPoro::
|
10
|
+
ActiverecordToPoro::ObjectMapper.create(a_active_record_class)
|
11
11
|
}
|
12
12
|
|
13
13
|
given(:mapper_with_custom_source){
|
14
|
-
ActiverecordToPoro::
|
15
|
-
|
16
|
-
|
14
|
+
ActiverecordToPoro::ObjectMapper.create(a_active_record_class,
|
15
|
+
load_source: custom_poro_class,
|
16
|
+
except: [:lock_version]
|
17
17
|
)
|
18
18
|
}
|
19
19
|
|
@@ -35,7 +35,7 @@ feature "Map active record objects", %q{
|
|
35
35
|
|
36
36
|
scenario "creates an ActiveRecord object from a poro object" do
|
37
37
|
poro = mapper.load(a_active_record_object)
|
38
|
-
expect(mapper.dump(poro)).to eq a_active_record_object
|
38
|
+
expect(mapper.dump(poro).attributes).to eq a_active_record_object.attributes
|
39
39
|
end
|
40
40
|
|
41
41
|
scenario "use my own source class for converting ActiveRecord objects" do
|
data/spec/ar_spec_helper.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
require 'sqlite3'
|
2
1
|
require 'active_record'
|
3
2
|
require 'database_cleaner'
|
4
3
|
|
5
4
|
db_dir = File.join(File.expand_path(__dir__ ), "ar_support/db/")
|
5
|
+
# ENV['DATABASE_URL']="sqlite3://localhost/:memory:?pool=5&timeout=5000"
|
6
|
+
|
7
|
+
|
6
8
|
|
7
9
|
ActiveRecord::Base.establish_connection({
|
8
10
|
pool: 5,
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'integration_spec_helper'
|
2
|
+
|
3
|
+
describe ActiverecordToPoro::MapperExtension do
|
4
|
+
subject{
|
5
|
+
Yaoc::ObjectMapper.new(target, source).tap do |mapper|
|
6
|
+
mapper.extend ActiverecordToPoro::MapperExtension
|
7
|
+
mapper.fetcher(:public_send)
|
8
|
+
end
|
9
|
+
}
|
10
|
+
|
11
|
+
let(:source){ User }
|
12
|
+
|
13
|
+
let(:target){ActiverecordToPoro::DefaultPoroClassBuilder.new(source).()}
|
14
|
+
|
15
|
+
let(:default_expected_params){
|
16
|
+
{
|
17
|
+
:to=>:roles,
|
18
|
+
:from=>:roles,
|
19
|
+
:reverse_to=>:roles,
|
20
|
+
:reverse_from=>:roles,
|
21
|
+
:reverse_lazy_loading=>false,
|
22
|
+
:is_collection=>true,
|
23
|
+
:lazy_loading=>nil,
|
24
|
+
:object_converter=>converter,
|
25
|
+
:reverse_object_converter=>converter
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
let(:converter){
|
30
|
+
double("converter")
|
31
|
+
}
|
32
|
+
|
33
|
+
describe '#association_rule' do
|
34
|
+
it "uses defaults" do
|
35
|
+
expect(subject).to receive(:rule).with default_expected_params
|
36
|
+
subject.association_rule(to: :roles,
|
37
|
+
converter: converter
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "does not convert forward when converter is nil" do
|
42
|
+
expected_params = default_expected_params.merge(converter: kind_of(Proc),
|
43
|
+
reverse_converter: kind_of(Proc),
|
44
|
+
object_converter: nil,
|
45
|
+
reverse_object_converter: nil
|
46
|
+
)
|
47
|
+
|
48
|
+
expect(subject).to receive(:rule).with expected_params
|
49
|
+
|
50
|
+
subject.association_rule(
|
51
|
+
to: :roles,
|
52
|
+
converter: nil
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "does not convert reverse when reverse converter is nil" do
|
57
|
+
expected_params = default_expected_params.merge(
|
58
|
+
reverse_converter: kind_of(Proc),
|
59
|
+
reverse_object_converter: nil
|
60
|
+
)
|
61
|
+
|
62
|
+
expect(subject).to receive(:rule).with expected_params
|
63
|
+
|
64
|
+
subject.association_rule(
|
65
|
+
to: :roles,
|
66
|
+
converter: converter,
|
67
|
+
reverse_converter: nil
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "disables reverse converter when the association is kind of has many through (no setter from rails)" do
|
72
|
+
expected_params = default_expected_params.merge(
|
73
|
+
reverse_converter: kind_of(Proc),
|
74
|
+
reverse_object_converter: nil,
|
75
|
+
to: :permissions,
|
76
|
+
from: :permissions,
|
77
|
+
reverse_to: :permissions,
|
78
|
+
reverse_from: :permissions,
|
79
|
+
)
|
80
|
+
|
81
|
+
expect(subject).to receive(:rule).with expected_params
|
82
|
+
|
83
|
+
subject.association_rule(
|
84
|
+
to: :permissions,
|
85
|
+
converter: converter
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'integration_spec_helper'
|
2
|
+
|
3
|
+
describe ActiverecordToPoro::MetadataEnabledAr do
|
4
|
+
subject{
|
5
|
+
User.extend ActiverecordToPoro::MetadataEnabledAr
|
6
|
+
User
|
7
|
+
}
|
8
|
+
|
9
|
+
let(:ar_object){
|
10
|
+
User.create!(name: 'my name', email: 'my_name@example.com')
|
11
|
+
}
|
12
|
+
|
13
|
+
let(:metadata){
|
14
|
+
ActiverecordToPoro::Metadata.new.tap do |meta|
|
15
|
+
meta.initialize_from_ar(ar_object)
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
describe '._from_attrs_with_metadata' do
|
20
|
+
it 'loads a record from db' do
|
21
|
+
new_user = subject._from_attrs_with_metadata({_set_metadata_to_ar: metadata })
|
22
|
+
expect(new_user.new_record?).to be_falsy
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'uses a precreated object' do
|
26
|
+
new_user = subject._from_attrs_with_metadata({_set_metadata_to_ar: metadata }, ar_object)
|
27
|
+
expect(new_user.object_id).to eq ar_object.object_id
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
require 'integration_spec_helper'
|
2
2
|
|
3
|
-
describe ActiverecordToPoro::
|
3
|
+
describe ActiverecordToPoro::ObjectMapper do
|
4
4
|
|
5
5
|
describe '.new' do
|
6
6
|
subject{
|
7
|
-
ActiverecordToPoro::
|
7
|
+
ActiverecordToPoro::ObjectMapper
|
8
8
|
}
|
9
9
|
|
10
10
|
it 'removes "except" attributes from default mapping' do
|
11
|
-
object = subject.
|
11
|
+
object = subject.create(User, except: :lock_version)
|
12
12
|
expect(object.attributes_for_default_mapping).not_to include :lock_version
|
13
13
|
end
|
14
14
|
|
15
15
|
it 'uses "only" attributes for default mapping' do
|
16
|
-
object = subject.
|
16
|
+
object = subject.create(User, only: :id)
|
17
17
|
expect(object.attributes_for_default_mapping).to eq [:id]
|
18
18
|
end
|
19
19
|
|
@@ -21,15 +21,15 @@ describe ActiverecordToPoro::Converter do
|
|
21
21
|
|
22
22
|
context 'instance methods' do
|
23
23
|
subject!{
|
24
|
-
ActiverecordToPoro::
|
24
|
+
ActiverecordToPoro::ObjectMapper.create(User, convert_associations: {roles: roles_converter, salutation: salutation_converter})
|
25
25
|
}
|
26
26
|
|
27
27
|
let(:roles_converter){
|
28
|
-
ActiverecordToPoro::
|
28
|
+
ActiverecordToPoro::ObjectMapper.create(Role)
|
29
29
|
}
|
30
30
|
|
31
31
|
let(:salutation_converter){
|
32
|
-
ActiverecordToPoro::
|
32
|
+
ActiverecordToPoro::ObjectMapper.create(Salutation)
|
33
33
|
}
|
34
34
|
|
35
35
|
let(:ar_object){
|
@@ -50,7 +50,6 @@ describe ActiverecordToPoro::Converter do
|
|
50
50
|
|
51
51
|
it 'sets metadata for loaded objects' do
|
52
52
|
expect(loaded_poro_object._metadata).not_to be_nil
|
53
|
-
expect(loaded_poro_object._metadata.primary_key_value).to eq ar_object.id
|
54
53
|
end
|
55
54
|
|
56
55
|
it 'converts also associated objects' do
|
@@ -62,18 +61,19 @@ describe ActiverecordToPoro::Converter do
|
|
62
61
|
subject.load(ar_object)
|
63
62
|
end
|
64
63
|
|
64
|
+
it 'fills an existing poro' do
|
65
|
+
poro_to_fill = subject.load_result_source.new
|
66
|
+
expect(subject.load(ar_object, poro_to_fill).object_id).to eq poro_to_fill.object_id
|
67
|
+
end
|
68
|
+
|
65
69
|
end
|
66
70
|
|
67
71
|
describe '#dump' do
|
68
|
-
it 'creates an
|
72
|
+
it 'creates an ActiveRecord object' do
|
69
73
|
expect(subject.dump(loaded_poro_object)).to be_kind_of ActiveRecord::Base
|
70
74
|
end
|
71
75
|
|
72
|
-
it '
|
73
|
-
expect(subject.dump(loaded_poro_object).id).to eq ar_object.id
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'sets new_record to false when it is an existing record' do
|
76
|
+
it 'loads the object from database when primary key and value are given in metadata' do
|
77
77
|
expect(subject.dump(loaded_poro_object).new_record?).to be_falsy
|
78
78
|
end
|
79
79
|
|
@@ -82,6 +82,11 @@ describe ActiverecordToPoro::Converter do
|
|
82
82
|
|
83
83
|
expect(subject.dump(loaded_poro_object).roles.size).to eq count_roles
|
84
84
|
end
|
85
|
+
|
86
|
+
it 'fills an existing ActiveRecord object' do
|
87
|
+
new_ar_object = User.new
|
88
|
+
expect(subject.dump(loaded_poro_object, new_ar_object).object_id).to eq new_ar_object.object_id
|
89
|
+
end
|
85
90
|
end
|
86
91
|
|
87
92
|
end
|
@@ -1,32 +1,62 @@
|
|
1
1
|
require 'unit_spec_helper'
|
2
2
|
|
3
3
|
describe ActiverecordToPoro::Metadata do
|
4
|
-
let(:
|
4
|
+
let(:ar_class){
|
5
5
|
Struct.new(:id) do
|
6
6
|
def self.primary_key
|
7
7
|
"id"
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
def self.name
|
11
|
+
"MyNameIsStruct"
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
}
|
16
|
+
|
17
|
+
let(:ar_object_1){
|
18
|
+
ar_class.new(1)
|
19
|
+
}
|
20
|
+
|
21
|
+
let(:ar_object_2){
|
22
|
+
ar_class.new(2)
|
11
23
|
}
|
12
24
|
|
25
|
+
|
13
26
|
describe "#initialize_from_ar" do
|
14
27
|
|
15
28
|
it "sets the primary_key" do
|
16
|
-
subject.initialize_from_ar(
|
29
|
+
subject.initialize_from_ar(ar_object_1)
|
17
30
|
|
18
|
-
expect(subject.
|
19
|
-
|
31
|
+
expect(subject.to_hash).to include( source_objects_info: [{class_name: "MyNameIsStruct",
|
32
|
+
primary_key: {:column=>"id", :value=>1},
|
33
|
+
object_id: ar_object_1.object_id,
|
34
|
+
lock_version: nil
|
35
|
+
}])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "adds multiple objects" do
|
39
|
+
subject.initialize_from_ar(ar_object_1)
|
40
|
+
subject.initialize_from_ar(ar_object_2)
|
41
|
+
|
42
|
+
expect(subject.to_hash[:source_objects_info].size).to eq 2
|
43
|
+
end
|
44
|
+
|
45
|
+
it "adds the same object only once" do
|
46
|
+
subject.initialize_from_ar(ar_class.new(1))
|
47
|
+
subject.initialize_from_ar(ar_class.new(1))
|
48
|
+
|
49
|
+
expect(subject.to_hash[:source_objects_info].size).to eq 1
|
20
50
|
end
|
21
51
|
|
22
52
|
end
|
23
53
|
|
24
|
-
describe
|
25
|
-
it
|
26
|
-
subject.
|
54
|
+
describe '#for_ar_class' do
|
55
|
+
it 'returns metadata for a ActiveRecord class' do
|
56
|
+
subject.initialize_from_ar(ar_object_1)
|
27
57
|
|
28
|
-
expect(subject.
|
29
|
-
expect(subject.primary_key_value).to eq 1
|
58
|
+
expect(subject.for_ar_class(ar_class.name).to_hash).to include({primary_key: {column: "id", value: 1}})
|
30
59
|
end
|
31
60
|
end
|
61
|
+
|
32
62
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord_to_poro
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dieter Späth
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: abstract_type
|
@@ -278,11 +278,12 @@ files:
|
|
278
278
|
- Rakefile
|
279
279
|
- activerecord_to_poro.gemspec
|
280
280
|
- lib/activerecord_to_poro.rb
|
281
|
-
- lib/activerecord_to_poro/converter.rb
|
282
281
|
- lib/activerecord_to_poro/default_poro_class_builder.rb
|
283
282
|
- lib/activerecord_to_poro/mapper_extension.rb
|
284
283
|
- lib/activerecord_to_poro/metadata.rb
|
285
284
|
- lib/activerecord_to_poro/metadata_enabled.rb
|
285
|
+
- lib/activerecord_to_poro/metadata_enabled_ar.rb
|
286
|
+
- lib/activerecord_to_poro/object_mapper.rb
|
286
287
|
- lib/activerecord_to_poro/version.rb
|
287
288
|
- spec/acceptance/map_ar_associations_spec.rb
|
288
289
|
- spec/acceptance/map_ar_objects_spec.rb
|
@@ -294,8 +295,10 @@ files:
|
|
294
295
|
- spec/ar_support/models/role.rb
|
295
296
|
- spec/ar_support/models/salutation.rb
|
296
297
|
- spec/ar_support/models/user.rb
|
297
|
-
- spec/integration/lib/activerecord_to_poro/converter_spec.rb
|
298
298
|
- spec/integration/lib/activerecord_to_poro/default_poro_class_builder_spec.rb
|
299
|
+
- spec/integration/lib/activerecord_to_poro/mapper_extension_spec.rb
|
300
|
+
- spec/integration/lib/activerecord_to_poro/metadata_enabled_ar_spec.rb
|
301
|
+
- spec/integration/lib/activerecord_to_poro/object_mapper_spec.rb
|
299
302
|
- spec/integration_spec_helper.rb
|
300
303
|
- spec/spec_helper.rb
|
301
304
|
- spec/support/feature.rb
|
@@ -336,8 +339,10 @@ test_files:
|
|
336
339
|
- spec/ar_support/models/role.rb
|
337
340
|
- spec/ar_support/models/salutation.rb
|
338
341
|
- spec/ar_support/models/user.rb
|
339
|
-
- spec/integration/lib/activerecord_to_poro/converter_spec.rb
|
340
342
|
- spec/integration/lib/activerecord_to_poro/default_poro_class_builder_spec.rb
|
343
|
+
- spec/integration/lib/activerecord_to_poro/mapper_extension_spec.rb
|
344
|
+
- spec/integration/lib/activerecord_to_poro/metadata_enabled_ar_spec.rb
|
345
|
+
- spec/integration/lib/activerecord_to_poro/object_mapper_spec.rb
|
341
346
|
- spec/integration_spec_helper.rb
|
342
347
|
- spec/spec_helper.rb
|
343
348
|
- spec/support/feature.rb
|
@@ -1,112 +0,0 @@
|
|
1
|
-
module ActiverecordToPoro
|
2
|
-
class Converter
|
3
|
-
include ColumnHelper
|
4
|
-
|
5
|
-
attr_accessor :load_source_class,
|
6
|
-
:dump_source_class,
|
7
|
-
:association_converters,
|
8
|
-
:use_lazy_loading,
|
9
|
-
:except_attributes,
|
10
|
-
:only_attributes
|
11
|
-
|
12
|
-
def initialize(ar_class,
|
13
|
-
use_lazy_loading=true,
|
14
|
-
except: nil,
|
15
|
-
only: nil,
|
16
|
-
load_source: nil,
|
17
|
-
convert_associations: {})
|
18
|
-
self.load_source_class = ar_class
|
19
|
-
self.dump_source_class = load_source || DefaultPoroClassBuilder.new(ar_class).()
|
20
|
-
self.association_converters = convert_associations
|
21
|
-
self.use_lazy_loading = use_lazy_loading
|
22
|
-
self.except_attributes = Array(except)
|
23
|
-
self.only_attributes = only.nil? ? nil : Array(only) # an empty array can be wanted, so that there is no default mapping @ all
|
24
|
-
end
|
25
|
-
|
26
|
-
def load(to_convert)
|
27
|
-
mapper.load(to_convert)
|
28
|
-
end
|
29
|
-
|
30
|
-
def dump(to_convert)
|
31
|
-
mapper.dump(to_convert)
|
32
|
-
end
|
33
|
-
|
34
|
-
def extend_mapping(&block)
|
35
|
-
mapper.add_mapping &block
|
36
|
-
end
|
37
|
-
|
38
|
-
|
39
|
-
def mapper
|
40
|
-
@mapper||= Yaoc::ObjectMapper.new(self.dump_source_class, self.load_source_class).tap do |mapper|
|
41
|
-
mapper.extend ActiverecordToPoro::MapperExtension
|
42
|
-
mapper.fetcher(:public_send)
|
43
|
-
|
44
|
-
add_default_mapping_for_current_class(mapper)
|
45
|
-
add_mapping_for_associations(mapper)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def attributes_for_default_mapping
|
50
|
-
self.only_attributes || (
|
51
|
-
columns(self.load_source_class) -
|
52
|
-
primary_keys(self.load_source_class) -
|
53
|
-
association_specific_columns(self.load_source_class) -
|
54
|
-
associated_object_accessors(self.load_source_class) -
|
55
|
-
self.except_attributes
|
56
|
-
)
|
57
|
-
end
|
58
|
-
|
59
|
-
protected
|
60
|
-
|
61
|
-
def add_default_mapping_for_current_class(mapper)
|
62
|
-
tmp_quirk = attributes_for_default_mapping
|
63
|
-
|
64
|
-
mapper.add_mapping do
|
65
|
-
rule to: tmp_quirk
|
66
|
-
|
67
|
-
rule to: :_set_metadata,
|
68
|
-
converter: ->(source, result){ fill_result_with_value(result, :_set_metadata_from_ar, source) },
|
69
|
-
reverse_converter: ->(source, result){
|
70
|
-
|
71
|
-
needs_conversion = if source.respond_to?("_needs_conversion?")
|
72
|
-
source._needs_conversion?
|
73
|
-
else
|
74
|
-
! source.nil? #would trigger lazy loading when it is a ToProcDelegator
|
75
|
-
end
|
76
|
-
|
77
|
-
fill_result_with_value(result, :_set_metadata_to_ar, source._metadata.to_hash) if needs_conversion
|
78
|
-
}
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def add_mapping_for_associations(mapper)
|
83
|
-
association_converters.each_pair do |association_name, association_converter|
|
84
|
-
|
85
|
-
lazy_quirk = self.use_lazy_loading
|
86
|
-
|
87
|
-
mapper.add_mapping do
|
88
|
-
association_rule to: association_name,
|
89
|
-
lazy_loading: lazy_quirk,
|
90
|
-
converter: association_converter
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def dump_source_class=(new_dump_source)
|
96
|
-
@dump_source_class = new_dump_source.tap do |source|
|
97
|
-
unless source.respond_to? :_metadata
|
98
|
-
source.send(:include, MetadataEnabled)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def load_source_class=(new_source)
|
104
|
-
@load_source_class=new_source.tap do |source|
|
105
|
-
unless source.respond_to? :_set_metadata_to_ar=
|
106
|
-
source.send(:include, ActiverecordToPoro::MetadataToAr)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
end
|
112
|
-
end
|