praxis-mapper 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +4 -0
  5. data/CHANGELOG.md +83 -0
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +102 -0
  8. data/Guardfile +11 -0
  9. data/LICENSE +22 -0
  10. data/README.md +19 -0
  11. data/Rakefile +14 -0
  12. data/lib/praxis-mapper/config_hash.rb +40 -0
  13. data/lib/praxis-mapper/connection_manager.rb +102 -0
  14. data/lib/praxis-mapper/finalizable.rb +38 -0
  15. data/lib/praxis-mapper/identity_map.rb +532 -0
  16. data/lib/praxis-mapper/logging.rb +22 -0
  17. data/lib/praxis-mapper/model.rb +430 -0
  18. data/lib/praxis-mapper/query/base.rb +213 -0
  19. data/lib/praxis-mapper/query/sql.rb +183 -0
  20. data/lib/praxis-mapper/query_statistics.rb +46 -0
  21. data/lib/praxis-mapper/resource.rb +226 -0
  22. data/lib/praxis-mapper/support/factory_girl.rb +104 -0
  23. data/lib/praxis-mapper/support/memory_query.rb +34 -0
  24. data/lib/praxis-mapper/support/memory_repository.rb +44 -0
  25. data/lib/praxis-mapper/support/schema_dumper.rb +66 -0
  26. data/lib/praxis-mapper/support/schema_loader.rb +56 -0
  27. data/lib/praxis-mapper/support.rb +2 -0
  28. data/lib/praxis-mapper/version.rb +5 -0
  29. data/lib/praxis-mapper.rb +60 -0
  30. data/praxis-mapper.gemspec +38 -0
  31. data/spec/praxis-mapper/connection_manager_spec.rb +117 -0
  32. data/spec/praxis-mapper/identity_map_spec.rb +905 -0
  33. data/spec/praxis-mapper/logging_spec.rb +9 -0
  34. data/spec/praxis-mapper/memory_repository_spec.rb +56 -0
  35. data/spec/praxis-mapper/model_spec.rb +389 -0
  36. data/spec/praxis-mapper/query/base_spec.rb +317 -0
  37. data/spec/praxis-mapper/query/sql_spec.rb +184 -0
  38. data/spec/praxis-mapper/resource_spec.rb +154 -0
  39. data/spec/praxis_mapper_spec.rb +21 -0
  40. data/spec/spec_fixtures.rb +12 -0
  41. data/spec/spec_helper.rb +63 -0
  42. data/spec/support/spec_models.rb +215 -0
  43. data/spec/support/spec_resources.rb +39 -0
  44. metadata +298 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7bb0361a7b29be6d0c9d393bf149369d22cfacd1
4
+ data.tar.gz: f86867db4ccfdd60992aab8cc66cf8959362c7ec
5
+ SHA512:
6
+ metadata.gz: 13f27e05d0c46b6ed3b906af2fbee4e05425530354ee873eba1097beeacb4df74955b9aa05e064f1ce1165fdda9f72b74beb4c5759454c4493d086c9ffea98d6
7
+ data.tar.gz: 697c2552d6c1b898df1d77e61e164f844d89361f66a5647ccac2005e745e7ccf7bc7d06acfaf066700d00e19af1f771ed8512f79672e786d0c90fdb9b023dbcb
data/.gitignore ADDED
@@ -0,0 +1,26 @@
1
+ *.swp
2
+ *.swo
3
+
4
+ # YARD
5
+ .yardoc
6
+
7
+ # Gemfile
8
+ *.gem
9
+
10
+ # Code coverage
11
+ coverage
12
+
13
+ # rbenv
14
+ .ruby-version
15
+
16
+ # Bundler
17
+ .bundle
18
+
19
+ # Mac OSX files
20
+ .DS_Store
21
+
22
+ bin
23
+
24
+ .idea
25
+
26
+ tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format=Fuubar
3
+ --backtrace
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.1.2"
4
+ script: bundle exec rspec spec
data/CHANGELOG.md ADDED
@@ -0,0 +1,83 @@
1
+ # praxis-mapper changelog
2
+
3
+ ## next
4
+
5
+ * Next feature description here
6
+
7
+ ## 3.1
8
+
9
+ * Begin migration to Sequel for `Query::Sql`.
10
+ * `#_execute` uses it for a database-agnostic adapter for running raw SQL.
11
+ * `#_multi_get` uses it generate a datbase-agnostic where clause
12
+ * Added accessor generation for `one_to_many` associations in resources.
13
+ * Imported `Model` extensions for FactoryGirl to `praxis-mapper/support/factory_girl`.
14
+ * This also adds `IdentityMap#persist!` to insert all rows in the identity map into the database.
15
+ * Added `SchemaDumper` and `SchemaLoader` for dumping and loading schema information in `praxis-mapper/support/schema_dumper` and `praxis-mapper/support/schema_loader` respectively.
16
+ * `IdentityMap#all` now accepts non-identity keys as conditions, and will create a secondary index for the key the first time it's queried.
17
+ * Added `Model#identities` to get a hash of identities and values for a record.
18
+ * Auto-generated attribute accessors on `Blueprint` do not load (coerce) the value using the attribute. Ensure values passed in already have the appropriate types. Blueprint attributes will still be wrapped properly, however.
19
+ * Performance and memory use optimizations.
20
+ * `IdentityMap#load` now supports eagerly-loading associated records in a query, and supports the full set of options on the inner query, including
21
+ * Tracked `:one_to_many` associations now support where clauses. Using `where` clauses when tracking other association types is not supported and will raise an exception.
22
+
23
+
24
+ ## 3.0
25
+
26
+ * Moved Blueprint inner attribute creation to finalize!
27
+ * Added :dsl_compiler and :identity as valid options for Blueprint.
28
+ * `Praxis::Mapper::Model`
29
+ * `Model.context` allows named query parameters to be reused across queries. See documentation for details.
30
+ * `Model#_query` references original query that loaded the record.
31
+ * `Model.serialized_fields` returns hash of fields defined with either `json` or `yaml` directives as serialized fields.
32
+ * Fixed accessors will to raise `KeyError` if the record does not the field (i.e., if it was not loaded from the database), rather than silently returning `nil`.
33
+ * `Praxis::Mapper::Query`:
34
+ * Multiple `select` and `track` calls within one query are now additive.
35
+ * `track` option now takes a block that will be applied to the query used to load the tracked records.
36
+ * `context` directive to apply the named context from the model to the query. See documentation for more.
37
+ * `Praxis::Mapper::IdentityMap`
38
+ * Removed `:track` option from `#add_records` in favor of using `Model#_query` to determine tracked associations.
39
+ * Added `Praxis::Mapper::Support` with `MemoryQuery` and `MemoryRepository` for use in testing data loading without requiring a database.
40
+
41
+
42
+ ### Notices
43
+
44
+ The `Model` accessor changes may break existing applications that (incorrectly) expect unloaded attributes to return `nil`.
45
+
46
+
47
+ ## 2.1.0
48
+
49
+ * Blueprint.validate now only accepts objects of the its type.
50
+ * Improved handling of Blueprints with circular relations.
51
+
52
+
53
+ ## 2.0.0
54
+
55
+ * First pass at reworking model associations.
56
+ * Split `belongs_to` into `many_to_one` and `array_to_many` associations
57
+ * Added `one_to_many` and `many_to_array` associations (the inverse of the above associations, aka: has_many)
58
+ * Added association configuration DSL to replace previous hash-based configuration.
59
+
60
+
61
+ ## 0.3.1
62
+
63
+ * Added support for code coverage
64
+ * Added support for 'guard' gem (use 'bundle exec guard')
65
+ * Fixed bug in Praxis::Mapper::Model.undefine\_data\_accessors when method doesn't exist.
66
+ * Safer version checking with Gem::Version class
67
+ * Cleaned up Gemfile
68
+ * Updated 'slop' gem
69
+
70
+
71
+ ## 0.3.0
72
+
73
+ * identity map hotfix
74
+
75
+
76
+ ## 0.2.0
77
+
78
+ * don't know what happened here, check git log I guess
79
+
80
+
81
+ ## 0.1.0
82
+
83
+ * initial release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,102 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ praxis-mapper (3.1.0)
5
+ activesupport (~> 4)
6
+ randexp (~> 0)
7
+ sequel (~> 4)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (4.1.5)
13
+ i18n (~> 0.6, >= 0.6.9)
14
+ json (~> 1.7, >= 1.7.7)
15
+ minitest (~> 5.1)
16
+ thread_safe (~> 0.1)
17
+ tzinfo (~> 1.1)
18
+ binding_of_caller (0.7.2)
19
+ debug_inspector (>= 0.0.1)
20
+ byebug (2.7.0)
21
+ columnize (~> 0.3)
22
+ debugger-linecache (~> 1.2)
23
+ celluloid (0.15.2)
24
+ timers (~> 1.1.0)
25
+ coderay (1.1.0)
26
+ columnize (0.8.9)
27
+ debug_inspector (0.0.2)
28
+ debugger-linecache (1.2.0)
29
+ diff-lcs (1.2.5)
30
+ ffi (1.9.3)
31
+ formatador (0.2.5)
32
+ fuubar (1.3.3)
33
+ rspec (>= 2.14.0, < 3.1.0)
34
+ ruby-progressbar (~> 1.4)
35
+ guard (2.6.1)
36
+ formatador (>= 0.2.4)
37
+ listen (~> 2.7)
38
+ lumberjack (~> 1.0)
39
+ pry (>= 0.9.12)
40
+ thor (>= 0.18.1)
41
+ guard-rspec (4.3.1)
42
+ guard (~> 2.1)
43
+ rspec (>= 2.14, < 4.0)
44
+ i18n (0.6.11)
45
+ json (1.8.1)
46
+ listen (2.7.9)
47
+ celluloid (>= 0.15.2)
48
+ rb-fsevent (>= 0.9.3)
49
+ rb-inotify (>= 0.9)
50
+ lumberjack (1.0.9)
51
+ method_source (0.8.2)
52
+ minitest (5.4.0)
53
+ pry (0.10.1)
54
+ coderay (~> 1.1.0)
55
+ method_source (~> 0.8.1)
56
+ slop (~> 3.4)
57
+ pry-byebug (1.3.3)
58
+ byebug (~> 2.7)
59
+ pry (~> 0.10)
60
+ pry-stack_explorer (0.4.9.1)
61
+ binding_of_caller (>= 0.7)
62
+ pry (>= 0.9.11)
63
+ rake (0.9.6)
64
+ randexp (0.1.7)
65
+ rb-fsevent (0.9.4)
66
+ rb-inotify (0.9.5)
67
+ ffi (>= 0.5.0)
68
+ redcarpet (2.3.0)
69
+ rspec (2.14.1)
70
+ rspec-core (~> 2.14.0)
71
+ rspec-expectations (~> 2.14.0)
72
+ rspec-mocks (~> 2.14.0)
73
+ rspec-core (2.14.8)
74
+ rspec-expectations (2.14.5)
75
+ diff-lcs (>= 1.1.3, < 2.0)
76
+ rspec-mocks (2.14.6)
77
+ ruby-progressbar (1.5.1)
78
+ sequel (4.13.0)
79
+ slop (3.6.0)
80
+ thor (0.19.1)
81
+ thread_safe (0.3.4)
82
+ timers (1.1.0)
83
+ tzinfo (1.2.2)
84
+ thread_safe (~> 0.1)
85
+ yard (0.8.7.4)
86
+
87
+ PLATFORMS
88
+ ruby
89
+
90
+ DEPENDENCIES
91
+ bundler (~> 1.6)
92
+ fuubar (~> 1)
93
+ guard (~> 2)
94
+ guard-rspec
95
+ praxis-mapper!
96
+ pry (~> 0)
97
+ pry-byebug (~> 1)
98
+ pry-stack_explorer (~> 0)
99
+ rake (~> 0)
100
+ redcarpet (< 3.0)
101
+ rspec (< 2.99)
102
+ yard (~> 0.8.7)
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ # Config file for Guard
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, cmd: 'bundle exec rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/praxis-mapper/(.+)\.rb$}) { |m| "spec/praxis-mapper/#{m[1]}_spec.rb" }
7
+ watch('spec/*.rb') { 'spec' }
8
+ watch('lib/praxis-mapper.rb') { 'spec' }
9
+ watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
10
+ end
11
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 RightScale
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Praxis::Mapper
2
+
3
+ Praxis::Mapper is a library that allows for large amounts of data to be loaded for a tree of associated models,
4
+ while minimizing requests to external services.
5
+ It does multi-stage fetch, with compaction between each stage.
6
+
7
+ Documentation and examples coming soon.
8
+
9
+ ## Testing
10
+
11
+ To run unit tests:
12
+
13
+ bundle exec rspec
14
+
15
+ ## License
16
+
17
+ This software is released under the [MIT License](http://www.opensource.org/licenses/MIT). Please see [LICENSE](LICENSE) for further details.
18
+
19
+ Copyright (c) 2014 RightScale
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler/setup'
2
+
3
+ require 'rspec/core'
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run RSpec code examples with simplecov"
7
+ RSpec::Core::RakeTask.new do |spec|
8
+ spec.pattern = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+ task :default => :spec
12
+
13
+ require 'yard'
14
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,40 @@
1
+ module Praxis::Mapper
2
+ class ConfigHash < BasicObject
3
+
4
+ attr_reader :hash
5
+
6
+ def self.from(hash={},&block)
7
+ self.new(hash,&block)
8
+ end
9
+
10
+ def initialize(hash={},&block)
11
+ @hash = hash
12
+ @block = block
13
+ end
14
+
15
+ def to_hash
16
+ self.instance_eval(&@block)
17
+ @hash
18
+ end
19
+
20
+ def method_missing(name, value, *rest, &block)
21
+ if (existing = @hash[name])
22
+ if block
23
+ existing << [value, block]
24
+ else
25
+ existing << value
26
+ rest.each do |v|
27
+ existing << v
28
+ end
29
+ end
30
+ else
31
+ if rest.any?
32
+ @hash[name] = [value] + rest
33
+ else
34
+ @hash[name] = value
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,102 @@
1
+ module Praxis::Mapper
2
+ class ConnectionManager
3
+
4
+ # Configures a data store.
5
+ def self.setup(config_data={}, &block)
6
+ config_data.each do |repository_name, data|
7
+ klass_name = data.delete(:connection_factory)
8
+ connection_factory_class = Object.const_get(klass_name)
9
+ repositories[repository_name][:connection_factory] = connection_factory_class.new(data[:connection_opts])
10
+
11
+ if (query_klass_name = data.delete(:query))
12
+ query_klass = Object.const_get(query_klass_name)
13
+ repositories[repository_name][:query] = query_klass
14
+ end
15
+ end
16
+ if block_given?
17
+ self.instance_eval(&block)
18
+ end
19
+ end
20
+
21
+ def self.repository(repository_name, data=nil,&block)
22
+ return repositories[repository_name] if data.nil? && !block_given?
23
+
24
+ if data && data[:query]
25
+ query_klass = case data[:query]
26
+ when String
27
+ query_klass_name = data[:query]
28
+ Object.const_get(query_klass_name) #FIXME: won't really work consistently
29
+ when Class
30
+ data[:query]
31
+ when Symbol
32
+ raise "symbol support is not implemented yet"
33
+ else
34
+ raise "unknown type for query: #{data[:query].inspect} has type #{data[:query].class}"
35
+ end
36
+ repositories[repository_name][:query] = query_klass
37
+ end
38
+
39
+ if block_given?
40
+ # TODO: ? complain if data.has_key?(:connection_factory)
41
+ repositories[repository_name][:connection_factory] = block
42
+ elsif data
43
+ klass_name = data.delete(:connection_factory)
44
+ connection_factory_class = Object.const_get(klass_name) #FIXME: won't really work consistently
45
+ repositories[repository_name][:connection_factory] = connection_factory_class.new(data[:connection_opts])
46
+ end
47
+ end
48
+
49
+
50
+ def self.repositories
51
+ @repositories ||= Hash.new do |hash,key|
52
+ hash[key] = {
53
+ :connection_factory => nil,
54
+ :query => Praxis::Mapper::Query::Sql
55
+ }
56
+ end
57
+ end
58
+
59
+ def repositories
60
+ self.class.repositories
61
+ end
62
+
63
+ def repository(repository_name)
64
+ self.repositories[repository_name]
65
+ end
66
+
67
+ def initialize
68
+ @connections = {}
69
+ end
70
+
71
+ def checkout(name)
72
+ connection = @connections[name]
73
+ return connection if connection
74
+
75
+ factory = repositories[name][:connection_factory]
76
+ connection = if factory.kind_of?(Proc)
77
+ factory.call
78
+ else
79
+ factory.checkout
80
+ end
81
+
82
+ @connections[name] = connection
83
+ end
84
+
85
+ def release_one(name)
86
+ if (connection = @connections.delete(name))
87
+ return true if repositories[name][:connection_factory].kind_of? Proc
88
+ repositories[name][:connection_factory].release(connection)
89
+ end
90
+ end
91
+
92
+ def release(name=nil)
93
+ if name
94
+ release_one(name)
95
+ else
96
+ names = @connections.keys
97
+ names.each { |name| release_one(name) }
98
+ end
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,38 @@
1
+ module Praxis::Mapper
2
+ module Finalizable
3
+
4
+
5
+ def self.extended(klass)
6
+ klass.module_eval do
7
+ @finalizable = Set.new
8
+ end
9
+ end
10
+
11
+ def inherited(base)
12
+ @finalizable << base
13
+ base.instance_variable_set(:@finalizable, @finalizable)
14
+ base.instance_variable_set(:@finalized, false)
15
+ end
16
+
17
+ def finalizable
18
+ @finalizable
19
+ end
20
+
21
+ def finalized?
22
+ @finalized
23
+ end
24
+
25
+ def _finalize!
26
+ @finalized = true
27
+ end
28
+
29
+ def finalize!
30
+ self.finalizable.reject(&:finalized?).each do |klass|
31
+ klass._finalize!
32
+ end
33
+
34
+ self.finalize! unless self.finalizable.all?(&:finalized?)
35
+ end
36
+
37
+ end
38
+ end