praxis-mapper 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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