quiver 0.21.0

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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +6 -0
  6. data/README.md +22 -0
  7. data/Rakefile +17 -0
  8. data/bin/quiver +9 -0
  9. data/lib/quiver.rb +46 -0
  10. data/lib/quiver/abstract_action.rb +31 -0
  11. data/lib/quiver/action.rb +208 -0
  12. data/lib/quiver/action/filter_error.rb +33 -0
  13. data/lib/quiver/action/filter_value.rb +152 -0
  14. data/lib/quiver/action/invalid_request_body_error.rb +30 -0
  15. data/lib/quiver/action/pagination_link_builder.rb +67 -0
  16. data/lib/quiver/adapter.rb +51 -0
  17. data/lib/quiver/adapter/active_record_adapter_filter.rb +102 -0
  18. data/lib/quiver/adapter/active_record_helpers.rb +258 -0
  19. data/lib/quiver/adapter/arec_low_level_creator.rb +82 -0
  20. data/lib/quiver/adapter/arec_low_level_deleter.rb +74 -0
  21. data/lib/quiver/adapter/arec_low_level_updater.rb +105 -0
  22. data/lib/quiver/adapter/filter_helpers.rb +58 -0
  23. data/lib/quiver/adapter/helpers_helpers.rb +53 -0
  24. data/lib/quiver/adapter/memory_adapter_filter.rb +71 -0
  25. data/lib/quiver/adapter/memory_adapter_store.rb +34 -0
  26. data/lib/quiver/adapter/memory_helpers.rb +182 -0
  27. data/lib/quiver/adapter/memory_uuid_primary_key.rb +25 -0
  28. data/lib/quiver/application.rb +128 -0
  29. data/lib/quiver/cli/app.rb +25 -0
  30. data/lib/quiver/cli/generators/endpoint.rb +17 -0
  31. data/lib/quiver/cli/generators/new_application.rb +166 -0
  32. data/lib/quiver/cli/generators/new_application_cli.rb +25 -0
  33. data/lib/quiver/cli/server.rb +37 -0
  34. data/lib/quiver/cli/templates/Gemfile.tt +8 -0
  35. data/lib/quiver/cli/templates/Rakefile.tt +12 -0
  36. data/lib/quiver/cli/templates/config.tt +3 -0
  37. data/lib/quiver/cli/templates/config/database.tt +21 -0
  38. data/lib/quiver/cli/templates/gemspec.tt +33 -0
  39. data/lib/quiver/cli/templates/gitignore.tt +10 -0
  40. data/lib/quiver/cli/templates/lib/application.tt +14 -0
  41. data/lib/quiver/cli/templates/lib/application/config/router.tt +11 -0
  42. data/lib/quiver/cli/templates/lib/application/version.tt +3 -0
  43. data/lib/quiver/cli/templates/spec/spec_helper.tt +19 -0
  44. data/lib/quiver/duty.rb +34 -0
  45. data/lib/quiver/duty_master.rb +23 -0
  46. data/lib/quiver/duty_master/delayed_job_adapter.rb +15 -0
  47. data/lib/quiver/duty_master/memory_adapter.rb +18 -0
  48. data/lib/quiver/duty_test_helper.rb +23 -0
  49. data/lib/quiver/duty_test_helper/delayed_job_helper.rb +9 -0
  50. data/lib/quiver/duty_test_helper/memory_helper.rb +15 -0
  51. data/lib/quiver/error.rb +24 -0
  52. data/lib/quiver/error_collection.rb +60 -0
  53. data/lib/quiver/json_parser.rb +17 -0
  54. data/lib/quiver/logger.rb +26 -0
  55. data/lib/quiver/mapper.rb +311 -0
  56. data/lib/quiver/mapper/hook.rb +21 -0
  57. data/lib/quiver/mapper/mapper_result.rb +7 -0
  58. data/lib/quiver/mapper/not_found_error.rb +27 -0
  59. data/lib/quiver/mapper/simple_query_builder.rb +70 -0
  60. data/lib/quiver/mapper/soft_delete.rb +15 -0
  61. data/lib/quiver/mappers.rb +75 -0
  62. data/lib/quiver/middleware_stack.rb +35 -0
  63. data/lib/quiver/model.rb +63 -0
  64. data/lib/quiver/model/soft_delete.rb +14 -0
  65. data/lib/quiver/model/validation_error.rb +27 -0
  66. data/lib/quiver/model/validations.rb +45 -0
  67. data/lib/quiver/patcher.rb +94 -0
  68. data/lib/quiver/result.rb +44 -0
  69. data/lib/quiver/route_helper.rb +16 -0
  70. data/lib/quiver/router.rb +37 -0
  71. data/lib/quiver/serialization.rb +6 -0
  72. data/lib/quiver/serialization/json_api.rb +7 -0
  73. data/lib/quiver/serialization/json_api/item_type_handler.rb +96 -0
  74. data/lib/quiver/serialization/json_api/serializer.rb +77 -0
  75. data/lib/quiver/tasks.rb +31 -0
  76. data/lib/quiver/validator.rb +83 -0
  77. data/lib/quiver/validators/base.rb +21 -0
  78. data/lib/quiver/validators/presence.rb +34 -0
  79. data/lib/quiver/validators/unique.rb +33 -0
  80. data/lib/quiver/version.rb +3 -0
  81. data/quiver.gemspec +42 -0
  82. metadata +393 -0
@@ -0,0 +1,34 @@
1
+ module Quiver
2
+ module Adapter
3
+ class MemoryAdapterStore
4
+ def initialize
5
+ clean!
6
+ end
7
+
8
+ def clean!
9
+ self.store = {}
10
+ end
11
+
12
+ def get(key)
13
+ store[key] ||= {}
14
+ end
15
+
16
+ def transaction(&block)
17
+ pristine_store = store.deep_dup
18
+
19
+ begin
20
+ yield block
21
+ rescue Quiver::Mappers::RollbackTransaction
22
+ self.store = pristine_store
23
+ rescue => ex
24
+ self.store = pristine_store
25
+ raise ex
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ attr_accessor :store
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,182 @@
1
+ require 'quiver/adapter/memory_adapter_filter'
2
+
3
+ module Quiver
4
+ module Adapter
5
+ module Memory
6
+ include Quiver::Adapter::HelpersHelpers
7
+
8
+ def self.included(host)
9
+ super
10
+
11
+ host.adapter_type(:memory)
12
+ host.send(:extend, ClassMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ def next_pk
17
+ @pk_series ||= 0
18
+ @pk_series += 1
19
+ end
20
+ end
21
+
22
+ def find(primary_key)
23
+ Quiver::Adapter::AdapterResult.new do |errors|
24
+ if store.key?(primary_key)
25
+ load_additional([store[primary_key].dup]).first
26
+ else
27
+ errors << Quiver::Mapper::NotFoundError.new('record', 'not_found')
28
+ nil
29
+ end
30
+ end
31
+ end
32
+
33
+ def count
34
+ count = stores.get(store_key).count
35
+
36
+ Quiver::Adapter::AdapterResult.new(count)
37
+ end
38
+
39
+ def create(attributes, transaction)
40
+ primary_key = new_primary_key
41
+
42
+ if attributes[:__type__]
43
+ attributes = attributes.merge(
44
+ primary_key_name => primary_key,
45
+ attributes[:__type__][:name] => attributes[:__type__][:value]
46
+ )
47
+
48
+ attributes.delete(:__type__)
49
+ else
50
+ attributes = attributes.merge(
51
+ primary_key_name => primary_key
52
+ )
53
+ end
54
+
55
+ store[primary_key] = attributes
56
+
57
+ Quiver::Adapter::AdapterResult.new(attributes.dup)
58
+ end
59
+
60
+ def update(attributes, transaction)
61
+ primary_key = attributes[primary_key_name]
62
+
63
+ if attributes[:__type__]
64
+ attributes = attributes.merge(
65
+ attributes[:__type__][:name] => attributes[:__type__][:value]
66
+ )
67
+
68
+ attributes.delete(:__type__)
69
+ end
70
+
71
+ Quiver::Adapter::AdapterResult.new do |errors|
72
+ if store.key?(primary_key)
73
+ store[primary_key] = attributes.dup
74
+ else
75
+ errors << Quiver::Mapper::NotFoundError.new('record', 'does_not_exist')
76
+ nil
77
+ end
78
+ end
79
+ end
80
+
81
+ def hard_delete(attributes, transaction)
82
+ store = stores.get(store_key)
83
+
84
+ Quiver::Adapter::AdapterResult.new do |errors|
85
+ object = store.delete(attributes[primary_key_name])
86
+
87
+ if object
88
+ {}
89
+ else
90
+ errors << Quiver::Mapper::NotFoundError.new('record', 'not_found')
91
+ nil
92
+ end
93
+ end
94
+ end
95
+
96
+ def query(q={})
97
+ store = stores.get(store_key)
98
+
99
+ filter_params = q[:filter] || {}
100
+ sort_params = q[:sort] || {}
101
+ pagination_params = q[:page] || {}
102
+
103
+ objects = filter_klass.new(
104
+ store.values,
105
+ filter_params
106
+ ).filter
107
+
108
+ if sort_params.any?
109
+ objects = objects.sort do |a, b|
110
+ sort_params.reduce(0) do |memo, (attr, asc)|
111
+ attr = attr.to_sym
112
+
113
+ # A memo of 0 means either no sorting has happened yet,
114
+ # or there has been a tie that might need breaking.
115
+ if memo == 0
116
+ sign = asc ? 1 : -1
117
+ sign * (a[attr] <=> b[attr])
118
+ else
119
+ memo
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ offset = pagination_params['offset'] || 0
126
+ limit = pagination_params['limit'] || -1
127
+ total_count = objects.count
128
+
129
+ if limit == -1
130
+ range_end = -1
131
+ else
132
+ range_end = offset + limit - 1
133
+ end
134
+ objects = objects[offset..range_end]
135
+
136
+ objects = load_additional(objects.map do |attrs|
137
+ attrs.dup
138
+ end)
139
+
140
+ result = Quiver::Adapter::AdapterResult.new(objects)
141
+
142
+ if pagination_params.any?
143
+ result.data[:pagination_offset] = offset
144
+ result.data[:pagination_limit] = limit
145
+ result.data[:total_count] = total_count
146
+ end
147
+
148
+ result
149
+ end
150
+
151
+ private
152
+
153
+ def stores
154
+ @stores ||= self.class.parents[-2]::Application.memory_adapter_store
155
+ end
156
+
157
+ private
158
+
159
+ def load_additional(items)
160
+ items
161
+ end
162
+
163
+ def new_primary_key
164
+ self.class.next_pk
165
+ end
166
+
167
+ def store_key
168
+ mapper_name
169
+ end
170
+
171
+ def store
172
+ stores.get(store_key.to_s)
173
+ end
174
+
175
+ def filter_klass
176
+ Quiver::Adapter::MemoryAdapterFilter
177
+ end
178
+ end
179
+
180
+ MemoryHelpers = Memory
181
+ end
182
+ end
@@ -0,0 +1,25 @@
1
+ module Quiver
2
+ module Adapter
3
+ module Memory
4
+ module UuidPrimaryKey
5
+ def self.included(k)
6
+ k.send(:extend, ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def next_pk
11
+ @pk_series ||= []
12
+
13
+ new_pk = SecureRandom.uuid
14
+ while @pk_series.include?(new_pk)
15
+ new_pk = SecureRandom.uuid
16
+ end
17
+
18
+ @pk_series << new_pk
19
+ new_pk
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,128 @@
1
+ module Quiver
2
+ module Application
3
+ def self.included(host)
4
+ host.send(:extend, ClassMethods)
5
+
6
+ host.root_module = host.parent
7
+ host.root_module.const_set(:Adapters, Module.new)
8
+ host.root_module.const_set(:Duties, Module.new)
9
+ host.root_module.const_set(:Endpoints, Module.new)
10
+ host.root_module.const_set(:Models, Module.new)
11
+ host.root_module.const_set(:Mappers, Quiver::Mappers.dup)
12
+ host.root_module.const_set(:Serializers, Module.new)
13
+ host.root_module.const_set(:Tasks, Quiver::Tasks.dup)
14
+ host.root_module.const_set(:Logger, Quiver::Logger.dup)
15
+ host.root_module.const_set(:DutyMaster, Quiver::DutyMaster.dup)
16
+ host.root_module.const_set(:DutyTestHelper, Quiver::DutyTestHelper.dup)
17
+ host.memory_adapter_store = Quiver::Adapter::MemoryAdapterStore.new
18
+
19
+ application_file = caller[0].partition(':').first
20
+ host.app_root = File.expand_path('..', File.dirname(application_file))
21
+ host.lib_dir = File.join(host.app_root, 'lib', File.basename(application_file, '.rb'))
22
+ host.default_adapter_type = :memory
23
+ host.default_duty_queue_backend = :memory
24
+ end
25
+
26
+ module ClassMethods
27
+ attr_reader :app_root, :lib_dir, :root_module, :using_active_record,
28
+ :using_delayed_job
29
+
30
+ attr_accessor :memory_adapter_store, :default_adapter_type, :default_duty_queue_backend,
31
+ :middleware_stack
32
+
33
+ def app_root=(path)
34
+ @app_root ||= path
35
+ end
36
+
37
+ def lib_dir=(path)
38
+ @lib_dir ||= path
39
+ end
40
+
41
+ def root_module=(mod)
42
+ @root_module ||= mod
43
+ end
44
+
45
+ def logger
46
+ @logger ||= begin
47
+ logger = root_module::Logger.new("log/logstash_#{ENV['RACK_ENV']}.log")
48
+ logger.formatter = -> (severity, datetime, progname, msg) {
49
+ "#{msg.to_json}\n"
50
+ }
51
+ logger
52
+ end
53
+ end
54
+
55
+ def middleware_stack
56
+ @middleware_stack ||= Quiver::MiddlewareStack.new
57
+ end
58
+
59
+ def load_everything!(extra_folders = [])
60
+ load_env_vars!
61
+
62
+ Dir[File.join(lib_dir, 'config', 'initializers', '**', '*.rb')].each { |f| require f}
63
+
64
+ if !ENV["#{self.parents.first.to_s.underscore.upcase}_DOUBLE_MODE"]
65
+ Dir[File.join(app_root, 'config', 'initializers', '**', '*.rb')].each { |f| require f}
66
+ end
67
+
68
+ require File.join(lib_dir, 'config', 'router')
69
+
70
+ folders = ['models', 'mappers', 'adapters', 'serializers', 'endpoints', 'duties'] + extra_folders
71
+
72
+ folders.each do |folder|
73
+ Dir[File.join(lib_dir, folder, '*.rb')].each { |f| require f }
74
+ Dir[File.join(lib_dir, folder, '**', '*.rb')].each { |f| require f }
75
+ end
76
+ end
77
+
78
+ def use_active_record!(config_path = File.join(app_root, 'config', 'database.yml'))
79
+ self.using_active_record = true
80
+ require 'active_record'
81
+
82
+ ActiveRecord::Base.configurations = YAML.load(ERB.new(File.read(config_path)).result)
83
+ ActiveRecord::Base.establish_connection
84
+ end
85
+
86
+ def use_delayed_job!
87
+ self.using_delayed_job = true
88
+ require 'delayed_job_active_record'
89
+ end
90
+
91
+ private
92
+
93
+ attr_writer :using_active_record, :using_delayed_job
94
+
95
+ def load_env_vars!
96
+ file_path = File.join(lib_dir, 'config', 'environment.yml')
97
+
98
+ if File.exist?(file_path)
99
+ require 'yaml'
100
+
101
+ var_data = YAML.load(File.read(file_path))
102
+
103
+ var_data.fetch(ENV['RACK_ENV'], {}).each do |k, v|
104
+ ENV[k.upcase] ||= v.to_s unless v.is_a?(Hash)
105
+ end
106
+
107
+ var_data.each do |k, v|
108
+ ENV[k.upcase] ||= v.to_s unless v.is_a?(Hash)
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ attr_reader :router
115
+
116
+ def initialize
117
+ self.router = self.class.root_module::Config::Router.new
118
+ end
119
+
120
+ def call(env)
121
+ self.class.middleware_stack.stack(router).call(env)
122
+ end
123
+
124
+ private
125
+
126
+ attr_writer :router
127
+ end
128
+ end
@@ -0,0 +1,25 @@
1
+ require 'thor'
2
+ require 'active_support'
3
+ require 'quiver/version'
4
+
5
+ require 'quiver/cli/generators/new_application'
6
+ require 'quiver/cli/generators/new_application_cli'
7
+
8
+ module Quiver
9
+ module CLI
10
+ class App < Thor
11
+ package_name "Quiver"
12
+
13
+ desc "server", "run a Quiver application"
14
+ method_option :host, aliases: ['-h', '-b'], desc: 'host for the server to bind to (default 127.0.0.1)'
15
+ method_option :port, aliases: '-p', desc: 'port to listen on'
16
+ method_option :reloading_code, aliases: '-r', desc: 'reload code automatically', type: :boolean, default: true
17
+ def server
18
+ require 'quiver/cli/server'
19
+ Quiver::CLI::Server.new(options).start
20
+ end
21
+
22
+ register(NewApplicationCli, "new", "new APP_NAME", "generates a new Quiver application")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Quiver
2
+ module CLI
3
+ class Endpoint < Thor::Group
4
+ include Thor::Actions
5
+
6
+ argument :name
7
+
8
+ def self.source_root
9
+ File.expand_path(File.join('..', '..'), __FILE__)
10
+ end
11
+
12
+ def first
13
+ puts name
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,166 @@
1
+ module Quiver
2
+ module CLI
3
+ class NewApplication
4
+ include Thor::Base
5
+ include Thor::Actions
6
+
7
+ def self.source_root
8
+ File.expand_path(File.join('..', '..', 'templates'), __FILE__)
9
+ end
10
+
11
+ def self.inherited(subclass)
12
+ generators_hash.each do |k, v|
13
+ subclass.generators_hash[k] = v
14
+ end
15
+
16
+ generators_array.each do |i|
17
+ subclass.generators_array << i
18
+ end
19
+ end
20
+
21
+ def self.generators_array
22
+ @generators_array ||= []
23
+ end
24
+
25
+ def self.generators_hash
26
+ @generators_hash ||= {}
27
+ end
28
+
29
+ def self.register_step(name, step, opts={})
30
+ generators_hash[name] = step
31
+ generators_array << name unless opts[:skip_queueing] || generators_array.include?(name)
32
+ end
33
+
34
+ def initialize(name, options)
35
+ self.name = name
36
+ self.options = options
37
+ @destination_stack ||= []
38
+ self.behavior = :invoke
39
+ end
40
+
41
+ def generate!
42
+ self.class.generators_array.each do |name|
43
+ if step = self.class.generators_hash[name]
44
+ instance_exec(&step)
45
+ end
46
+ end
47
+
48
+ git_init
49
+ end
50
+
51
+ register_step :gemfile, proc {
52
+ template(
53
+ File.join('Gemfile.tt'),
54
+ File.join(underscored_name, 'Gemfile')
55
+ )
56
+ }
57
+
58
+ register_step :gemspec, proc {
59
+ template(
60
+ File.join('gemspec.tt'),
61
+ File.join(underscored_name, "#{underscored_name}.gemspec")
62
+ )
63
+ }
64
+
65
+ register_step :rakefile, proc {
66
+ template(
67
+ File.join('Rakefile.tt'),
68
+ File.join(underscored_name, 'Rakefile')
69
+ )
70
+ }
71
+
72
+ register_step :config_ru, proc {
73
+ template(
74
+ File.join('config.tt'),
75
+ File.join(underscored_name, 'config.ru')
76
+ )
77
+ }
78
+
79
+ register_step :application, proc {
80
+ template(
81
+ File.join('lib', 'application.tt'),
82
+ File.join(underscored_name, 'lib', "#{underscored_name}.rb")
83
+ )
84
+ }
85
+
86
+ register_step :version, proc {
87
+ template(
88
+ File.join('lib', 'application', 'version.tt'),
89
+ File.join(underscored_name, 'lib', underscored_name, 'version.rb')
90
+ )
91
+ }
92
+
93
+ register_step :router, proc {
94
+ template(
95
+ File.join('lib', 'application', 'config', 'router.tt'),
96
+ File.join(underscored_name, 'lib', underscored_name, 'config', 'router.rb')
97
+ )
98
+ }
99
+
100
+ register_step :gitkeeps, proc {
101
+ create_file(File.join(underscored_name, 'config', '.gitkeep'))
102
+ create_file(File.join(underscored_name, 'lib', underscored_name, 'adapters', '.gitkeep'))
103
+ create_file(File.join(underscored_name, 'lib', underscored_name, 'endpoints', '.gitkeep'))
104
+ create_file(File.join(underscored_name, 'lib', underscored_name, 'mappers', '.gitkeep'))
105
+ create_file(File.join(underscored_name, 'lib', underscored_name, 'models', '.gitkeep'))
106
+ create_file(File.join(underscored_name, 'lib', underscored_name, 'serializers', '.gitkeep'))
107
+ }
108
+
109
+ register_step :gitignore, proc {
110
+ template(
111
+ File.join('gitignore.tt'),
112
+ File.join(underscored_name, '.gitignore')
113
+ )
114
+ }
115
+
116
+ register_step :spec_helper, proc {
117
+ if options[:rspec]
118
+ template(
119
+ File.join('spec', 'spec_helper.tt'),
120
+ File.join(underscored_name, 'spec', 'spec_helper.rb')
121
+ )
122
+
123
+ create_file(File.join(underscored_name, 'spec', 'support', '.gitkeep'))
124
+ end
125
+ }
126
+
127
+ register_step :active_record, proc {
128
+ if options[:active_record]
129
+ create_file(File.join(underscored_name, 'db', 'migrate', '.gitkeep'))
130
+
131
+ template(
132
+ File.join('config', 'database.tt'),
133
+ File.join(underscored_name, 'config', 'database.yml')
134
+ )
135
+
136
+ template(
137
+ File.join('config', 'database.tt'),
138
+ File.join(underscored_name, 'config', 'database.example.yml')
139
+ )
140
+ end
141
+ }
142
+
143
+ private
144
+
145
+ attr_accessor :name, :options
146
+
147
+ def underscored_name
148
+ @underscored_name ||= name.underscore
149
+ end
150
+
151
+ def camelized_name
152
+ @camelized_name ||= name.camelize
153
+ end
154
+
155
+ def git_init
156
+ inside(underscored_name) do
157
+ run('git init')
158
+ run('git add .')
159
+
160
+ output = run(%Q{git commit -m " Initial Commit\n _____________________\n| /--<<<\n|___________________/----<<<"}, verbose: false, capture: true)
161
+ puts output.sub(/Commit.*?\n/, "Commit\n")
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end