quiver 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
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