ocean-rails 1.14.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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +72 -0
  4. data/Rakefile +38 -0
  5. data/lib/generators/ocean_scaffold/USAGE +8 -0
  6. data/lib/generators/ocean_scaffold/ocean_scaffold_generator.rb +76 -0
  7. data/lib/generators/ocean_scaffold/templates/controller_specs/create_spec.rb +71 -0
  8. data/lib/generators/ocean_scaffold/templates/controller_specs/delete_spec.rb +47 -0
  9. data/lib/generators/ocean_scaffold/templates/controller_specs/index_spec.rb +45 -0
  10. data/lib/generators/ocean_scaffold/templates/controller_specs/show_spec.rb +43 -0
  11. data/lib/generators/ocean_scaffold/templates/controller_specs/update_spec.rb +85 -0
  12. data/lib/generators/ocean_scaffold/templates/model_spec.rb +76 -0
  13. data/lib/generators/ocean_scaffold/templates/resource_routing_spec.rb +27 -0
  14. data/lib/generators/ocean_scaffold/templates/view_specs/_resource_spec.rb +55 -0
  15. data/lib/generators/ocean_scaffold/templates/views/_resource.json.jbuilder +8 -0
  16. data/lib/generators/ocean_setup/USAGE +8 -0
  17. data/lib/generators/ocean_setup/ocean_setup_generator.rb +93 -0
  18. data/lib/generators/ocean_setup/templates/Gemfile +19 -0
  19. data/lib/generators/ocean_setup/templates/alive_controller.rb +18 -0
  20. data/lib/generators/ocean_setup/templates/alive_routing_spec.rb +11 -0
  21. data/lib/generators/ocean_setup/templates/alive_spec.rb +12 -0
  22. data/lib/generators/ocean_setup/templates/api_constants.rb +19 -0
  23. data/lib/generators/ocean_setup/templates/application_controller.rb +8 -0
  24. data/lib/generators/ocean_setup/templates/application_helper.rb +34 -0
  25. data/lib/generators/ocean_setup/templates/config.yml.example +57 -0
  26. data/lib/generators/ocean_setup/templates/errors_controller.rb +14 -0
  27. data/lib/generators/ocean_setup/templates/gitignore +37 -0
  28. data/lib/generators/ocean_setup/templates/hyperlinks.rb +22 -0
  29. data/lib/generators/ocean_setup/templates/ocean_constants.rb +36 -0
  30. data/lib/generators/ocean_setup/templates/routes.rb +8 -0
  31. data/lib/generators/ocean_setup/templates/spec_helper.rb +47 -0
  32. data/lib/generators/ocean_setup/templates/zeromq_logger.rb +15 -0
  33. data/lib/ocean-rails.rb +38 -0
  34. data/lib/ocean/api.rb +263 -0
  35. data/lib/ocean/api_resource.rb +135 -0
  36. data/lib/ocean/flooding.rb +29 -0
  37. data/lib/ocean/ocean_application_controller.rb +214 -0
  38. data/lib/ocean/ocean_resource_controller.rb +76 -0
  39. data/lib/ocean/ocean_resource_model.rb +61 -0
  40. data/lib/ocean/selective_rack_logger.rb +33 -0
  41. data/lib/ocean/version.rb +3 -0
  42. data/lib/ocean/zero_log.rb +184 -0
  43. data/lib/ocean/zeromq_logger.rb +42 -0
  44. data/lib/tasks/ocean_tasks.rake +4 -0
  45. data/lib/template.rb +31 -0
  46. data/lib/templates/rails/scaffold_controller/controller.rb +91 -0
  47. metadata +267 -0
@@ -0,0 +1,61 @@
1
+ module Ocean
2
+ module OceanResourceModel
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Inheritable invalidation callbacks
8
+ after_create { |model| model.try(:invalidate, true) }
9
+ after_update { |model| model.try(:invalidate) }
10
+ after_touch { |model| model.try(:invalidate) }
11
+ after_destroy { |model| model.try(:invalidate) }
12
+ end
13
+
14
+
15
+ module ClassMethods
16
+
17
+ #
18
+ # The presence of +ocean_resource_model+ in a Rails ActiveRecord model declares
19
+ # that the model is an Ocean resource. It takes five keyword parameters:
20
+ #
21
+ # +index+: defaults to <code>[:name]</code>. Enumerates the model attributes which
22
+ # may be used for parameter matching and grouping.
23
+ #
24
+ # +search+: defaults to +:description+. Names the model attribute used for substring
25
+ # searches. The attribute must be a string or text attribute.
26
+ #
27
+ # +page_size+: defaults to 25. When paginated collections are retrieved, this will
28
+ # be the default page_size for this resource.
29
+ #
30
+ # +invalidate_member+: defaults to +INVALIDATE_MEMBER_DEFAULT+. An array of strings
31
+ # enumerating the Varnish +BAN+ HTTP request URI suffixes to use to invalidate a
32
+ # resource member, including any relations derived from it.
33
+ #
34
+ # +invalidate_collection+: defaults to +INVALIDATE_COLLECTION_DEFAULT+. An array of strings
35
+ # enumerating the Varnish +BAN+ HTTP request URI suffixes to use to invalidate a
36
+ # collection of resources.
37
+ #
38
+ def ocean_resource_model(index: [:name],
39
+ search: :description,
40
+ page_size: 25,
41
+ invalidate_member: INVALIDATE_MEMBER_DEFAULT,
42
+ invalidate_collection: INVALIDATE_COLLECTION_DEFAULT
43
+ )
44
+ include ApiResource
45
+ cattr_accessor :index_only
46
+ cattr_accessor :index_search_property
47
+ cattr_accessor :collection_page_size
48
+ cattr_accessor :varnish_invalidate_member
49
+ cattr_accessor :varnish_invalidate_collection
50
+ self.index_only = index
51
+ self.index_search_property = search
52
+ self.collection_page_size = page_size
53
+ self.varnish_invalidate_member = invalidate_member
54
+ self.varnish_invalidate_collection = invalidate_collection
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+
61
+ ActiveRecord::Base.send :include, Ocean::OceanResourceModel
@@ -0,0 +1,33 @@
1
+ #
2
+ # This custom Rack middleware is used to turn off logging of requests made to
3
+ # <code>/alive</code> by Varnish every 15 seconds in order to detect
4
+ # failing instances for failover purposes.
5
+ #
6
+ class SelectiveRackLogger < Rails::Rack::Logger
7
+
8
+ #
9
+ # Initialises the selective Rack logger.
10
+ #
11
+ def initialize(app, opts = {})
12
+ @app = app
13
+ super
14
+ end
15
+
16
+ #
17
+ # Suppresses logging of /alive requests from Varnish.
18
+ #
19
+ def call(env)
20
+ if env['PATH_INFO'] == "/alive"
21
+ old_level = Rails.logger.level
22
+ Rails.logger.level = 1234567890 # > 5
23
+ begin
24
+ @app.call(env) # returns [..., ..., ...]
25
+ ensure
26
+ Rails.logger.level = old_level
27
+ end
28
+ else
29
+ super(env) # returns [..., ..., ...]
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,3 @@
1
+ module Ocean
2
+ VERSION = "1.14.0"
3
+ end
@@ -0,0 +1,184 @@
1
+ require 'ffi-rzmq'
2
+
3
+
4
+ #
5
+ # This is the class that provides the interface to ZeroMQ, both for sending log
6
+ # data to a cluster of log servers, and for receiving it.
7
+ #
8
+ # The Rails logger is replaced in production by the initializer zero_mq_logger.rb.
9
+ #
10
+ # The worker daemons running on the log machines use the pull_server and
11
+ # pull_worker methods in this class.
12
+ #
13
+ class ZeroLog
14
+
15
+ #
16
+ # This is the ZeroMQ context. We only need one per process, thus we
17
+ # store it in a class variable.
18
+ #
19
+ @@context = nil
20
+
21
+
22
+ #
23
+ # When the first ZeroLog instance is created, a ZeroMQ Context will be
24
+ # set up for the use of all threads in the process. A process exit
25
+ # handler will be set up to terminate the context on exit.
26
+ #
27
+ def initialize
28
+ super
29
+ unless @@context
30
+ #puts "Creating context"
31
+ @@context = ZMQ::Context.new(1)
32
+ #puts "Registering at_exit context terminator"
33
+ at_exit { @@context.terminate
34
+ #puts "Context terminated"
35
+ }
36
+ end
37
+ end
38
+
39
+
40
+ #
41
+ # This method returns the ZeroMQ Context object.
42
+ #
43
+ def context
44
+ @@context
45
+ end
46
+
47
+
48
+ #
49
+ # Register a trap handler for INT, to clean up and close any
50
+ # sockets we might have opened previously.
51
+ #
52
+ def trap_int(*sockets)
53
+ sockets.each do |s|
54
+ #puts "Registering at_exit socket closer"
55
+ at_exit { s.setsockopt(ZMQ::LINGER, 0)
56
+ s.close
57
+ #puts "Socket closed"
58
+ }
59
+ end
60
+ end
61
+
62
+
63
+ # ---------------------------------------------------------------------------
64
+ #
65
+ # Log data sender side (ruby, Ruby on Rails)
66
+ #
67
+ # ---------------------------------------------------------------------------
68
+
69
+ #
70
+ # This initialises the log data sender. We begin by creating the server and
71
+ # binding it; this is required when using +inproc+ sockets: bind must precede
72
+ # any connects. (Normally in ZeroMQ, this is not the case.) The code sets up,
73
+ # in a new thread, a +SUB+ server which is then connected to a +PUSH+ socket. The
74
+ # +SUB+ server waits for log data which is then pushed via the +PUSH+ socket to
75
+ # all available +LOG_HOSTS+. ZeroMQ handles buffering, re-connection and multiplexing
76
+ # (round-robin) to the +LOG_HOSTS+ +PULL+ servers.
77
+ #
78
+ def init_log_data_sender(sub_push="sub_push")
79
+ # First create the server and let it bind, as this is required
80
+ # when using inproc: bind must precede any connects.
81
+ Thread.new(context) do |c|
82
+ # Set up the server socket
83
+ #puts "Starting SUB server"
84
+ subscriber = c.socket(ZMQ::SUB)
85
+ #puts "Binding to the SUB socket"
86
+ subscriber.bind("inproc://#{sub_push}")
87
+ subscriber.setsockopt(ZMQ::SUBSCRIBE, "")
88
+ # Set up the PUSH socket
89
+ loggers = context.socket(ZMQ::PUSH)
90
+ LOG_HOSTS.each do |host|
91
+ #puts "Connecting to the PULL server #{host}"
92
+ loggers.connect("tcp://#{host}:10000")
93
+ end
94
+ # Connect SUB to PUSH via a queue and block
95
+ ZMQ::Device.new(ZMQ::QUEUE, subscriber, loggers)
96
+ # Context has been terminated, close sockets
97
+ subscriber.setsockopt(ZMQ::LINGER, 0)
98
+ subscriber.close
99
+ loggers.setsockopt(ZMQ::LINGER, 0)
100
+ loggers.close
101
+ #puts "Closed sockets in other thread"
102
+ end
103
+
104
+ sleep 0.1 # Brute force and primitive, but it works
105
+
106
+ # Next create the PUB socket and connect it to the other thread
107
+ #puts "Creating the PUB socket"
108
+ $log_publisher = context.socket(ZMQ::PUB)
109
+ trap_int($log_publisher)
110
+ #puts "Connecting to the SUB server"
111
+ $log_publisher.connect("inproc://#{sub_push}")
112
+ end
113
+
114
+
115
+ #
116
+ # This is a combination of a PUB main thread log data sender,
117
+ # pushing directly to the pull_server without the need for a local
118
+ # aggregator.
119
+ #
120
+ def log(data)
121
+ init_log_data_sender unless $log_publisher
122
+
123
+ # Send the data
124
+ json = data.to_json
125
+ #puts "Sending message #{json}"
126
+ $log_publisher.send_string(json)
127
+ data
128
+ end
129
+
130
+
131
+ # ---------------------------------------------------------------------------
132
+ #
133
+ # Log service (aggregation) side
134
+ #
135
+ # ---------------------------------------------------------------------------
136
+
137
+ #
138
+ # This is the inter-process ZeroMQ socket to which the Log workers send
139
+ # their received data.
140
+ #
141
+ PUSH_ADDRESS = "ipc://pull_worker.ipc"
142
+
143
+
144
+ #
145
+ # This is the PULL to PUSH server. It pushes the received data over IPC
146
+ # round-robin fashion to each PULL log worker on the machine.
147
+ #
148
+ def pull_server(address=PUSH_ADDRESS)
149
+ #puts "Starting PULL server"
150
+ puller = context.socket(ZMQ::PULL)
151
+ #puts "Binding to the PULL socket"
152
+ puller.bind("tcp://*:10000")
153
+ # Set up the PUSH socket
154
+ pusher = context.socket(ZMQ::PUSH)
155
+ #puts "Binding to the PUSH socket"
156
+ pusher.bind(address)
157
+ # Trap everything
158
+ trap_int(puller, pusher)
159
+ # Connect PULL to PUSH via a queue
160
+ ZMQ::Device.new(ZMQ::QUEUE, puller, pusher)
161
+ end
162
+
163
+
164
+ #
165
+ # This is the PULL worker. This particular version just prints all
166
+ # received data to STDOUT. Overload this method for other types of
167
+ # processing (such as storing received log info in a database).
168
+ #
169
+ def pull_worker(address=PUSH_ADDRESS)
170
+ #puts "Starting PULL worker"
171
+ puller = context.socket(ZMQ::PULL)
172
+ #puts "Connect to the PUSH socket"
173
+ puller.connect(address)
174
+ trap_int(puller)
175
+
176
+ while true do
177
+ s = ''
178
+ puller.recv_string(s)
179
+ puts "Received: #{s}"
180
+ end
181
+ end
182
+
183
+
184
+ end
@@ -0,0 +1,42 @@
1
+ require 'socket'
2
+
3
+ class ZeromqLogger
4
+
5
+ attr_accessor :level, :log_tags
6
+
7
+
8
+ def initialize
9
+ super
10
+ # Get info about our environment
11
+ @ip = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}.getnameinfo[0]
12
+ # Set up the logger
13
+ @logger = ZeroLog.new
14
+ @logger.init_log_data_sender "/tmp/sub_push_#{Process.pid}"
15
+ end
16
+
17
+
18
+ def debug?() @level <= 0; end
19
+ def info?() @level <= 1; end
20
+ def warn?() @level <= 2; end
21
+ def error?() @level <= 3; end
22
+ def fatal?() @level <= 4; end
23
+
24
+
25
+ def add(level, msg, progname)
26
+ return true if level < @level
27
+ msg = progname if msg.blank?
28
+ return true if msg.blank? # Don't send
29
+ #puts "Adding #{level} #{msg} #{progname}"
30
+ milliseconds = (Time.now.utc.to_f * 1000).to_i
31
+ data = { timestamp: milliseconds,
32
+ ip: @ip,
33
+ pid: Process.pid,
34
+ service: APP_NAME,
35
+ level: level,
36
+ msg: msg.kind_of?(String) ? msg : msg.inspect
37
+ }
38
+ @logger.log data
39
+ true
40
+ end
41
+
42
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :ocean do
3
+ # # Task goes here
4
+ # end
data/lib/template.rb ADDED
@@ -0,0 +1,31 @@
1
+ # Update the Gemfile
2
+ gem "ocean-rails", git: "git://github.com/OceanDev/ocean-rails.git"
3
+
4
+ # Install a .rvmrc file
5
+ run "rvm rvmrc create ruby-2.0.0-p195@rails-4.0.0"
6
+ # Select the ruby and gem bag
7
+ run "rvm use ruby-2.0.0-p195@rails-4.0.0"
8
+ # Run bundle install - we need the generators in it now
9
+ run "bundle install"
10
+
11
+ # Set up the application as a SOA service Rails application
12
+ generate "ocean_setup", app_name
13
+
14
+ # Install the required gems and package them with the app
15
+ run "bundle install"
16
+ run "bundle package --all"
17
+
18
+ # Remove the asset stuff from the application conf file
19
+ gsub_file "config/application.rb",
20
+ / # Enable the asset pipeline.+config\.assets\.version = '1\.0'\s/m, ''
21
+
22
+ # Set up SQLite to run tests in memory
23
+ gsub_file "config/database.yml",
24
+ /test:\s+adapter: sqlite3\s+database: db\/test.sqlite3/m,
25
+ 'test:
26
+ adapter: sqlite3
27
+ database: ":memory:"
28
+ verbosity: quiet'
29
+
30
+ # Get the DBs in order
31
+ rake "db:migrate"
@@ -0,0 +1,91 @@
1
+ <% if namespaced? -%>
2
+ require_dependency "<%= namespaced_file_path %>/application_controller"
3
+
4
+ <% end -%>
5
+ <% module_namespacing do -%>
6
+ class <%= controller_class_name %>Controller < ApplicationController
7
+
8
+ ocean_resource_controller required_attributes: [:lock_version, :name, :description]
9
+
10
+ respond_to :json
11
+
12
+ before_action :find_<%= singular_table_name %>, :only => [:show, :update, :destroy]
13
+
14
+
15
+ # GET <%= route_url %>
16
+ def index
17
+ expires_in 0, 's-maxage' => 30.minutes
18
+ if stale?(collection_etag(<%= class_name %>))
19
+ api_render <%= class_name %>.collection(params)
20
+ end
21
+ end
22
+
23
+
24
+ # GET <%= route_url %>/1
25
+ def show
26
+ expires_in 0, 's-maxage' => 30.minutes
27
+ if stale?(@<%= singular_table_name %>)
28
+ api_render @<%= singular_table_name %>
29
+ end
30
+ end
31
+
32
+
33
+ # POST <%= route_url %>
34
+ def create
35
+ @<%= singular_table_name %> = <%= class_name %>.new(filtered_params <%= class_name %>)
36
+ set_updater(@<%= singular_table_name %>)
37
+ if @<%= singular_table_name %>.valid?
38
+ begin
39
+ @<%= singular_table_name %>.save!
40
+ rescue ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid,
41
+ SQLite3::ConstraintException
42
+ render_api_error 422, "<%= class_name %> already exists"
43
+ return
44
+ end
45
+ api_render @<%= singular_table_name %>, new: true
46
+ else
47
+ render_validation_errors @<%= singular_table_name %>
48
+ end
49
+ end
50
+
51
+
52
+ # PUT <%= route_url %>/1
53
+ def update
54
+ if missing_attributes?
55
+ render_api_error 422, "Missing resource attributes"
56
+ return
57
+ end
58
+ begin
59
+ @<%= singular_table_name %>.assign_attributes(filtered_params <%= class_name %>)
60
+ set_updater(@<%= singular_table_name %>)
61
+ @<%= singular_table_name %>.save
62
+ rescue ActiveRecord::StaleObjectError
63
+ render_api_error 409, "Stale <%= class_name %>"
64
+ return
65
+ end
66
+ if @<%= singular_table_name %>.valid?
67
+ api_render @<%= singular_table_name %>
68
+ else
69
+ render_validation_errors(@<%= singular_table_name %>)
70
+ end
71
+ end
72
+
73
+
74
+ # DELETE <%= route_url %>/1
75
+ def destroy
76
+ @<%= orm_instance.destroy %>
77
+ render_head_204
78
+ end
79
+
80
+
81
+ private
82
+
83
+ def find_<%= singular_table_name %>
84
+ @<%= singular_table_name %> = <%= class_name %>.find_by_id params[:id]
85
+ return true if @<%= singular_table_name %>
86
+ render_api_error 404, "<%= class_name %> not found"
87
+ false
88
+ end
89
+
90
+ end
91
+ <% end -%>