embedson 0.0.2 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +13 -0
- data/README.md +125 -23
- data/Rakefile +4 -0
- data/embedson.gemspec +4 -1
- data/lib/embedson.rb +4 -0
- data/lib/embedson/exceptions.rb +21 -0
- data/lib/embedson/model.rb +8 -73
- data/lib/embedson/model/embedded_builder.rb +100 -0
- data/lib/embedson/model/embeds_builder.rb +68 -0
- data/lib/embedson/model/method_builder.rb +81 -0
- data/lib/embedson/version.rb +1 -1
- data/spec/embedson/model_spec.rb +421 -60
- data/spec/spec_helper.rb +17 -47
- metadata +25 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ae243da2a4284cf3d516517dcd888d43f8162ff
|
4
|
+
data.tar.gz: 288c5328e6abeac9b9189868b675e48d2a44c2e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 991fbdc6bda1324574b4a8299cee3ebaa82f5f28d07bc1730d490d1bbd442e42b8dca396a92bc6a20927f92b8bf613ddfc03b6687935c7e675921b58e756c083
|
7
|
+
data.tar.gz: b42bb795094c2ae21434b800212212edc0364311a559da8b561d833dbd42000451982ac102458a45297d17e3ed55c654daac2c9eb8c4776ad1e71fea0ee05223
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,15 +1,20 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/embedson.svg)](http://badge.fury.io/rb/embedson)
|
2
|
+
[![Dependency Status](https://gemnasium.com/sufleR/embedson.svg)](https://gemnasium.com/sufleR/embedson)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/sufleR/embedson/badges/gpa.svg)](https://codeclimate.com/github/sufleR/embedson)
|
4
|
+
[![Test Coverage](https://codeclimate.com/github/sufleR/embedson/badges/coverage.svg)](https://codeclimate.com/github/sufleR/embedson)
|
5
|
+
[![Build Status](https://travis-ci.org/sufleR/embedson.svg?branch=master)](https://travis-ci.org/sufleR/embedson)
|
6
|
+
|
1
7
|
# Embedson
|
2
8
|
|
3
|
-
Adds functionality of `
|
9
|
+
Adds functionality of `embeds_one` to ActiveRecord.
|
4
10
|
|
5
|
-
|
11
|
+
Adds functionality of `embedded_in` to any class:
|
6
12
|
|
7
|
-
|
13
|
+
- with defined `to_h` method which should return `Hash`
|
14
|
+
- initialized with Hash.
|
8
15
|
|
9
|
-
|
16
|
+
Result of `to_h` is saved json/hstore column.
|
10
17
|
|
11
|
-
1. More tests
|
12
|
-
2. Code refactoring to clean up `embedded_in` and `embeds_one` from define methods
|
13
18
|
|
14
19
|
## Installation
|
15
20
|
|
@@ -31,45 +36,142 @@ Example with [Virtus](https://github.com/solnic/virtus):
|
|
31
36
|
|
32
37
|
```RUBY
|
33
38
|
|
34
|
-
#create_tests.rb - migration
|
39
|
+
#create_tests.rb - migration
|
35
40
|
class CreateTests < ActiveRecord::Migration
|
36
41
|
def change
|
37
42
|
create_table :tests do |t|
|
38
|
-
|
39
|
-
|
43
|
+
t.json :data
|
44
|
+
end
|
40
45
|
end
|
41
46
|
end
|
42
47
|
|
43
48
|
class Test < ActiveRecord::Base
|
44
|
-
|
45
|
-
|
46
|
-
embeds_one :virt, column_name: :data, inverse_of: :parent
|
49
|
+
|
50
|
+
embeds_one :virt, class_name: Virt, column_name: :data, inverse_of: :parent
|
47
51
|
end
|
48
52
|
|
49
53
|
class Virt
|
50
54
|
include Virtus.model
|
51
55
|
extend Embedson::Model
|
52
|
-
|
56
|
+
|
53
57
|
attribute :name, String
|
54
58
|
attribute :address, Hash
|
55
|
-
|
56
|
-
embedded_in :parent, class_name: Test
|
59
|
+
|
60
|
+
embedded_in :parent, class_name: Test, inverse_of: :virt
|
57
61
|
end
|
58
|
-
|
62
|
+
|
59
63
|
virt = Virt.new(name: 'Sample', address: { street: 'Kind', number: '33' })
|
60
64
|
virt.attributes # => {:name=>"Sample", :address=>{:street=>"Kind", :number=>"33"}}
|
61
|
-
|
65
|
+
|
62
66
|
test = Test.create!
|
63
|
-
test.attributes # => {"id"=>1, "data"=>nil
|
64
|
-
|
67
|
+
test.attributes # => {"id"=>1, "data"=>nil}
|
68
|
+
|
65
69
|
test.virt = virt
|
66
70
|
test.save
|
67
|
-
test.attributes # => {"id"=>1, "data"=>{"name"=>"Sample", "address"=>{"street"=>"Kind", "number"=>"33"}
|
68
|
-
|
69
|
-
test.reload.virt.attributes # => {:name=>"Sample", :address=>{:street=>"Kind", :number=>"33"}
|
71
|
+
test.attributes # => {"id"=>1, "data"=>{"name"=>"Sample", "address"=>{"street"=>"Kind", "number"=>"33"}}
|
72
|
+
|
73
|
+
test.reload.virt.attributes # => {:name=>"Sample", :address=>{:street=>"Kind", :number=>"33"}}
|
70
74
|
test.virt == virt # => true
|
71
75
|
test.virt.parent == test # => true
|
72
|
-
|
76
|
+
|
77
|
+
```
|
78
|
+
|
79
|
+
You don't have to use all options to define ```embeds_one``` and ```embedded_in```. Just name it with downcased related class name.
|
80
|
+
|
81
|
+
|
82
|
+
```RUBY
|
83
|
+
#create_tests.rb - migration
|
84
|
+
class CreateTests < ActiveRecord::Migration
|
85
|
+
def change
|
86
|
+
create_table :tests do |t|
|
87
|
+
t.json :virt
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Test < ActiveRecord::Base
|
93
|
+
|
94
|
+
embeds_one :virt
|
95
|
+
end
|
96
|
+
|
97
|
+
class Virt
|
98
|
+
include Virtus.model
|
99
|
+
extend Embedson::Model
|
100
|
+
|
101
|
+
attribute :name, String
|
102
|
+
attribute :address, Hash
|
103
|
+
|
104
|
+
embedded_in :test
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
### Additional methods in embedded model:
|
109
|
+
|
110
|
+
- ####save
|
111
|
+
Assigns ```to_h``` result to parent and saves it with ```save```.
|
112
|
+
|
113
|
+
- ####save!
|
114
|
+
Assigns ```to_h``` result to parent and saves it with ```save!```.
|
115
|
+
|
116
|
+
- ####destroy
|
117
|
+
Assigns ```nil``` to parent and saves it with ```save!```
|
118
|
+
|
119
|
+
- ####embedson_model_changed!
|
120
|
+
This gem does not provide dirty tracking of embedded model. To register change in parent model use this method in your setter.
|
121
|
+
|
122
|
+
|
123
|
+
```RUBY
|
124
|
+
|
125
|
+
def your_variable=(arg)
|
126
|
+
@your_variable = arg
|
127
|
+
embedson_model_changed!
|
128
|
+
end
|
129
|
+
|
130
|
+
```
|
131
|
+
|
132
|
+
## Known issues
|
133
|
+
|
134
|
+
- Placing ```initialize``` method after ```embedded_in``` and using ```Emb.new(parent: parent)```
|
135
|
+
This examples will work:
|
136
|
+
|
137
|
+
```RUBY
|
138
|
+
class Emb
|
139
|
+
extend Embedson::Model
|
140
|
+
|
141
|
+
def initialize(attributes = {})
|
142
|
+
# do your work here
|
143
|
+
end
|
144
|
+
|
145
|
+
embedded_in :parent
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
```RUBY
|
150
|
+
class Emb
|
151
|
+
extend Embedson::Model
|
152
|
+
|
153
|
+
embedded_in :parent
|
154
|
+
|
155
|
+
def initialize(attributes = {})
|
156
|
+
self.parent = attributes[:parent]
|
157
|
+
# do your work here
|
158
|
+
end
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
This will **not** work!
|
163
|
+
|
164
|
+
```RUBY
|
165
|
+
class Emb
|
166
|
+
extend Embedson::Model
|
167
|
+
embedded_in :parent
|
168
|
+
|
169
|
+
def initialize(attributes = {})
|
170
|
+
# if you forget about assigning parent
|
171
|
+
# do your work here
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
73
175
|
```
|
74
176
|
|
75
177
|
|
data/Rakefile
CHANGED
data/embedson.gemspec
CHANGED
@@ -18,12 +18,15 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.
|
21
|
+
spec.required_ruby_version = ">= 1.9.3"
|
22
|
+
|
23
|
+
spec.add_dependency "activerecord", ">= 4"
|
22
24
|
spec.add_development_dependency "bundler", "~> 1.6"
|
23
25
|
spec.add_development_dependency "rake"
|
24
26
|
spec.add_development_dependency "rspec"
|
25
27
|
spec.add_development_dependency "pg"
|
26
28
|
spec.add_development_dependency "pry"
|
27
29
|
spec.add_development_dependency "with_model"
|
30
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
28
31
|
|
29
32
|
end
|
data/lib/embedson.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Embedson
|
2
|
+
class ClassTypeError < TypeError
|
3
|
+
attr_reader :wrong_name, :correct_name
|
4
|
+
|
5
|
+
def initialize(wrong_name, correct_name)
|
6
|
+
@wrong_name, @correct_name = wrong_name, correct_name
|
7
|
+
super(build_message)
|
8
|
+
end
|
9
|
+
|
10
|
+
def build_message
|
11
|
+
"wrong argument type #{wrong_name} (expected #{correct_name})"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class NoParentError < StandardError
|
16
|
+
|
17
|
+
def initialize(action, klass_name)
|
18
|
+
super("Cannot #{action} embedded #{klass_name} without a parent relation.")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/embedson/model.rb
CHANGED
@@ -1,79 +1,14 @@
|
|
1
|
-
module Embedson
|
1
|
+
module Embedson
|
2
|
+
module Model
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
klass_name = (options.fetch(:class_name, nil) || name).to_s.classify
|
6
|
-
inverse_get = options.fetch(:inverse_of, nil) || self.name.downcase
|
7
|
-
inverse_set = "#{inverse_get}="
|
8
|
-
|
9
|
-
define_method("#{name}=") do |arg|
|
10
|
-
raise TypeError, "wrong argument type #{arg.class.name} (expected #{klass_name})" unless arg.nil? || arg.is_a?(klass_name.constantize)
|
11
|
-
|
12
|
-
if arg.respond_to?(inverse_set) && arg.public_send(inverse_get) != self
|
13
|
-
arg.public_send(inverse_set, self)
|
14
|
-
end
|
15
|
-
|
16
|
-
instance_variable_set("@#{name}", arg)
|
17
|
-
write_attribute(column_name, arg.nil? ? arg : arg.to_h)
|
18
|
-
end
|
19
|
-
|
20
|
-
define_method(name) do
|
21
|
-
return if read_attribute(column_name).nil?
|
22
|
-
|
23
|
-
if instance_variable_get("@#{name}").nil?
|
24
|
-
model = klass_name.constantize.new(read_attribute(column_name))
|
25
|
-
instance_variable_set("@#{name}", model)
|
26
|
-
model.public_send(inverse_set, self) if model.respond_to?(inverse_set)
|
27
|
-
end
|
28
|
-
instance_variable_get("@#{name}")
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def embedded_in(name, options = {})
|
33
|
-
klass_name = (options.fetch(:class_name, nil) || name).to_s.classify
|
34
|
-
inverse_get = options.fetch(:inverse_of, nil) || self.name.demodulize.tableize.singularize
|
35
|
-
inverse_set = "#{inverse_get}="
|
36
|
-
|
37
|
-
|
38
|
-
define_method(name) do
|
39
|
-
instance_variable_get("@#{name}")
|
4
|
+
def embeds_one(name, options = {})
|
5
|
+
MethodBuilder.new(self, name, options).embeds
|
40
6
|
end
|
41
7
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
instance_variable_set("@#{name}", arg)
|
46
|
-
parent = public_send(name)
|
47
|
-
|
48
|
-
if parent.respond_to?(inverse_set) && parent.public_send(inverse_get) != self
|
49
|
-
parent.public_send(inverse_set, self)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
define_method('destroy') do
|
54
|
-
parent = public_send(name)
|
55
|
-
return false unless parent.present?
|
56
|
-
parent.public_send(inverse_set, nil)
|
57
|
-
parent.save!
|
58
|
-
end
|
59
|
-
|
60
|
-
define_method('save') do
|
61
|
-
parent = public_send(name)
|
62
|
-
return false unless parent.present?
|
63
|
-
parent.save
|
64
|
-
end
|
65
|
-
|
66
|
-
define_method('save!') do
|
67
|
-
parent = public_send(name)
|
68
|
-
raise "No parent model defined!" unless parent.present?
|
69
|
-
parent.save!
|
70
|
-
end
|
71
|
-
|
72
|
-
define_method('embedson_model_changed!') do
|
73
|
-
parent = public_send(name)
|
74
|
-
raise "No parent model defined!" unless parent.present?
|
75
|
-
parent.public_send(inverse_set, self)
|
76
|
-
true
|
8
|
+
def embedded_in(name, options = {})
|
9
|
+
MethodBuilder.new(self, name, options).embedded
|
77
10
|
end
|
78
11
|
end
|
79
12
|
end
|
13
|
+
|
14
|
+
ActiveRecord::Base.send :extend, Embedson::Model
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Embedson
|
2
|
+
module Model
|
3
|
+
class EmbeddedBuilder
|
4
|
+
attr_reader :builder, :klass
|
5
|
+
|
6
|
+
def initialize(builder)
|
7
|
+
@builder = builder
|
8
|
+
@klass = builder.klass
|
9
|
+
end
|
10
|
+
|
11
|
+
def define
|
12
|
+
methods_embedded.each do |meth|
|
13
|
+
klass.class_exec builder, &send(meth)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def methods_embedded
|
20
|
+
self.class.private_instance_methods(false).select{ |m| m.to_s.start_with?('embedded_') }
|
21
|
+
end
|
22
|
+
|
23
|
+
def embedded_initializer
|
24
|
+
proc do |builder|
|
25
|
+
alias_method :orig_initialize, :initialize
|
26
|
+
|
27
|
+
define_method("initialize") do |*args|
|
28
|
+
attrs = args[0]
|
29
|
+
attrs ||= {}
|
30
|
+
public_send("#{builder.field_name}=", attrs.fetch(builder.field_name, nil))
|
31
|
+
orig_initialize(*args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def embedded_writer
|
37
|
+
proc do |builder|
|
38
|
+
define_method("#{builder.field_name}=") do |arg|
|
39
|
+
verify_arg_klass(arg)
|
40
|
+
|
41
|
+
instance_variable_set(builder.instance_var_name, arg)
|
42
|
+
parent = public_send(builder.field_name)
|
43
|
+
|
44
|
+
send_self_to_related(parent)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def embedded_reader
|
50
|
+
proc do |builder|
|
51
|
+
define_method(builder.field_name) do
|
52
|
+
instance_variable_get(builder.instance_var_name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def embedded_destroy
|
58
|
+
proc do |builder|
|
59
|
+
define_method('destroy') do
|
60
|
+
parent = public_send(builder.field_name)
|
61
|
+
return false unless parent.present?
|
62
|
+
parent.public_send(builder.inverse_set, nil)
|
63
|
+
parent.save!
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def embedded_save
|
69
|
+
proc do |builder|
|
70
|
+
define_method('save') do
|
71
|
+
parent = public_send(builder.field_name)
|
72
|
+
return false unless parent.present?
|
73
|
+
parent.save
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def embedded_save!
|
79
|
+
proc do |builder|
|
80
|
+
define_method('save!') do
|
81
|
+
parent = public_send(builder.field_name)
|
82
|
+
raise NoParentError.new('save', self.class.name) unless parent.present?
|
83
|
+
parent.save!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def embedded_changed
|
89
|
+
proc do |builder|
|
90
|
+
define_method('embedson_model_changed!') do
|
91
|
+
parent = public_send(builder.field_name)
|
92
|
+
raise NoParentError.new('register change', self.class.name) unless parent.present?
|
93
|
+
parent.public_send(builder.inverse_set, self)
|
94
|
+
true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|