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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9b52da8a4596e89eaa71d2a1f109988c9157cc51
4
- data.tar.gz: adf12afbc8c12ca1c1b751def0865010217fa421
3
+ metadata.gz: 1ae243da2a4284cf3d516517dcd888d43f8162ff
4
+ data.tar.gz: 288c5328e6abeac9b9189868b675e48d2a44c2e8
5
5
  SHA512:
6
- metadata.gz: 9463f20a91ef4449246cda718aa4e1a2671d1b7fe6a37d2f92836392e6f76b3caa84b0042482b5e400c653caa7deb6969c3335b3016b17f7e5339946283a5a1b
7
- data.tar.gz: e0346bfea5cd34fc28bd695b96ab5160421a65f3c42cf7e8aac19fd0fcdf42e81fe8fb78aeb68cdd4333ae28990b60dc1de2d3c551ae9d68f737cccfe3a39349
6
+ metadata.gz: 991fbdc6bda1324574b4a8299cee3ebaa82f5f28d07bc1730d490d1bbd442e42b8dca396a92bc6a20927f92b8bf613ddfc03b6687935c7e675921b58e756c083
7
+ data.tar.gz: b42bb795094c2ae21434b800212212edc0364311a559da8b561d833dbd42000451982ac102458a45297d17e3ed55c654daac2c9eb8c4776ad1e71fea0ee05223
data/.gitignore CHANGED
@@ -19,4 +19,6 @@ tmp
19
19
  *.so
20
20
  *.o
21
21
  *.a
22
+ *.swp
23
+ *.swo
22
24
  mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ - "2.1.3"
6
+
7
+ addons:
8
+ postgresql: "9.3"
9
+
10
+ env:
11
+ - BUILDER=travis
12
+ before_script:
13
+ - psql -c 'create database travis_ci_test;' -U postgres
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 `embedded_one` and `embedded_in`.
9
+ Adds functionality of `embeds_one` to ActiveRecord.
4
10
 
5
- Embeded class is saved in json column.
11
+ Adds functionality of `embedded_in` to any class:
6
12
 
7
- Embedded class have to provide `to_h` method which should return data to store in database.
13
+ - with defined `to_h` method which should return `Hash`
14
+ - initialized with Hash.
8
15
 
9
- ####TODO
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
- t.json :data
39
- end
43
+ t.json :data
44
+ end
40
45
  end
41
46
  end
42
47
 
43
48
  class Test < ActiveRecord::Base
44
- extend Embedson::Model
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"}, :children=>[]}
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
@@ -1,2 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
2
3
 
4
+ task :default => :spec
5
+
6
+ RSpec::Core::RakeTask.new
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.add_dependency "activerecord"
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
@@ -1,2 +1,6 @@
1
1
  require "embedson/version"
2
+ require "embedson/exceptions"
2
3
  require "embedson/model"
4
+ require "embedson/model/method_builder"
5
+ require "embedson/model/embeds_builder"
6
+ require "embedson/model/embedded_builder"
@@ -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
@@ -1,79 +1,14 @@
1
- module Embedson::Model
1
+ module Embedson
2
+ module Model
2
3
 
3
- def embeds_one(name, options = {})
4
- column_name = options.fetch(:column_name, nil) || name
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
- define_method("#{name}=") do |arg|
43
- raise TypeError, "wrong argument type #{arg.class.name} (expected #{klass_name})" unless arg.nil? || arg.is_a?(klass_name.constantize)
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