hstore-document 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +137 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +20 -0
- data/README.md +63 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/hstore-document.gemspec +91 -0
- data/lib/active_record/associations/builder/embeds_one.rb +18 -0
- data/lib/active_record/associations/embeds_one_association.rb +77 -0
- data/lib/active_record/embeds_reflection.rb +40 -0
- data/lib/hstore-document.rb +9 -0
- data/lib/hstore/document.rb +217 -0
- data/lib/hstore/fields.rb +60 -0
- data/lib/hstore/railtie.rb +14 -0
- data/spec/document_spec.rb +87 -0
- data/spec/integration_spec.rb +117 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/models.rb +20 -0
- metadata +226 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a6b822aa3298952dc3444dc2c5198d92c7901aae
|
4
|
+
data.tar.gz: 2e08a5d7627f638ffd4c68d9618075172832f312
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eba7709d09faff0caf967936a1be26ed47c6e5a2e937d1d500b79144555f7f58926cb3f4711dbc33e476cbe399ea273e813c756c9c7aa1990f23554d8ed35290
|
7
|
+
data.tar.gz: 3206bd0eaaa2fbc5d957191247fef144e4e5f8d5078e1c73d3256214ec21beb97130a28964b93eaedb4e10418c23927e2fa5168748aa695d8282a61290f2a712
|
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 2.14"
|
10
|
+
gem "rdoc", "~> 3.12"
|
11
|
+
gem "bundler", "~> 1.0"
|
12
|
+
gem "jeweler", "~> 1.8.7"
|
13
|
+
gem "pry"
|
14
|
+
gem "pry-debugger"
|
15
|
+
gem "guard"
|
16
|
+
gem "guard-rspec"
|
17
|
+
end
|
18
|
+
|
19
|
+
gem 'pg'
|
20
|
+
gem 'pg-hstore'
|
21
|
+
gem 'activerecord', '>=3.2', '<4.1'
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (4.0.4)
|
5
|
+
activesupport (= 4.0.4)
|
6
|
+
builder (~> 3.1.0)
|
7
|
+
activerecord (4.0.4)
|
8
|
+
activemodel (= 4.0.4)
|
9
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
10
|
+
activesupport (= 4.0.4)
|
11
|
+
arel (~> 4.0.0)
|
12
|
+
activerecord-deprecated_finders (1.0.3)
|
13
|
+
activesupport (4.0.4)
|
14
|
+
i18n (~> 0.6, >= 0.6.9)
|
15
|
+
minitest (~> 4.2)
|
16
|
+
multi_json (~> 1.3)
|
17
|
+
thread_safe (~> 0.1)
|
18
|
+
tzinfo (~> 0.3.37)
|
19
|
+
addressable (2.3.6)
|
20
|
+
arel (4.0.2)
|
21
|
+
atomic (1.1.16)
|
22
|
+
builder (3.1.4)
|
23
|
+
celluloid (0.15.2)
|
24
|
+
timers (~> 1.1.0)
|
25
|
+
celluloid-io (0.15.0)
|
26
|
+
celluloid (>= 0.15.0)
|
27
|
+
nio4r (>= 0.5.0)
|
28
|
+
coderay (1.1.0)
|
29
|
+
columnize (0.3.6)
|
30
|
+
debugger (1.6.6)
|
31
|
+
columnize (>= 0.3.1)
|
32
|
+
debugger-linecache (~> 1.2.0)
|
33
|
+
debugger-ruby_core_source (~> 1.3.2)
|
34
|
+
debugger-linecache (1.2.0)
|
35
|
+
debugger-ruby_core_source (1.3.2)
|
36
|
+
diff-lcs (1.2.5)
|
37
|
+
faraday (0.8.9)
|
38
|
+
multipart-post (~> 1.2.0)
|
39
|
+
ffi (1.9.3)
|
40
|
+
formatador (0.2.4)
|
41
|
+
git (1.2.6)
|
42
|
+
github_api (0.10.1)
|
43
|
+
addressable
|
44
|
+
faraday (~> 0.8.1)
|
45
|
+
hashie (>= 1.2)
|
46
|
+
multi_json (~> 1.4)
|
47
|
+
nokogiri (~> 1.5.2)
|
48
|
+
oauth2
|
49
|
+
guard (2.6.0)
|
50
|
+
formatador (>= 0.2.4)
|
51
|
+
listen (~> 2.7)
|
52
|
+
lumberjack (~> 1.0)
|
53
|
+
pry (>= 0.9.12)
|
54
|
+
thor (>= 0.18.1)
|
55
|
+
guard-rspec (4.2.8)
|
56
|
+
guard (~> 2.1)
|
57
|
+
rspec (>= 2.14, < 4.0)
|
58
|
+
hashie (2.0.5)
|
59
|
+
highline (1.6.21)
|
60
|
+
i18n (0.6.9)
|
61
|
+
jeweler (1.8.8)
|
62
|
+
builder
|
63
|
+
bundler (~> 1.0)
|
64
|
+
git (>= 1.2.5)
|
65
|
+
github_api (= 0.10.1)
|
66
|
+
highline (>= 1.6.15)
|
67
|
+
nokogiri (= 1.5.10)
|
68
|
+
rake
|
69
|
+
rdoc
|
70
|
+
json (1.8.1)
|
71
|
+
jwt (0.1.11)
|
72
|
+
multi_json (>= 1.5)
|
73
|
+
listen (2.7.1)
|
74
|
+
celluloid (>= 0.15.2)
|
75
|
+
celluloid-io (>= 0.15.0)
|
76
|
+
rb-fsevent (>= 0.9.3)
|
77
|
+
rb-inotify (>= 0.9)
|
78
|
+
lumberjack (1.0.5)
|
79
|
+
method_source (0.8.2)
|
80
|
+
minitest (4.7.5)
|
81
|
+
multi_json (1.9.2)
|
82
|
+
multi_xml (0.5.5)
|
83
|
+
multipart-post (1.2.0)
|
84
|
+
nio4r (1.0.0)
|
85
|
+
nokogiri (1.5.10)
|
86
|
+
oauth2 (0.9.3)
|
87
|
+
faraday (>= 0.8, < 0.10)
|
88
|
+
jwt (~> 0.1.8)
|
89
|
+
multi_json (~> 1.3)
|
90
|
+
multi_xml (~> 0.5)
|
91
|
+
rack (~> 1.2)
|
92
|
+
pg (0.17.1)
|
93
|
+
pg-hstore (1.2.0)
|
94
|
+
pry (0.9.12.6)
|
95
|
+
coderay (~> 1.0)
|
96
|
+
method_source (~> 0.8)
|
97
|
+
slop (~> 3.4)
|
98
|
+
pry-debugger (0.2.2)
|
99
|
+
debugger (~> 1.3)
|
100
|
+
pry (~> 0.9.10)
|
101
|
+
rack (1.5.2)
|
102
|
+
rake (10.2.2)
|
103
|
+
rb-fsevent (0.9.4)
|
104
|
+
rb-inotify (0.9.3)
|
105
|
+
ffi (>= 0.5.0)
|
106
|
+
rdoc (3.12.2)
|
107
|
+
json (~> 1.4)
|
108
|
+
rspec (2.14.1)
|
109
|
+
rspec-core (~> 2.14.0)
|
110
|
+
rspec-expectations (~> 2.14.0)
|
111
|
+
rspec-mocks (~> 2.14.0)
|
112
|
+
rspec-core (2.14.8)
|
113
|
+
rspec-expectations (2.14.5)
|
114
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
115
|
+
rspec-mocks (2.14.6)
|
116
|
+
slop (3.5.0)
|
117
|
+
thor (0.19.1)
|
118
|
+
thread_safe (0.3.1)
|
119
|
+
atomic (>= 1.1.7, < 2)
|
120
|
+
timers (1.1.0)
|
121
|
+
tzinfo (0.3.39)
|
122
|
+
|
123
|
+
PLATFORMS
|
124
|
+
ruby
|
125
|
+
|
126
|
+
DEPENDENCIES
|
127
|
+
activerecord (>= 3.2, < 4.1)
|
128
|
+
bundler (~> 1.0)
|
129
|
+
guard
|
130
|
+
guard-rspec
|
131
|
+
jeweler (~> 1.8.7)
|
132
|
+
pg
|
133
|
+
pg-hstore
|
134
|
+
pry
|
135
|
+
pry-debugger
|
136
|
+
rdoc (~> 3.12)
|
137
|
+
rspec (~> 2.14)
|
data/Guardfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :rspec do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
|
9
|
+
# Rails example
|
10
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
11
|
+
watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
12
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
13
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
14
|
+
watch('config/routes.rb') { "spec/routing" }
|
15
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
16
|
+
|
17
|
+
# Capybara features specs
|
18
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
|
19
|
+
|
20
|
+
# Turnip features and steps
|
21
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
22
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
23
|
+
end
|
24
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Roman Shterenzon
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# hstore-document
|
2
|
+
|
3
|
+
This gem allows embedding of an auxillary object in an ActiveRecord::Base model using Hstore.
|
4
|
+
This brings the niceties of Mongo's embedded documents to PostgreSQL.
|
5
|
+
It's modelled after Mongoid's embedded documents.
|
6
|
+
|
7
|
+
An example is worth a thousand words:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
|
11
|
+
class Person < ActiveRecord::Base
|
12
|
+
embeds :address
|
13
|
+
|
14
|
+
# you can even do that:
|
15
|
+
accepts_nested_attributes_for :address
|
16
|
+
end
|
17
|
+
|
18
|
+
class Address
|
19
|
+
include Hstore::Document
|
20
|
+
|
21
|
+
field :street
|
22
|
+
field :number, type: Fixnum
|
23
|
+
field :zip, type: Fixnum
|
24
|
+
field :business, type: Boolean
|
25
|
+
# though it's probably a very bad idea to store large arrays
|
26
|
+
field :phones, type: Array
|
27
|
+
|
28
|
+
validates_presence_of :street, :number
|
29
|
+
validates_numericality_of :number, :zip
|
30
|
+
|
31
|
+
before_validation :check_zip_code
|
32
|
+
|
33
|
+
# etc.
|
34
|
+
end
|
35
|
+
|
36
|
+
person = Person.new
|
37
|
+
person.address = Address.new(street: 'Elm', number: 'invalid')
|
38
|
+
person.save # => false (validations failed)
|
39
|
+
|
40
|
+
person.address.number = 13
|
41
|
+
person.save
|
42
|
+
|
43
|
+
person.address.destroy
|
44
|
+
```
|
45
|
+
|
46
|
+
## TODO
|
47
|
+
|
48
|
+
* Rails 4.1 support
|
49
|
+
* Query support, e.g. Person.where('address.street' => 'Elm')
|
50
|
+
|
51
|
+
## Contributing to hstore-document
|
52
|
+
|
53
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
54
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
55
|
+
* Fork the project.
|
56
|
+
* Start a feature/bugfix branch.
|
57
|
+
* Commit and push until you are happy with your contribution.
|
58
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
59
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
60
|
+
|
61
|
+
## Copyright
|
62
|
+
|
63
|
+
Copyright (c) 2013 Roman Shterenzon. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "hstore-document"
|
18
|
+
gem.homepage = "http://github.com/romanbsd/hstore-document"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Embedded documents using HStore}
|
21
|
+
gem.description = %Q{Allows embedding documents in ActiveRecord models using PostgreSQL HStore}
|
22
|
+
gem.email = "roman.shterenzon@gmail.com"
|
23
|
+
gem.authors = ["Roman Shterenzon"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "hstore-document #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: hstore-document 0.1.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "hstore-document"
|
9
|
+
s.version = "0.1.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Roman Shterenzon"]
|
14
|
+
s.date = "2014-04-22"
|
15
|
+
s.description = "Allows embedding documents in ActiveRecord models using PostgreSQL HStore"
|
16
|
+
s.email = "roman.shterenzon@gmail.com"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".rspec",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"Guardfile",
|
27
|
+
"LICENSE.txt",
|
28
|
+
"README.md",
|
29
|
+
"Rakefile",
|
30
|
+
"VERSION",
|
31
|
+
"hstore-document.gemspec",
|
32
|
+
"lib/active_record/associations/builder/embeds_one.rb",
|
33
|
+
"lib/active_record/associations/embeds_one_association.rb",
|
34
|
+
"lib/active_record/embeds_reflection.rb",
|
35
|
+
"lib/hstore-document.rb",
|
36
|
+
"lib/hstore/document.rb",
|
37
|
+
"lib/hstore/fields.rb",
|
38
|
+
"lib/hstore/railtie.rb",
|
39
|
+
"spec/document_spec.rb",
|
40
|
+
"spec/integration_spec.rb",
|
41
|
+
"spec/spec_helper.rb",
|
42
|
+
"spec/support/models.rb"
|
43
|
+
]
|
44
|
+
s.homepage = "http://github.com/romanbsd/hstore-document"
|
45
|
+
s.licenses = ["MIT"]
|
46
|
+
s.rubygems_version = "2.2.2"
|
47
|
+
s.summary = "Embedded documents using HStore"
|
48
|
+
|
49
|
+
if s.respond_to? :specification_version then
|
50
|
+
s.specification_version = 4
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<pg>, [">= 0"])
|
54
|
+
s.add_runtime_dependency(%q<pg-hstore>, [">= 0"])
|
55
|
+
s.add_runtime_dependency(%q<activerecord>, ["< 4.1", ">= 3.2"])
|
56
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.14"])
|
57
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
58
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
59
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.7"])
|
60
|
+
s.add_development_dependency(%q<pry>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<pry-debugger>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<guard>, [">= 0"])
|
63
|
+
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<pg>, [">= 0"])
|
66
|
+
s.add_dependency(%q<pg-hstore>, [">= 0"])
|
67
|
+
s.add_dependency(%q<activerecord>, ["< 4.1", ">= 3.2"])
|
68
|
+
s.add_dependency(%q<rspec>, ["~> 2.14"])
|
69
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
70
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
71
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.7"])
|
72
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
73
|
+
s.add_dependency(%q<pry-debugger>, [">= 0"])
|
74
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
75
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
76
|
+
end
|
77
|
+
else
|
78
|
+
s.add_dependency(%q<pg>, [">= 0"])
|
79
|
+
s.add_dependency(%q<pg-hstore>, [">= 0"])
|
80
|
+
s.add_dependency(%q<activerecord>, ["< 4.1", ">= 3.2"])
|
81
|
+
s.add_dependency(%q<rspec>, ["~> 2.14"])
|
82
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
83
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
84
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.7"])
|
85
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
86
|
+
s.add_dependency(%q<pry-debugger>, [">= 0"])
|
87
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
88
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_record/associations/builder/association'
|
2
|
+
require 'active_record/associations/builder/singular_association'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module Associations
|
6
|
+
module Builder
|
7
|
+
class EmbedsOne < SingularAssociation # :nodoc:
|
8
|
+
self.macro = :embeds_one if respond_to?(:macro=)
|
9
|
+
self.valid_options = [:class_name]
|
10
|
+
|
11
|
+
# For AR 4.0
|
12
|
+
def macro
|
13
|
+
:embeds_one
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'active_record/associations/association'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
module ClassMethods
|
6
|
+
# @param [Symbol, String] name of association
|
7
|
+
# @param [Hash] options
|
8
|
+
# @option options [Boolean] :validate Validate associaited object (default: true)
|
9
|
+
# @option options [String] :class_name Name of the associated class
|
10
|
+
def embeds(name, options = {})
|
11
|
+
validates_associated(name) unless options.delete(:validate) == false
|
12
|
+
if ActiveRecord::VERSION::MAJOR < 4
|
13
|
+
Builder::EmbedsOne.build(self, name, options)
|
14
|
+
else
|
15
|
+
Builder::EmbedsOne.build(self, name, options, nil)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class EmbedsOneAssociation < Association # :nodoc:
|
21
|
+
def reader(force_reload = false)
|
22
|
+
return @reader if @reader
|
23
|
+
data = owner.read_attribute(reflection.name)
|
24
|
+
if data.present?
|
25
|
+
reflection.klass.from_hstore(data).tap do |inst|
|
26
|
+
inst.send(:_owner=, owner)
|
27
|
+
@reader = inst
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def writer(record)
|
33
|
+
replace(record, false)
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace(record, save = true)
|
37
|
+
@reader = record
|
38
|
+
if record
|
39
|
+
record.send(:_owner=, owner)
|
40
|
+
end
|
41
|
+
owner.send(:write_attribute, reflection.name, record ? record.to_hstore : nil)
|
42
|
+
owner.save if save
|
43
|
+
end
|
44
|
+
|
45
|
+
def create(attributes = {}, options = {}, &block)
|
46
|
+
create_record(attributes, options, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def create!(attributes = {}, options = {}, &block)
|
50
|
+
create_record(attributes, options, true, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def build(attributes = {}, options = {})
|
54
|
+
build_record(attributes, options).tap do |record|
|
55
|
+
yield(record) if block_given?
|
56
|
+
replace(record, false)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def build_record(attributes, options = nil)
|
62
|
+
reflection.build_association(attributes) do |record|
|
63
|
+
record.assign_attributes(attributes, without_protection: true)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_record(attributes, options, raise_error = false)
|
68
|
+
build_record(attributes, options).tap do |record|
|
69
|
+
yield record if block_given?
|
70
|
+
saved = replace(record)
|
71
|
+
raise RecordInvalid.new(record) if !saved && raise_error
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'active_record/reflection'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module Reflection
|
6
|
+
class AssociationReflection # :nodoc:
|
7
|
+
def association_class_with_embeds_one
|
8
|
+
if macro == :embeds_one
|
9
|
+
Associations::EmbedsOneAssociation
|
10
|
+
else
|
11
|
+
association_class_without_embeds_one
|
12
|
+
end
|
13
|
+
end
|
14
|
+
alias_method_chain :association_class, :embeds_one
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods # :nodoc:
|
18
|
+
if ActiveRecord::VERSION::MAJOR < 4
|
19
|
+
def create_reflection_with_embeds(macro, name, options, active_record)
|
20
|
+
unless macro == :embeds_one
|
21
|
+
return create_reflection_without_embeds(macro, name, options, active_record)
|
22
|
+
end
|
23
|
+
AssociationReflection.new(macro, name, options, active_record).tap do |reflection|
|
24
|
+
reflections.merge!(name => reflection)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
else
|
28
|
+
def create_reflection_with_embeds(macro, name, scope, options, active_record)
|
29
|
+
unless macro == :embeds_one
|
30
|
+
return create_reflection_without_embeds(macro, name, scope, options, active_record)
|
31
|
+
end
|
32
|
+
AssociationReflection.new(macro, name, scope, options, active_record).tap do |reflection|
|
33
|
+
reflections.merge!(name => reflection)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
alias_method_chain :create_reflection, :embeds
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
if defined?(Rails)
|
2
|
+
require 'hstore/railtie'
|
3
|
+
else
|
4
|
+
require 'active_record'
|
5
|
+
require 'active_record/embeds_reflection'
|
6
|
+
require 'active_record/associations/builder/embeds_one'
|
7
|
+
require 'active_record/associations/embeds_one_association'
|
8
|
+
require 'hstore/document'
|
9
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_model/naming'
|
3
|
+
require 'active_model/conversion'
|
4
|
+
require 'active_model/translation'
|
5
|
+
require 'active_model/validations'
|
6
|
+
require 'active_record/readonly_attributes'
|
7
|
+
require 'pg_hstore'
|
8
|
+
require 'hstore/fields'
|
9
|
+
|
10
|
+
module Hstore
|
11
|
+
module Document
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
included do
|
15
|
+
include Hstore::Fields
|
16
|
+
include ActiveModel::Conversion
|
17
|
+
include ActiveModel::Validations
|
18
|
+
include ActiveModel::Serialization
|
19
|
+
include ActiveModel::Dirty
|
20
|
+
include ActiveModel::AttributeMethods
|
21
|
+
include ActiveRecord::AttributeAssignment
|
22
|
+
include ActiveRecord::ReadonlyAttributes
|
23
|
+
include ActiveRecord::AttributeMethods::BeforeTypeCast
|
24
|
+
include ActiveRecord::AttributeMethods::Serialization
|
25
|
+
extend ActiveModel::Naming
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def attributes_protected_by_default
|
29
|
+
[]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class_attribute :_attr_defaults, instance_writer: false
|
34
|
+
self._attr_defaults = {}
|
35
|
+
alias_method :as_json, :serializable_hash
|
36
|
+
end
|
37
|
+
|
38
|
+
Attribute = ActiveRecord::AttributeMethods::Serialization::Attribute
|
39
|
+
class OwnerMissingError < StandardError; end
|
40
|
+
|
41
|
+
def initialize(attributes = nil, options = {})
|
42
|
+
@attributes = {}
|
43
|
+
assign_attributes(self.class._attr_defaults)
|
44
|
+
|
45
|
+
if options[:serialized] and attributes.present?
|
46
|
+
attributes = attributes.stringify_keys
|
47
|
+
self.class.serialized_attributes.each do |key, coder|
|
48
|
+
if attributes.key?(key)
|
49
|
+
attributes[key] = Attribute.new(coder, attributes[key], :serialized)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
assign_attributes(attributes.except(self.class.serialized_attributes.keys))
|
53
|
+
else
|
54
|
+
assign_attributes(attributes)
|
55
|
+
end
|
56
|
+
changed_attributes.clear
|
57
|
+
end
|
58
|
+
|
59
|
+
def attributes
|
60
|
+
attrs = {}
|
61
|
+
@attributes.each_key { |name| attrs[name] = read_attribute(name) }
|
62
|
+
attrs
|
63
|
+
end
|
64
|
+
|
65
|
+
def eql?(other)
|
66
|
+
other.attributes == attributes
|
67
|
+
end
|
68
|
+
alias :== :eql?
|
69
|
+
|
70
|
+
def update_attribute(name, value)
|
71
|
+
name = name.to_s
|
72
|
+
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
|
73
|
+
send("#{name}=", value)
|
74
|
+
save(validate: false)
|
75
|
+
end
|
76
|
+
|
77
|
+
def read_attribute(name)
|
78
|
+
name = name.to_s
|
79
|
+
if @attributes.key?(name)
|
80
|
+
val = @attributes[name]
|
81
|
+
if self.class.serialized_attributes.key?(name)
|
82
|
+
val = val.unserialized_value
|
83
|
+
end
|
84
|
+
val
|
85
|
+
else
|
86
|
+
_attr_defaults[name]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_attribute(name, value)
|
91
|
+
name = name.to_s
|
92
|
+
if coder = self.class.serialized_attributes[name] and ! value.respond_to?(:serialized_value)
|
93
|
+
value = Attribute.new(coder, value, :unserialized)
|
94
|
+
end
|
95
|
+
attribute_will_change!(name)
|
96
|
+
@attributes[name] = value
|
97
|
+
end
|
98
|
+
|
99
|
+
def update_attributes(attributes, options)
|
100
|
+
assign_attributes(attributes, options)
|
101
|
+
save
|
102
|
+
end
|
103
|
+
|
104
|
+
def update_attributes!(attributes, options)
|
105
|
+
assign_attributes(attributes, options)
|
106
|
+
save!
|
107
|
+
end
|
108
|
+
|
109
|
+
def persisted?
|
110
|
+
! changed? && _owner && _owner.persisted?
|
111
|
+
end
|
112
|
+
|
113
|
+
def destroyed?
|
114
|
+
@_destroyed || (_owner && _owner.destroyed?)
|
115
|
+
end
|
116
|
+
|
117
|
+
def marked_for_destruction?
|
118
|
+
_owner && _owner.marked_for_destruction?
|
119
|
+
end
|
120
|
+
|
121
|
+
def save(options = {})
|
122
|
+
perform_validations(options) ? create_or_update : false
|
123
|
+
end
|
124
|
+
|
125
|
+
# XXX the proper way would have been to introduce an embedded_in macro, which would
|
126
|
+
# even support polymorphic associations, but it's a lot of work, overhead, and I
|
127
|
+
# think that accesing the parent from embedded object should be discouraged as a matter
|
128
|
+
# of principle.
|
129
|
+
def destroy
|
130
|
+
@_destroyed = true
|
131
|
+
_owner.update_attribute(embedded_as, nil) if _owner
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_hstore
|
135
|
+
PgHstore.dump(attributes_after_type_cast, true)
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
attr_accessor :_owner
|
141
|
+
|
142
|
+
def embedded_as
|
143
|
+
self.class.name.demodulize.underscore
|
144
|
+
end
|
145
|
+
|
146
|
+
def perform_validations(options = {})
|
147
|
+
perform_validation = options[:validate] != false
|
148
|
+
perform_validation ? valid?(options[:context]) : true
|
149
|
+
end
|
150
|
+
|
151
|
+
def create_or_update
|
152
|
+
raise OwnerMissingError unless _owner
|
153
|
+
changed_attributes.clear if _owner.update_attribute(embedded_as, self)
|
154
|
+
end
|
155
|
+
|
156
|
+
def attributes_after_type_cast
|
157
|
+
@attributes.dup.tap do |attributes|
|
158
|
+
self.class.serialized_attributes.each_key do |key|
|
159
|
+
if attributes.key?(key)
|
160
|
+
attributes[key] = attributes[key].serialized_value
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
module ClassMethods
|
167
|
+
|
168
|
+
def from_hstore(data)
|
169
|
+
if ActiveRecord::VERSION::MAJOR < 4
|
170
|
+
data = PgHstore.load(data)
|
171
|
+
end
|
172
|
+
new(data, serialized: true)
|
173
|
+
end
|
174
|
+
|
175
|
+
def field(name, options = {})
|
176
|
+
klass = options[:type]
|
177
|
+
|
178
|
+
default = options[:default]
|
179
|
+
_attr_defaults[name.to_s] = default unless default.nil?
|
180
|
+
|
181
|
+
if klass.to_s.start_with?('Hstore::Fields::')
|
182
|
+
serialize(name, klass)
|
183
|
+
end
|
184
|
+
define_accessors(name)
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
def define_accessors(name)
|
189
|
+
define_reader name
|
190
|
+
define_writer name
|
191
|
+
end
|
192
|
+
|
193
|
+
def define_reader(name)
|
194
|
+
define_method(name) do
|
195
|
+
read_attribute(name)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def define_writer(name)
|
200
|
+
define_method("#{name}=".to_sym) do |value|
|
201
|
+
write_attribute(name, value)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
=begin
|
210
|
+
$: << Dir.pwd + '/lib'; true
|
211
|
+
require 'hstore-document'
|
212
|
+
ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: 'hstore_document', username: 'postgres', password: 'postgres')
|
213
|
+
class Address; include Hstore::Document; field :street; end
|
214
|
+
class Person < ActiveRecord::Base; embeds :address; end
|
215
|
+
a = Address.new(bar: true)
|
216
|
+
p = Person.new(address: a)
|
217
|
+
=end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Hstore
|
2
|
+
module Fields # :nodoc:
|
3
|
+
|
4
|
+
class Boolean
|
5
|
+
def self.load(str)
|
6
|
+
case str
|
7
|
+
when 't'
|
8
|
+
true
|
9
|
+
when 'f'
|
10
|
+
false
|
11
|
+
else
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.dump(obj)
|
17
|
+
case obj
|
18
|
+
when true
|
19
|
+
't'
|
20
|
+
when false
|
21
|
+
'f'
|
22
|
+
else
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Fixnum
|
29
|
+
def self.load(str)
|
30
|
+
str.to_i if str.present?
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.dump(obj)
|
34
|
+
obj.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
Integer = Fixnum
|
38
|
+
|
39
|
+
class Array
|
40
|
+
def self.load(str)
|
41
|
+
MultiJson.load(str) if str.present?
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.dump(obj)
|
45
|
+
MultiJson.dump(obj)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Time
|
50
|
+
def self.load(str)
|
51
|
+
Time.parse(str) if str.present?
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.dump(obj)
|
55
|
+
obj.strftime("%Y-%m-%d %H:%M:%S") if obj.present?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Hstore
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
|
4
|
+
initializer 'hstore_document' do
|
5
|
+
ActiveSupport.on_load :active_record do
|
6
|
+
require 'active_record/embeds_reflection'
|
7
|
+
require 'active_record/associations/builder/embeds_one'
|
8
|
+
require 'active_record/associations/embeds_one_association'
|
9
|
+
require 'hstore/document'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Hstore::Document do
|
4
|
+
|
5
|
+
subject { Address.new(street: 'Elm', number: 13) }
|
6
|
+
|
7
|
+
describe "Instantiation" do
|
8
|
+
it "builds" do
|
9
|
+
subject.street.should eq('Elm')
|
10
|
+
subject.number.should eq(13)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has defaults" do
|
14
|
+
address = Address.new
|
15
|
+
address.street.should eq('unknown')
|
16
|
+
address.street = nil
|
17
|
+
address.street.should be_nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "Validations" do
|
22
|
+
subject { StrictAddress.new }
|
23
|
+
|
24
|
+
it "validates" do
|
25
|
+
subject.street = nil
|
26
|
+
subject.should_not be_valid
|
27
|
+
subject.errors[:street].should_not be_empty
|
28
|
+
subject.errors[:number].should_not be_empty
|
29
|
+
|
30
|
+
subject.street = 'Oak'
|
31
|
+
subject.number = 7
|
32
|
+
subject.should be_valid
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "Serialization" do
|
37
|
+
before { subject.business = true }
|
38
|
+
let(:data) { %{"street"=>"Elm","number"=>"13","business"=>"t"} }
|
39
|
+
|
40
|
+
it "serializes to json" do
|
41
|
+
subject.as_json.should eq({
|
42
|
+
'street' => 'Elm',
|
43
|
+
'number' => 13,
|
44
|
+
'business' => true
|
45
|
+
})
|
46
|
+
end
|
47
|
+
|
48
|
+
it "serializes to hstore" do
|
49
|
+
subject.to_hstore.should eq(data)
|
50
|
+
subject.business = false
|
51
|
+
subject.to_hstore.should eq(%{"street"=>"Elm","number"=>"13","business"=>"f"})
|
52
|
+
end
|
53
|
+
|
54
|
+
it "unserializes from hstore" do
|
55
|
+
serialized = if ActiveRecord::VERSION::MAJOR < 4
|
56
|
+
data
|
57
|
+
else
|
58
|
+
{"street"=>"Elm","number"=>"13","business"=>"t"}
|
59
|
+
end
|
60
|
+
obj = Address.from_hstore(serialized)
|
61
|
+
obj.should be_a_kind_of(Address)
|
62
|
+
obj.street.should eq('Elm')
|
63
|
+
obj.business.should eq(true)
|
64
|
+
obj.number.should eq(13)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "State change tracking" do
|
70
|
+
subject { Address.new }
|
71
|
+
|
72
|
+
it { should_not be_changed }
|
73
|
+
|
74
|
+
it "is changed when attribute is changed" do
|
75
|
+
subject.street = 'Elm'
|
76
|
+
subject.should be_changed
|
77
|
+
end
|
78
|
+
|
79
|
+
it "destroys" do
|
80
|
+
expect { subject.destroy }.to change { subject.destroyed? }.to(true)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "raises error when save is attempted" do
|
85
|
+
expect { subject.save }.to raise_error(Hstore::Document::OwnerMissingError)
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Embedded" do
|
4
|
+
|
5
|
+
subject { Person.new }
|
6
|
+
let(:address) { Address.new(street: 'Elm', number: 13) }
|
7
|
+
|
8
|
+
let(:valid_attributes) do
|
9
|
+
{street: 'Birch', number: 1, business: false}
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "Assignment" do
|
13
|
+
it "assigns" do
|
14
|
+
subject.address = address
|
15
|
+
subject.address.should eq(address)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "Persistence" do
|
20
|
+
subject { Person.create!(address: address) }
|
21
|
+
|
22
|
+
it "saves" do
|
23
|
+
subject.reload.address.should eq(address)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "embedded" do
|
28
|
+
subject { Person.create!(address: address) }
|
29
|
+
|
30
|
+
it "tracks persisted state" do
|
31
|
+
subject.reload.address.should be_persisted
|
32
|
+
end
|
33
|
+
|
34
|
+
it "tracks destroyed state" do
|
35
|
+
subject.destroy
|
36
|
+
subject.address.should be_destroyed
|
37
|
+
end
|
38
|
+
|
39
|
+
it "destroys" do
|
40
|
+
subject.address.destroy
|
41
|
+
subject.should_not be_destroyed
|
42
|
+
subject.reload.address.should be_nil
|
43
|
+
end
|
44
|
+
|
45
|
+
it "updates" do
|
46
|
+
subject.address.number = 17
|
47
|
+
subject.address.save
|
48
|
+
subject.reload.address.number.should eq(17)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "creates" do
|
52
|
+
person = Person.new
|
53
|
+
address = Address.new(valid_attributes)
|
54
|
+
person.address = address
|
55
|
+
person.address.save
|
56
|
+
person.reload.address.should eq(address)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "Nested attributes" do
|
62
|
+
subject { Person.new(address_attributes: valid_attributes) }
|
63
|
+
|
64
|
+
it "assigns" do
|
65
|
+
address = subject.address
|
66
|
+
address.street.should eq('Birch')
|
67
|
+
address.number.should eq(1)
|
68
|
+
address.business.should eq(false)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "saves" do
|
72
|
+
subject.save!
|
73
|
+
subject.reload
|
74
|
+
address = subject.address
|
75
|
+
address.street.should eq('Birch')
|
76
|
+
address.number.should eq(1)
|
77
|
+
address.business.should eq(false)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "Building" do
|
82
|
+
|
83
|
+
it "builds" do
|
84
|
+
address = subject.build_address(valid_attributes)
|
85
|
+
address.should be_a_kind_of(Address)
|
86
|
+
address.number.should eq(1)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "creates" do
|
90
|
+
address = subject.create_address(valid_attributes)
|
91
|
+
address.should be_a_kind_of(Address)
|
92
|
+
address.number.should eq(1)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "creates!" do
|
96
|
+
address = subject.create_address!(valid_attributes)
|
97
|
+
address.should be_a_kind_of(Address)
|
98
|
+
address.number.should eq(1)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "Validation" do
|
103
|
+
before { subject.address = Address.new }
|
104
|
+
|
105
|
+
context "Invalid" do
|
106
|
+
it "validates associated" do
|
107
|
+
subject.address.should_not be_valid
|
108
|
+
subject.should_not be_valid
|
109
|
+
subject.errors['address'].should_not be_empty
|
110
|
+
end
|
111
|
+
|
112
|
+
it "doesn't save parent when validation fails" do
|
113
|
+
subject.save.should be_false
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'active_record'
|
5
|
+
require 'hstore-document'
|
6
|
+
require 'pry'
|
7
|
+
|
8
|
+
# Requires supporting files with custom matchers and macros, etc,
|
9
|
+
# in ./support/ and its subdirectories.
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.before(:suite) do
|
14
|
+
ActiveRecord::Base.establish_connection({
|
15
|
+
adapter: 'postgresql',
|
16
|
+
encoding: 'unicode',
|
17
|
+
database: 'hstore_document',
|
18
|
+
username: 'postgres',
|
19
|
+
password: 'postgres'
|
20
|
+
})
|
21
|
+
# ActiveRecord::Base.logger = Logger.new(STDERR)
|
22
|
+
end
|
23
|
+
|
24
|
+
config.after(:suite) do
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Address
|
2
|
+
include Hstore::Document
|
3
|
+
|
4
|
+
field :street, type: String, default: 'unknown'
|
5
|
+
field :number, type: Fixnum
|
6
|
+
field :business, type: Boolean
|
7
|
+
|
8
|
+
validates_presence_of :number
|
9
|
+
end
|
10
|
+
|
11
|
+
class StrictAddress < Address
|
12
|
+
validates_presence_of :street
|
13
|
+
validates_numericality_of :number
|
14
|
+
end
|
15
|
+
|
16
|
+
class Person < ActiveRecord::Base
|
17
|
+
embeds :address
|
18
|
+
|
19
|
+
accepts_nested_attributes_for :address
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hstore-document
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roman Shterenzon
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pg
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg-hstore
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "<"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.1'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '3.2'
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "<"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '4.1'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.2'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.14'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2.14'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rdoc
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.12'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.12'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: bundler
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '1.0'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: jeweler
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.8.7
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 1.8.7
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: pry
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: pry-debugger
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: guard
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: guard-rspec
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
description: Allows embedding documents in ActiveRecord models using PostgreSQL HStore
|
174
|
+
email: roman.shterenzon@gmail.com
|
175
|
+
executables: []
|
176
|
+
extensions: []
|
177
|
+
extra_rdoc_files:
|
178
|
+
- LICENSE.txt
|
179
|
+
- README.md
|
180
|
+
files:
|
181
|
+
- ".document"
|
182
|
+
- ".rspec"
|
183
|
+
- Gemfile
|
184
|
+
- Gemfile.lock
|
185
|
+
- Guardfile
|
186
|
+
- LICENSE.txt
|
187
|
+
- README.md
|
188
|
+
- Rakefile
|
189
|
+
- VERSION
|
190
|
+
- hstore-document.gemspec
|
191
|
+
- lib/active_record/associations/builder/embeds_one.rb
|
192
|
+
- lib/active_record/associations/embeds_one_association.rb
|
193
|
+
- lib/active_record/embeds_reflection.rb
|
194
|
+
- lib/hstore-document.rb
|
195
|
+
- lib/hstore/document.rb
|
196
|
+
- lib/hstore/fields.rb
|
197
|
+
- lib/hstore/railtie.rb
|
198
|
+
- spec/document_spec.rb
|
199
|
+
- spec/integration_spec.rb
|
200
|
+
- spec/spec_helper.rb
|
201
|
+
- spec/support/models.rb
|
202
|
+
homepage: http://github.com/romanbsd/hstore-document
|
203
|
+
licenses:
|
204
|
+
- MIT
|
205
|
+
metadata: {}
|
206
|
+
post_install_message:
|
207
|
+
rdoc_options: []
|
208
|
+
require_paths:
|
209
|
+
- lib
|
210
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - ">="
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: '0'
|
215
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
216
|
+
requirements:
|
217
|
+
- - ">="
|
218
|
+
- !ruby/object:Gem::Version
|
219
|
+
version: '0'
|
220
|
+
requirements: []
|
221
|
+
rubyforge_project:
|
222
|
+
rubygems_version: 2.2.2
|
223
|
+
signing_key:
|
224
|
+
specification_version: 4
|
225
|
+
summary: Embedded documents using HStore
|
226
|
+
test_files: []
|