ocean-rails 1.14.0

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