embedson 0.0.2 → 1.0.1
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/.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
|
+
[](http://badge.fury.io/rb/embedson)
|
2
|
+
[](https://gemnasium.com/sufleR/embedson)
|
3
|
+
[](https://codeclimate.com/github/sufleR/embedson)
|
4
|
+
[](https://codeclimate.com/github/sufleR/embedson)
|
5
|
+
[](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
|