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 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