perpetuity 0.7.3 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile +3 -0
  4. data/README.md +18 -8
  5. data/lib/perpetuity.rb +4 -4
  6. data/lib/perpetuity/attribute.rb +9 -0
  7. data/lib/perpetuity/attribute_set.rb +4 -6
  8. data/lib/perpetuity/data_injectable.rb +1 -1
  9. data/lib/perpetuity/duplicator.rb +27 -0
  10. data/lib/perpetuity/mapper.rb +9 -14
  11. data/lib/perpetuity/version.rb +1 -1
  12. data/perpetuity.gemspec +0 -1
  13. data/spec/integration/associations_spec.rb +2 -6
  14. data/spec/integration/indexing_spec.rb +1 -1
  15. data/spec/integration/persistence_spec.rb +3 -2
  16. data/spec/integration/retrieval_spec.rb +5 -13
  17. data/spec/perpetuity/attribute_set_spec.rb +1 -1
  18. data/spec/perpetuity/attribute_spec.rb +6 -1
  19. data/spec/perpetuity/duplicator_spec.rb +35 -0
  20. data/spec/perpetuity/mapper_spec.rb +21 -2
  21. data/spec/perpetuity_spec.rb +2 -2
  22. data/spec/spec_helper.rb +8 -2
  23. data/spec/support/test_classes.rb +22 -26
  24. data/spec/support/test_classes/topic.rb +5 -0
  25. data/spec/support/test_classes/user.rb +1 -1
  26. metadata +7 -55
  27. data/lib/perpetuity/mongodb.rb +0 -230
  28. data/lib/perpetuity/mongodb/index.rb +0 -52
  29. data/lib/perpetuity/mongodb/nil_query.rb +0 -11
  30. data/lib/perpetuity/mongodb/query.rb +0 -33
  31. data/lib/perpetuity/mongodb/query_attribute.rb +0 -66
  32. data/lib/perpetuity/mongodb/query_expression.rb +0 -94
  33. data/lib/perpetuity/mongodb/query_intersection.rb +0 -16
  34. data/lib/perpetuity/mongodb/query_union.rb +0 -16
  35. data/lib/perpetuity/mongodb/serializer.rb +0 -174
  36. data/lib/perpetuity/validations.rb +0 -1
  37. data/lib/perpetuity/validations/length.rb +0 -36
  38. data/lib/perpetuity/validations/presence.rb +0 -14
  39. data/lib/perpetuity/validations/validation_set.rb +0 -28
  40. data/spec/integration/mongodb_spec.rb +0 -218
  41. data/spec/integration/validations_spec.rb +0 -17
  42. data/spec/perpetuity/mongodb/index_spec.rb +0 -44
  43. data/spec/perpetuity/mongodb/query_attribute_spec.rb +0 -58
  44. data/spec/perpetuity/mongodb/query_expression_spec.rb +0 -67
  45. data/spec/perpetuity/mongodb/query_intersection_spec.rb +0 -16
  46. data/spec/perpetuity/mongodb/query_spec.rb +0 -79
  47. data/spec/perpetuity/mongodb/query_union_spec.rb +0 -16
  48. data/spec/perpetuity/mongodb/serializer_spec.rb +0 -212
  49. data/spec/perpetuity/validations/length_spec.rb +0 -53
  50. data/spec/perpetuity/validations/presence_spec.rb +0 -30
  51. data/spec/perpetuity/validations_spec.rb +0 -87
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2244d23d7746c76519f66795e7799800d7ed84f5
4
- data.tar.gz: f3aa364123d96e1477e64eeda2676e616a712515
3
+ metadata.gz: 7e6705adfb6aafad45d73afe6fd8fc8da9ecb7d0
4
+ data.tar.gz: c7bb3bc720c641e4b5951188591279b4fda33baa
5
5
  SHA512:
6
- metadata.gz: 4baa0facacc3099d0f8c8e36e0fb283ee1dd319067a6249ef2d6ffbdad6afddca95d70779bd8b140b713b6717778182afcfccb26661113e22401aa2cf18aed62
7
- data.tar.gz: fea3b1c06ef2b0f90738d53d9f1783024527582f5c49bd4869233a71882e248623c7b5980b7679701d6e1c1f61f0bb78886807fd861570017e2710f21a4916df
6
+ metadata.gz: 94d4440cfb7076313402de2f8055d81fc7151504dacc1bc3e7004549ab60605f04f3cf6342dcd56893dd85e4714d39327ffe240b31fd51ebe9cb2342261c4941
7
+ data.tar.gz: 8d868e8b3ba8a5fdc63b9a8ac2e32ff24bcac8b09275cadff37262be8e7ef1e39586b7c067e50627247131249a3692f467e635855332cf1350a617192d03bbf6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## Version 1.0.0.beta
2
+
3
+ - Duplicate objects properly for the identity map. The identity map holds references to objects pulled out of the DB. We need to duplicate these references on insertion and retrieval from the identity map to make sure that modifications to an object do not pollute the version in the identity map.
4
+ - Add support for the [Amazon DynamoDB adapter](https://github.com/cardspring/perpetuity-dynamodb) by [Cardspring](https://github.com/cardspring). This adapter is still in very early development.
5
+ - Allow passing in the type of the `id` attribute for databases that enforce column/field types. This allows us to say `id(String) { ... }`, telling the database that `id` needs to be a String.
6
+ - Use hashes for AttributeSets to reduce serialization time.
7
+ - Extract all database-specific logic to their own gems. The existing MongoDB adapter has been moved to the `perpetuity-mongodb` gem and there is a PostgreSQL adapter that lives under `perpetuity-postgres`.
8
+ - Allow specs to be run on top of `perpetuity-postgres`
9
+ - Remove validations.
10
+
1
11
  ## Version 0.7.3
2
12
 
3
13
  - Only save attributes which have changed
data/Gemfile CHANGED
@@ -1,3 +1,6 @@
1
1
  source "http://rubygems.org"
2
2
 
3
+ gem "perpetuity-postgres", github: "jgaskins/perpetuity-postgres"
4
+ gem "perpetuity-mongodb", github: "jgaskins/perpetuity-mongodb"
5
+
3
6
  gemspec
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Perpetuity is a simple Ruby object persistence layer that attempts to follow Martin Fowler's Data Mapper pattern, allowing you to use plain-old Ruby objects in your Ruby apps in order to decouple your domain logic from the database as well as speed up your tests. There is no need for your model classes to inherit from another class or even include a mix-in.
4
4
 
5
- Your objects will hopefully eventually be able to be persisted into whichever database you like. Right now, only MongoDB is supported. Other persistence solutions will come later.
5
+ Your objects will hopefully eventually be able to be persisted into whichever database you like. Right now, only MongoDB is supported. There is also a [PostgreSQL adapter](https://github.com/jgaskins/perpetuity-postgres) under heavy development (nearly up-to-date with the [MongoDB adapter](https://github.com/jgaskins/perpetuity-mongodb)). Other persistence solutions will come later.
6
6
 
7
7
  ## How it works
8
8
 
@@ -16,15 +16,25 @@ Add the following to your Gemfile and run `bundle` to install it.
16
16
  gem 'perpetuity'
17
17
  ```
18
18
 
19
+ **NOTE:** In version 1.0, you will need to install the Perpetuity database adapter, which will pull in the core Perpetuity gem:
20
+
21
+ ```ruby
22
+ gem 'perpetuity/mongodb' # if using MongoDB
23
+ gem 'perpetuity/postgres' # if using Postgres
24
+ ```
25
+
19
26
  ## Configuration
20
27
 
21
- The only currently supported persistence method is MongoDB. Other schemaless solutions can probably be implemented easily. The simplest configuration is with the following line:
28
+ The only currently supported persistence method is MongoDB, but stay tuned for the [Postgres adapter](https://github.com/jgaskins/perpetuity-postgres). Other schemaless solutions can probably be implemented easily. The simplest configuration is with the following line:
22
29
 
23
30
  ```ruby
24
- Perpetuity.data_source :mongodb, 'my_database'
31
+ Perpetuity.data_source :mongodb, 'my_mongo_database'
32
+ Perpetuity.data_source :postgres, 'my_pg_database'
25
33
  ```
26
34
 
27
- If your database is on another server or you need authentication, you can specify those as options:
35
+ *Note:* You cannot use different databases in the same app like that. At least, not yet. :-)
36
+
37
+ If your database is on another server/port or you need authentication, you can specify those as options:
28
38
 
29
39
  ```ruby
30
40
  Perpetuity.data_source :mongodb, 'my_database', host: 'mongo.example.com',
@@ -129,7 +139,7 @@ Perpetuity.generate_mapper_for Comment do
129
139
  end
130
140
  ```
131
141
 
132
- In this case, the article has an array of `Comment` objects, which the serializer knows that MongoDB cannot serialize. It will then tell the `Comment` mapper to serialize it and it stores that within the array.
142
+ In this case, the article has an array of `Comment` objects, which the serializer knows that the data source cannot serialize. It will then tell the `Comment` mapper to serialize it and it stores that within the array.
133
143
 
134
144
  If some of the comments aren't objects of class `Comment`, it will adapt and serialize them according to their class. This works very well for objects that can have attributes of various types, such as a `User` having a profile attribute that can be either a `UserProfile` or `AdminProfile` object. You don't need to declare anything different for this case, just store the appropriate type of object into the `User`'s `profile` attribute and the mapper will take care of the details.
135
145
 
@@ -187,7 +197,7 @@ Perpetuity.generate_mapper_for Article do
187
197
  end
188
198
  ```
189
199
 
190
- Also, MongoDB, as well as some other databases, provide the ability to specify an order for the index. For example, if you want to query your blog with articles in descending order, you can specify a descending-order index on the timestamp for increased query performance.
200
+ Also, some databases provide the ability to specify an order for the index. For example, if you want to query your blog with articles in descending order, you can specify a descending-order index on the timestamp for increased query performance.
191
201
 
192
202
  ```ruby
193
203
  Perpetuity.generate_mapper_for Article do
@@ -197,7 +207,7 @@ end
197
207
 
198
208
  ### Applying indexes
199
209
 
200
- It's very important to keep in mind that specifying an index does not create it on the database immediately. If you did this, you could potentially introduce downtime every time you specify a new index and deploy your application.
210
+ It's very important to keep in mind that specifying an index does not create it on the database immediately. If you did this, you could potentially introduce downtime every time you specify a new index and deploy your application. Additionally, if a unique index fails to apply, you would not be able to start your app.
201
211
 
202
212
  In order to apply indexes to the database, you must send `reindex!` to the mapper. For example:
203
213
 
@@ -221,7 +231,7 @@ Let's face it, most Ruby apps run on Rails, so we need to be able to support it.
221
231
 
222
232
  Previous versions of Perpetuity would break when Rails reloaded your models in development mode due to class objects being different. It now reloads mappers dynamically based on whether the class has been reloaded.
223
233
 
224
- In order for this to work, your mapper files need to be named `*_mapper.rb` and be stored anywhere inside your project's `app` directory.
234
+ In order for this to work, your mapper files need to be named `*_mapper.rb` and be stored anywhere inside your project's `app` directory. Usually, this would be `app/mappers`, but this is not enforced.
225
235
 
226
236
  ### ActiveModel-compliant API
227
237
 
data/lib/perpetuity.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require "perpetuity/version"
2
- require "perpetuity/mongodb"
3
2
  require "perpetuity/config"
4
3
  require "perpetuity/mapper"
5
4
  require "perpetuity/mapper_registry"
@@ -9,7 +8,7 @@ module Perpetuity
9
8
  detect_rails
10
9
  configuration.instance_exec(&block)
11
10
  end
12
-
11
+
13
12
  def self.configuration
14
13
  @configuration ||= Configuration.new
15
14
  end
@@ -29,9 +28,10 @@ module Perpetuity
29
28
  end
30
29
 
31
30
  def self.data_source adapter, db_name, options={}
32
- adapters = { mongodb: MongoDB }
31
+ adapters = { dynamodb: 'DynamoDB', mongodb: 'MongoDB', postgres: 'Postgres' }
32
+ adapter_class = const_get(adapters[adapter])
33
33
 
34
- configure { data_source adapters[adapter].new(options.merge(db: db_name)) }
34
+ configure { data_source adapter_class.new(options.merge(db: db_name)) }
35
35
  end
36
36
 
37
37
  # Necessary to be able to check whether Rails is loaded and initialized
@@ -5,11 +5,20 @@ module Perpetuity
5
5
  @name = name
6
6
  @type = type
7
7
 
8
+ @_options = options.dup
8
9
  options.each do |option, value|
9
10
  instance_variable_set "@#{option}", value
10
11
  end
11
12
  end
12
13
 
14
+ def options option=nil
15
+ if option
16
+ instance_variable_get("@#{option}")
17
+ else
18
+ @_options
19
+ end
20
+ end
21
+
13
22
  def embedded?
14
23
  @embedded ||= false
15
24
  end
@@ -1,23 +1,21 @@
1
- require 'set'
2
-
3
1
  module Perpetuity
4
2
  class AttributeSet
5
3
  include Enumerable
6
4
 
7
5
  def initialize
8
- @attributes = Set.new
6
+ @attributes = {}
9
7
  end
10
8
 
11
9
  def << attribute
12
- @attributes << attribute
10
+ @attributes[attribute.name] = attribute
13
11
  end
14
12
 
15
13
  def [] name
16
- @attributes.find { |attr| attr.name == name }
14
+ @attributes[name]
17
15
  end
18
16
 
19
17
  def each &block
20
- @attributes.each(&block)
18
+ @attributes.each_value(&block)
21
19
  end
22
20
  end
23
21
  end
@@ -5,7 +5,7 @@ module Perpetuity
5
5
  end
6
6
 
7
7
  def inject_data object, data
8
- data.each_pair do |attribute,value|
8
+ data.each do |attribute,value|
9
9
  inject_attribute object, attribute, value
10
10
  end
11
11
  give_id_to object if object.instance_variables.include?(:@id)
@@ -0,0 +1,27 @@
1
+ module Perpetuity
2
+ class Duplicator
3
+ attr_reader :object
4
+ def initialize object
5
+ if object.is_a? Array
6
+ @object = object.map { |i| Duplicator.new(i).object }
7
+ else
8
+ @object = object.dup rescue object
9
+ end
10
+ @object.instance_variables.each do |ivar|
11
+ duplicate_attribute ivar
12
+ end
13
+ end
14
+
15
+ def attribute ivar
16
+ object.instance_variable_get ivar
17
+ end
18
+
19
+ def set_attribute ivar, value
20
+ object.instance_variable_set ivar, value
21
+ end
22
+
23
+ def duplicate_attribute ivar
24
+ set_attribute ivar, Duplicator.new(attribute(ivar)).object
25
+ end
26
+ end
27
+ end
@@ -1,9 +1,9 @@
1
1
  require 'perpetuity/attribute_set'
2
2
  require 'perpetuity/attribute'
3
- require 'perpetuity/validations'
4
3
  require 'perpetuity/data_injectable'
5
4
  require 'perpetuity/dereferencer'
6
5
  require 'perpetuity/retrieval'
6
+ require 'perpetuity/duplicator'
7
7
 
8
8
  module Perpetuity
9
9
  class Mapper
@@ -71,11 +71,10 @@ module Perpetuity
71
71
  end
72
72
 
73
73
  def insert object
74
- raise "#{object} is invalid and cannot be persisted." unless self.class.validations.valid?(object)
75
74
  objects = Array(object)
76
75
  serialized_objects = objects.map { |obj| serialize(obj) }
77
76
 
78
- new_ids = data_source.insert(mapped_class, serialized_objects)
77
+ new_ids = data_source.insert(mapped_class, serialized_objects, attribute_set)
79
78
  objects.each_with_index do |obj, index|
80
79
  give_id_to obj, new_ids[index]
81
80
  end
@@ -139,7 +138,7 @@ module Perpetuity
139
138
  result = select { |object| object.id == id }.first
140
139
 
141
140
  if cache_result and !result.nil?
142
- identity_map << result
141
+ identity_map << Duplicator.new(result).object
143
142
  end
144
143
 
145
144
  result
@@ -174,9 +173,13 @@ module Perpetuity
174
173
  end
175
174
  end
176
175
 
177
- def self.id &block
176
+ def self.id type=nil, &block
178
177
  if block_given?
179
178
  @id = block
179
+ if type
180
+ attribute :id, type: type
181
+ end
182
+ nil
180
183
  else
181
184
  @id ||= -> { nil }
182
185
  end
@@ -222,14 +225,6 @@ module Perpetuity
222
225
  object.instance_variable_get(:@id) if persisted?(object)
223
226
  end
224
227
 
225
- def self.validate &block
226
- validations.instance_exec(&block)
227
- end
228
-
229
- def self.validations
230
- @validations ||= ValidationSet.new
231
- end
232
-
233
228
  def data_source
234
229
  self.class.data_source
235
230
  end
@@ -237,7 +232,7 @@ module Perpetuity
237
232
  def serialize object
238
233
  attributes = data_source.serialize(object, self)
239
234
  if o_id = generate_id_for(object)
240
- attributes[:id] = o_id
235
+ attributes['id'] = o_id
241
236
  end
242
237
 
243
238
  attributes
@@ -1,3 +1,3 @@
1
1
  module Perpetuity
2
- VERSION = "0.7.3"
2
+ VERSION = "1.0.0.beta"
3
3
  end
data/perpetuity.gemspec CHANGED
@@ -20,5 +20,4 @@ Gem::Specification.new do |s|
20
20
  # specify any dependencies here; for example:
21
21
  s.add_development_dependency "rake"
22
22
  s.add_development_dependency "rspec", "~> 2.13"
23
- s.add_runtime_dependency "moped"
24
23
  end
@@ -2,16 +2,12 @@ require 'spec_helper'
2
2
  require 'support/test_classes'
3
3
 
4
4
  describe 'associations with other objects' do
5
- let(:user) { User.new }
6
- let(:topic) { Topic.new }
5
+ let(:user) { User.new('Flump') }
6
+ let(:topic) { Topic.new('Title', user) }
7
7
  let(:user_mapper) { Perpetuity[User] }
8
8
  let(:topic_mapper) { Perpetuity[Topic] }
9
9
 
10
10
  before do
11
- user.name = 'Flump'
12
- topic.creator = user
13
- topic.title = 'Title'
14
-
15
11
  user_mapper.insert user
16
12
  topic_mapper.insert topic
17
13
  end
@@ -24,7 +24,7 @@ describe 'indexing' do
24
24
  let(:db_name) { Perpetuity.configuration.data_source.db }
25
25
 
26
26
  before do
27
- Perpetuity.data_source :mongodb, db_name
27
+ load './spec/spec_helper.rb'
28
28
  mapper.data_source.drop_collection Object
29
29
  end
30
30
  after { mapper.data_source.drop_collection Object }
@@ -1,13 +1,14 @@
1
1
  require 'spec_helper'
2
2
  require 'support/test_classes'
3
+ require 'securerandom'
3
4
 
4
5
  describe 'Persistence' do
5
6
  let(:mapper) { Perpetuity[Article] }
6
7
 
7
8
  it "persists an object" do
8
9
  article = Article.new 'I have a title'
9
- expect { mapper.insert article }.
10
- to change { mapper.count }.by 1
10
+ mapper.serialize article
11
+ expect { mapper.insert article }.to change { mapper.count }.by 1
11
12
  mapper.find(mapper.id_for(article)).title.should eq 'I have a title'
12
13
  end
13
14
 
@@ -195,22 +195,14 @@ describe "retrieval" do
195
195
  end
196
196
  end
197
197
 
198
- it 'selects objects with nested data' do
199
- user = User.new(first_name: 'foo', last_name: 'bar')
200
- mapper = Perpetuity[User]
201
- mapper.insert user
202
- users = mapper.select { |u| u.name.first_name == 'foo' }
203
- ids = users.map { |retrieved_user| mapper.id_for(retrieved_user) }
204
- ids.should include mapper.id_for(user)
205
- end
206
-
207
198
  it 'skips a specified number of objects' do
208
- author = SecureRandom.hex
209
- articles = 3.times.map { Article.new(SecureRandom.hex, nil, author) }.sort_by(&:title)
199
+ title = SecureRandom.hex
200
+ articles = 3.times.map { Article.new(title) }.sort_by { |article| mapper.id_for(article) }
210
201
  articles.each { |article| mapper.insert article }
211
202
 
212
- ret = mapper.select { |article| article.author == author }.drop(2).sort(:title).first
213
- ret.should == articles.last
203
+ ret = mapper.select { |article| article.title == title }.drop(2).sort(:id).to_a
204
+ ret.should have(1).items
205
+ ret.first.should == articles.last
214
206
  end
215
207
 
216
208
  describe 'selecting random objects' do
@@ -3,7 +3,7 @@ require 'perpetuity/attribute_set'
3
3
  module Perpetuity
4
4
  describe AttributeSet do
5
5
  it 'contains attributes' do
6
- attribute = double('Attribute')
6
+ attribute = double('Attribute', name: :foo)
7
7
  subject << attribute
8
8
 
9
9
  subject.first.should eq attribute
@@ -2,7 +2,7 @@ require 'perpetuity/attribute'
2
2
 
3
3
  module Perpetuity
4
4
  describe Attribute do
5
- let(:attribute) { Attribute.new :article, Object }
5
+ let(:attribute) { Attribute.new :article, Object, default: 1 }
6
6
  subject { attribute }
7
7
 
8
8
  it 'has a name' do
@@ -13,6 +13,11 @@ module Perpetuity
13
13
  subject.type.should == Object
14
14
  end
15
15
 
16
+ it 'can get extra options' do
17
+ attribute.options.should == { default: 1 }
18
+ attribute.options(:default).should == 1
19
+ end
20
+
16
21
  it 'can be embedded' do
17
22
  attribute = Attribute.new :article, Object, embedded: true
18
23
  attribute.should be_embedded
@@ -0,0 +1,35 @@
1
+ require 'perpetuity/duplicator'
2
+ require 'support/test_classes/book'
3
+ require 'support/test_classes/user'
4
+
5
+ module Perpetuity
6
+ describe Duplicator do
7
+ let(:authors) { [User.new('Dave'), User.new('Andy')] }
8
+ let(:book) { Book.new('Title', authors) }
9
+ let(:duper) { Duplicator.new(book) }
10
+
11
+ it 'duplicates an object' do
12
+ duper.object.should be_a Book
13
+ duper.object.should_not be book
14
+ end
15
+
16
+ it 'duplicates attributes inside an object' do
17
+ duped_book = duper.object
18
+ duped_book.title.should be_a String
19
+ duped_book.title.should == book.title
20
+ duped_book.title.should_not be book.title
21
+ end
22
+
23
+ it 'does not duplicate non-duplicable attributes' do
24
+ # Symbols cannot be duped
25
+ book = Book.new(:foo)
26
+ duper = Duplicator.new(book)
27
+ duper.object.title.should be :foo
28
+ end
29
+
30
+ it 'duplicates objects contained within array attributes' do
31
+ duper.object.authors.first.should be_a User
32
+ duper.object.authors.first.should_not be authors.first
33
+ end
34
+ end
35
+ end