embedson 1.0.2 → 1.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 CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ef3de65a0b403021adc635d027d7ab9d8265c854
4
- data.tar.gz: ab1e6d98c11f7b4dc3f03a33f906842384937368
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YzBjMmI2NTk4ZWRhMGJkZTZlZWI3MTBjMzI4MzA1ZmU5ODg1MjA5NA==
5
+ data.tar.gz: !binary |-
6
+ MTAyNjhmYmY3OGM4ZTYxYTcxZjVhYzgzY2ZhNzYwNTM0ZGFhYjAxYQ==
5
7
  SHA512:
6
- metadata.gz: b7f1ae0b831c508775371d3667b85b3a7ac9dfa738a9641df620f2789e1128dbf9ab1c5fb82d725ba5bcb40753d73b8eba039fdd4b8eef0ac79dc827ca5b6da6
7
- data.tar.gz: c0895ec2264f217f1228f4fb6fe0a014e193f481224d3d44552a2552b595d76f4d7270e5965b7aaaa8599a189f23ad9736a97adfb6577db7708a436e1041525a
8
+ metadata.gz: !binary |-
9
+ NjEyYTE2ZmJiZGE2ODA5Njc3Y2U4ZDJiYmM4ODU5MGJjNDNlYzZjYzA4Mzhk
10
+ ZDJkYjllZWJhMWFlNjNiOGY5MzI5OTFiODlhMTNkYWQ2MjgzYTYyOGFhMTE3
11
+ YTBjY2FlMDBkNWExYzU0MzcyYjgyZGEwOWQzMzIxYjI2ZDQ5NGQ=
12
+ data.tar.gz: !binary |-
13
+ NjZhZDliMWE4YWMyZmIxNDk4YTE2ZDQ5NTVmYzZhMmFiMDZkOWE3MGQ4MWU1
14
+ YjFiOTFkNmVlZDc1Mzc3YTljODA5OWZjZGI5NTVkNDAyNjAxNzQ1M2NiYWVl
15
+ OThjODgzZjQ2YjY5YzM0MzM4NzlmMjVjYmJjNzg1ZTk2ZTgxMmI=
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 sufleR
1
+ Copyright (c) 2014 Szymon Frącczak
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -184,3 +184,6 @@ This will **not** work!
184
184
  4. Push to the branch (`git push origin my-new-feature`)
185
185
  5. Create a new Pull Request
186
186
 
187
+ ## License
188
+
189
+ Copyright (c) 2014 Szymon Frącczak. See LICENSE.txt for further details.
data/TODO.md ADDED
@@ -0,0 +1,5 @@
1
+ ## ToDo list
2
+
3
+ - support for inheritance of embedded class
4
+ - support for dirty tracking
5
+ - never enough tests
@@ -1,14 +1,8 @@
1
1
  module Embedson
2
2
  class ClassTypeError < TypeError
3
- attr_reader :wrong_name, :correct_name
4
3
 
5
4
  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})"
5
+ super("Wrong argument type #{wrong_name} (expected #{correct_name})")
12
6
  end
13
7
  end
14
8
 
@@ -18,4 +12,11 @@ module Embedson
18
12
  super("Cannot #{action} embedded #{klass_name} without a parent relation.")
19
13
  end
20
14
  end
15
+
16
+ class NoRelationDefinedError < StandardError
17
+
18
+ def initialize(klass_name, inverse_name)
19
+ super("Parent class #{klass_name} has no '#{inverse_name}' method defined or inverse_of option is not set properly.")
20
+ end
21
+ end
21
22
  end
@@ -10,7 +10,7 @@ module Embedson
10
10
 
11
11
  def define
12
12
  methods_embedded.each do |meth|
13
- klass.class_exec builder, &send(meth)
13
+ klass.class_exec builder, &self.method(meth)
14
14
  end
15
15
  end
16
16
 
@@ -20,81 +20,104 @@ module Embedson
20
20
  self.class.private_instance_methods(false).select{ |m| m.to_s.start_with?('embedded_') }
21
21
  end
22
22
 
23
- def embedded_initializer
24
- proc do |builder|
25
- alias_method :orig_initialize, :initialize
23
+ def embedded_alter_initialize(builder)
24
+ klass.send :alias_method, "#{builder.field_name}_initialize".to_sym, :initialize
25
+ klass.send :private, "#{builder.field_name}_initialize"
26
+ end
26
27
 
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
28
+ def embedded_initializer(builder)
29
+ klass.send :define_method, "initialize" do |*args|
30
+ attrs = args[0] || {}
31
+ val = attrs.delete(builder.field_name)
32
+
33
+ send("#{builder.field_name}_initialize", *args)
34
+ public_send("#{builder.field_name}=", val) if val.present?
33
35
  end
34
36
  end
35
37
 
36
- def embedded_writer
37
- proc do |builder|
38
- define_method("#{builder.field_name}=") do |arg|
39
- verify_arg_klass(arg)
38
+ def embedded_writer(builder)
39
+ klass.send :define_method, "#{builder.field_name}=" do |arg|
40
+ send("#{builder.field_name}_verify_arg_klass", arg)
40
41
 
41
- instance_variable_set(builder.instance_var_name, arg)
42
- parent = public_send(builder.field_name)
42
+ instance_variable_set(builder.instance_var_name, arg)
43
43
 
44
- send_self_to_related(parent)
45
- end
44
+ send("#{builder.field_name}_send_to_related", self)
46
45
  end
47
46
  end
48
47
 
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
48
+ def embedded_reader(builder)
49
+ klass.send :define_method, builder.field_name do
50
+ instance_variable_get(builder.instance_var_name)
54
51
  end
55
52
  end
56
53
 
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
54
+ def embedded_destroy(builder)
55
+ klass.send :define_method, 'destroy' do
56
+ call_in_transaction_for_all_embedding('save!', nil)
57
+ end
58
+ end
59
+
60
+ def embedded_save(builder)
61
+ klass.send :define_method, 'save' do
62
+ call_in_transaction_for_all_embedding('save', self)
65
63
  end
66
64
  end
67
65
 
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
66
+ def embedded_save!(builder)
67
+ klass.send :define_method, 'save!' do
68
+ raise NoParentError.new('save!', self.class.name) unless any_embedding_present?
69
+ call_in_transaction_for_all_embedding('save!', self)
70
+ end
71
+ end
72
+
73
+ def embedded_changed(builder)
74
+ klass.send :define_method, 'embedson_model_changed!' do
75
+ raise NoParentError.new('register change', self.class.name) unless any_embedding_present?
76
+
77
+ self.class.embedson_relations.each do |relation|
78
+ send("#{relation}_send_to_related", self) if public_send(relation).present?
74
79
  end
80
+ true
75
81
  end
76
82
  end
77
83
 
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
+ def embedded_send_to_related(builder)
85
+ klass.send :define_method, "#{builder.field_name}_send_to_related" do |arg|
86
+ parent = public_send(builder.field_name)
87
+ return if parent.nil?
88
+ unless parent.respond_to?(builder.inverse_set)
89
+ raise NoRelationDefinedError.new(parent.class, builder.inverse_set)
84
90
  end
91
+ parent.public_send(builder.inverse_set, arg)
85
92
  end
93
+ klass.send :private, "#{builder.field_name}_send_to_related"
86
94
  end
87
95
 
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
96
+ def embedded_call_in_transaction_for_all_embedding(builder)
97
+ return if klass.methods.include? :call_in_transaction_for_all_embedding
98
+ klass.send :define_method, :call_in_transaction_for_all_embedding do |method, object|
99
+ results = []
100
+ ActiveRecord::Base.transaction do
101
+ self.class.embedson_relations.each do |field_name|
102
+ next if public_send(field_name).nil?
103
+ send("#{field_name}_send_to_related", object)
104
+ save_res = public_send(field_name).send(method)
105
+ results << save_res
106
+ raise ActiveRecord::Rollback unless save_res
107
+ end
95
108
  end
109
+ !results.size.zero? && results.all?
96
110
  end
97
111
  end
112
+
113
+ def embedded_any_present?(builder)
114
+ return if klass.methods.include? :any_ebedding_present?
115
+ klass.send :define_method, :any_embedding_present? do
116
+ self.class.embedson_relations.any?{ |r| public_send(r).present? }
117
+ end
118
+
119
+ klass.send :private, :any_embedding_present?
120
+ end
98
121
  end
99
122
  end
100
123
  end
@@ -10,58 +10,63 @@ module Embedson
10
10
 
11
11
  def define
12
12
  methods_embeds.each do |meth|
13
- klass.class_exec builder, &send(meth)
13
+ klass.class_exec builder, &self.method(meth)
14
14
  end
15
15
  end
16
16
 
17
17
  private
18
18
 
19
19
  def methods_embeds
20
- [:writer, :reader, :related_model, :build_related_model]
20
+ [:writer, :reader, :related_model, :build_related_model, :send_to_related]
21
21
  end
22
22
 
23
- def writer
24
- proc do |builder|
25
- define_method("#{builder.field_name}=") do |arg|
26
- verify_arg_klass(arg)
27
- send_self_to_related(arg)
23
+ def writer(builder)
24
+ klass.send :define_method, "#{builder.field_name}=" do |arg|
25
+ send("#{builder.field_name}_verify_arg_klass", arg)
26
+ send("#{builder.field_name}_send_to_related", arg)
28
27
 
29
- instance_variable_set(builder.instance_var_name, arg)
30
- write_attribute(builder.column_name, arg.nil? ? arg : arg.to_h)
28
+ instance_variable_set(builder.instance_var_name, arg)
29
+ val = arg.nil? ? arg : arg.to_h.stringify_keys
30
+ unless val == read_attribute(builder.column_name)
31
+ write_attribute(builder.column_name, val)
31
32
  end
32
33
  end
33
34
  end
34
35
 
35
- def reader
36
- proc do |builder|
37
- define_method(builder.field_name) do
38
- return if read_attribute(builder.column_name).nil?
36
+ def reader(builder)
37
+ klass.send :define_method, builder.field_name do
38
+ return if read_attribute(builder.column_name).nil?
39
39
 
40
- build_related_model if instance_variable_get(builder.instance_var_name).nil?
41
- instance_variable_get(builder.instance_var_name)
42
- end
40
+ send("#{builder.field_name}_build_related_model") if instance_variable_get(builder.instance_var_name).nil?
41
+ instance_variable_get(builder.instance_var_name)
43
42
  end
44
43
  end
45
44
 
46
- def related_model
47
- proc do |builder|
48
- private
45
+ def related_model(builder)
46
+ klass.send :define_method, "#{builder.field_name}_related_model" do
47
+ builder.related_klass_name.constantize.new(read_attribute(builder.column_name))
48
+ end
49
+ klass.send :private, "#{builder.field_name}_related_model"
50
+ end
49
51
 
50
- define_method('related_model') do
51
- builder.related_klass_name.constantize.new(read_attribute(builder.column_name))
52
+ def build_related_model(builder)
53
+ klass.send :define_method, "#{builder.field_name}_build_related_model" do
54
+ related_model = send("#{builder.field_name}_related_model")
55
+ instance_variable_set(builder.instance_var_name, related_model)
56
+ if related_model.respond_to?(builder.inverse_set)
57
+ related_model.public_send(builder.inverse_set, self)
52
58
  end
53
59
  end
60
+ klass.send :private, "#{builder.field_name}_build_related_model"
54
61
  end
55
62
 
56
- def build_related_model
57
- proc do |builder|
58
- private
59
-
60
- define_method('build_related_model') do
61
- instance_variable_set(builder.instance_var_name, related_model)
62
- related_model.public_send(builder.inverse_set, self) if related_model.respond_to?(builder.inverse_set)
63
+ def send_to_related(builder)
64
+ klass.send :define_method, "#{builder.field_name}_send_to_related" do |arg|
65
+ if arg.respond_to?(builder.inverse_set) && arg.public_send(builder.inverse_get) != self
66
+ arg.public_send(builder.inverse_set, self)
63
67
  end
64
68
  end
69
+ klass.send :private, "#{builder.field_name}_send_to_related"
65
70
  end
66
71
  end
67
72
  end
@@ -26,7 +26,7 @@ module Embedson
26
26
  end
27
27
 
28
28
  def related_klass_name
29
- @related_klass_name ||= (options.fetch(:class_name, nil) || field_name).to_s.classify
29
+ @related_klass_name ||= (options.fetch(:class_name, nil) || field_name).to_s.camelize
30
30
  end
31
31
 
32
32
  def instance_var_name
@@ -45,36 +45,21 @@ module Embedson
45
45
 
46
46
  def generate_common
47
47
  methods_for_both.each do |meth|
48
- klass.class_exec self, &send(meth)
48
+ klass.class_exec self, &self.method(meth)
49
49
  end
50
50
  end
51
51
 
52
52
  def methods_for_both
53
- [:send_self_to_related, :verify_arg_klass]
53
+ [:verify_arg_klass]
54
54
  end
55
55
 
56
- def verify_arg_klass
57
- proc do |builder|
58
- private
59
-
60
- define_method('verify_arg_klass') do |arg|
61
- unless arg.nil? || arg.is_a?(builder.related_klass_name.constantize)
62
- raise ClassTypeError.new(arg.class.name, builder.related_klass_name)
63
- end
64
- end
65
- end
66
- end
67
-
68
- def send_self_to_related
69
- proc do |builder|
70
- private
71
-
72
- define_method('send_self_to_related') do |arg|
73
- if arg.respond_to?(builder.inverse_set) && arg.public_send(builder.inverse_get) != self
74
- arg.public_send(builder.inverse_set, self)
75
- end
56
+ def verify_arg_klass(builder)
57
+ klass.send(:define_method, "#{field_name}_verify_arg_klass") do |arg|
58
+ unless arg.nil? || arg.is_a?(builder.related_klass_name.constantize)
59
+ raise ClassTypeError.new(arg.class.name, builder.related_klass_name)
76
60
  end
77
61
  end
62
+ klass.send(:private, "#{field_name}_verify_arg_klass")
78
63
  end
79
64
  end
80
65
  end
@@ -1,13 +1,57 @@
1
1
  module Embedson
2
+ # Public: Defines embeds_one and embedded_in methods.
3
+ #
4
+ # Examples
5
+ #
6
+ # class Emb
7
+ # extend Embedson::Model
8
+ # end
2
9
  module Model
3
10
 
11
+ # Public: Creates methods to manage embedded class.
12
+ #
13
+ # name - Name of of relation.
14
+ # options - The Hash options used to define custom column name, class name
15
+ # and field name in embedded class (default: {}):
16
+ # :class_name - Name of class which will be ebedded.
17
+ # :column_name - Name of column where Hash representation will be stored.
18
+ # :inverse_of - Name of field where related class will store current object.
19
+ #
20
+ # Examples
21
+ #
22
+ # embeds_one :virt, class_name: Virt, column_name: :data, inverse_of: :parent
23
+ #
24
+ # embeds_one :virt
25
+ #
26
+ # Returns nothing
4
27
  def embeds_one(name, options = {})
5
28
  MethodBuilder.new(self, name, options).embeds
6
29
  end
7
30
 
31
+ # Public: Creates methods to manage parent class.
32
+ #
33
+ # name - Name of relation where parent object will be stored.
34
+ # options - The hash options used to define custom class name and field name
35
+ # in parent class (default: {}):
36
+ # :class_name - Name of class where current object will be embedded.
37
+ # :inverse_of - Name of field where parent class will keep current object.
38
+ #
39
+ # Examples
40
+ #
41
+ # embedded_in :parent, class_name: Test, inverse_of: :virt
42
+ #
43
+ # embedded_in :parent
44
+ #
45
+ # Returns nothing
8
46
  def embedded_in(name, options = {})
47
+ @embedson_relations ||= []
48
+ @embedson_relations << name
9
49
  MethodBuilder.new(self, name, options).embedded
10
50
  end
51
+
52
+ def self.extended(mod)
53
+ attr_reader :embedson_relations
54
+ end
11
55
  end
12
56
  end
13
57
 
@@ -1,3 +1,3 @@
1
1
  module Embedson
2
- VERSION = "1.0.2"
2
+ VERSION = "1.0.3"
3
3
  end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Embedson::ClassTypeError do
4
+ let(:error) { Embedson::ClassTypeError.new('Wrong', 'Correct') }
5
+
6
+ it 'raises with correct message' do
7
+ expect(error.message).to eq "Wrong argument type Wrong (expected Correct)"
8
+ end
9
+ end
10
+
11
+ describe Embedson::NoParentError do
12
+ let(:error) { Embedson::NoParentError.new('assign', 'Correct') }
13
+
14
+ it 'raises with correct message' do
15
+ expect(error.message).to eq "Cannot assign embedded Correct without a parent relation."
16
+ end
17
+ end
18
+
19
+ describe Embedson::NoParentError do
20
+ let(:error) { Embedson::NoRelationDefinedError.new('Klass', 'embedded=') }
21
+
22
+ it 'raises with correct message' do
23
+ expect(error.message).to eq "Parent class Klass has no 'embedded=' method defined or inverse_of option is not set properly."
24
+ end
25
+ end