reactive-core 0.2.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.
- data/LICENSE +20 -0
- data/Manifest +30 -0
- data/README +25 -0
- data/Rakefile +19 -0
- data/lib/reactive-core.rb +8 -0
- data/lib/reactive-core/console_app.rb +9 -0
- data/lib/reactive-core/core_ext.rb +3 -0
- data/lib/reactive-core/dispatcher.rb +63 -0
- data/lib/reactive-core/errors.rb +99 -0
- data/lib/reactive-core/ext/memoizable.rb +6 -0
- data/lib/reactive-core/ext/object.rb +14 -0
- data/lib/reactive-core/ext/object_instance.rb +9 -0
- data/lib/reactive-core/ext/ordered_options.rb +1 -0
- data/lib/reactive-core/ext/rubygems_activate_patch.rb +140 -0
- data/lib/reactive-core/gem_dependency.rb +231 -0
- data/lib/reactive-core/helper_module.rb +14 -0
- data/lib/reactive-core/initializer.rb +488 -0
- data/lib/reactive-core/meta_model.rb +92 -0
- data/lib/reactive-core/output_handler.rb +143 -0
- data/lib/reactive-core/request.rb +27 -0
- data/lib/reactive-core/response.rb +14 -0
- data/lib/reactive-core/tasks/app.rake +29 -0
- data/lib/reactive-core/tasks/gems.rake +43 -0
- data/lib/reactive-core/tasks/log.rake +9 -0
- data/lib/reactive-core/tasks/misc.rake +5 -0
- data/lib/reactive-core/tasks/reactive.rb +18 -0
- data/lib/reactive-core/updater/base.rb +24 -0
- data/lib/reactive-core/updater/cli.rb +54 -0
- data/lib/reactive-core/updater/gui.rb +13 -0
- data/lib/reactive-core/version.rb +9 -0
- metadata +109 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
module Reactive
|
2
|
+
module MetaModel
|
3
|
+
class Instance
|
4
|
+
def new_record?
|
5
|
+
end
|
6
|
+
|
7
|
+
def changed?
|
8
|
+
end
|
9
|
+
|
10
|
+
def mark_for_destruction
|
11
|
+
end
|
12
|
+
|
13
|
+
# Determine if this record is marked for destruction.
|
14
|
+
# Destruction will occur ???
|
15
|
+
def marked_for_destruction?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Determine validness of the record.
|
19
|
+
# Note that it triggers the validations
|
20
|
+
def valid?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns an array of error object. Empty array when valid.
|
24
|
+
def errors
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Column
|
29
|
+
attr_reader :name, :type, :primary
|
30
|
+
alias :primary? :primary
|
31
|
+
end
|
32
|
+
|
33
|
+
class Association
|
34
|
+
attr_reader :name, :kind, :klass, :polymorphic
|
35
|
+
alias :polymorphic? :polymorphic
|
36
|
+
|
37
|
+
# returns an array of column names that are handled by the association
|
38
|
+
# By default, foreign keys are in this array, counter cache and also _type columns for
|
39
|
+
# polymorphic assocations may be there too.
|
40
|
+
attr_reader :columns
|
41
|
+
end
|
42
|
+
|
43
|
+
class Model
|
44
|
+
# registers the passed object as an observer.
|
45
|
+
# the object may receive notification through these methods:
|
46
|
+
# (see ActiveRecord::Callbacks)
|
47
|
+
# Note that each of these methods will receive a unique argument which
|
48
|
+
# is the record that has been treated.
|
49
|
+
def add_observer(object)
|
50
|
+
end
|
51
|
+
|
52
|
+
# remove the object from the registered observers
|
53
|
+
def delete_observer(object)
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_manys
|
57
|
+
@attributes.select {|name,value| value.is_a?(Association) && value.kind == :has_many}
|
58
|
+
end
|
59
|
+
|
60
|
+
def has_ones
|
61
|
+
@attributes.select {|name,value| value.is_a?(Association) && value.kind == :has_one}
|
62
|
+
end
|
63
|
+
|
64
|
+
def belong_tos
|
65
|
+
@attributes.select {|name,value| value.is_a?(Association) && value.kind == :belongs_to}
|
66
|
+
end
|
67
|
+
|
68
|
+
def associations
|
69
|
+
@attributes.select {|name,value| value.is_a? Association}
|
70
|
+
end
|
71
|
+
|
72
|
+
def columns
|
73
|
+
@attributes.select {|name,value| value.is_a? Column}
|
74
|
+
end
|
75
|
+
|
76
|
+
def attributes
|
77
|
+
@attributes.values
|
78
|
+
end
|
79
|
+
|
80
|
+
def attribute(name)
|
81
|
+
@attributes[name.to_sym]
|
82
|
+
end
|
83
|
+
|
84
|
+
def cooked_attributes
|
85
|
+
association_columns = associations.collect {|assoc| assoc.columns}.flatten
|
86
|
+
associations + columns.reject {|col| association_columns.include?(col.name) || col.primary? }
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'reactive-core/helper_module'
|
2
|
+
|
3
|
+
module Reactive
|
4
|
+
module OutputHandler
|
5
|
+
class Error < Reactive::Error #:nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
class NoHandlerFound < Error
|
9
|
+
end
|
10
|
+
|
11
|
+
class Base
|
12
|
+
cattr_accessor :logger
|
13
|
+
|
14
|
+
@@output_handlers = {}
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def register_output_handler(format, handler)
|
18
|
+
output_handlers(format) << handler
|
19
|
+
end
|
20
|
+
|
21
|
+
def output_handlers(format)
|
22
|
+
@@output_handlers[format] ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
def output_handler(format, name)
|
26
|
+
output_handlers(format).find {|handler| handler.handler_name == name}
|
27
|
+
end
|
28
|
+
|
29
|
+
def helper(format, mod)
|
30
|
+
helper_module(format).send(:include, mod)
|
31
|
+
end
|
32
|
+
|
33
|
+
def helper_module(format)
|
34
|
+
@@helper_modules[format] ||= Module.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def process(request, response)
|
38
|
+
# a 'false' handler is used to disable output handling
|
39
|
+
if response.handler_name == false
|
40
|
+
logger.info "Handling disabled for #{request.params.inspect}"
|
41
|
+
return response.body
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
logger.info "Handling #{response.handler_name}/#{response.treatment} for format #{response.format}"
|
46
|
+
handler = pick_handler(response.format, response.handler_name)
|
47
|
+
raise NoHandlerFound, "Requested output handler #{response.handler_name} for format #{response.format} not available!" unless handler
|
48
|
+
|
49
|
+
#
|
50
|
+
handler_instance(handler).treat(request, response)
|
51
|
+
rescue Exception => exception
|
52
|
+
log_error(exception)
|
53
|
+
raise exception
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def handler_instance(handler)
|
59
|
+
#TODO: We may cache handler instances in the future (for better performance)
|
60
|
+
handler.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def pick_handler(format, name)
|
64
|
+
# We will first search in the registered handlers (plugins use this form)
|
65
|
+
if handler = output_handler(format, name)
|
66
|
+
return handler
|
67
|
+
end
|
68
|
+
|
69
|
+
# Nothing found, let's see in the application dirs
|
70
|
+
dir = Reactive.path_for(:output_handlers, format.to_s)
|
71
|
+
if name.nil?
|
72
|
+
files = Dir.entries(dir).select {|entry| File.file? entry }
|
73
|
+
name = File.basename(files.first) if files.size == 1
|
74
|
+
else
|
75
|
+
name = "#{name}_handler"
|
76
|
+
end
|
77
|
+
require "#{dir}/#{name}"
|
78
|
+
name.constantize
|
79
|
+
rescue LoadError
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# Overwrite to implement custom logging of errors. By default logs as fatal.
|
84
|
+
def log_error(exception) #:doc:
|
85
|
+
ActiveSupport::Deprecation.silence do
|
86
|
+
if exception.respond_to? :template_error
|
87
|
+
logger.fatal(exception.to_s)
|
88
|
+
else
|
89
|
+
logger.fatal(
|
90
|
+
"\n\n#{exception.class} (#{exception.message}):\n " +
|
91
|
+
clean_backtrace(exception).join("\n ") +
|
92
|
+
"\n\n"
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
@@helper_modules = {}
|
101
|
+
|
102
|
+
class ProxyModule < Module
|
103
|
+
def initialize(receiver)
|
104
|
+
@receiver = receiver
|
105
|
+
end
|
106
|
+
|
107
|
+
def include(*args)
|
108
|
+
super(*args)
|
109
|
+
@receiver.extend(*args)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
attr_reader :request, :response, :helpers
|
114
|
+
|
115
|
+
def treat(request, response)
|
116
|
+
@request, @response = request, response
|
117
|
+
setup_helpers
|
118
|
+
send(response.treatment || default_treatment)
|
119
|
+
end
|
120
|
+
|
121
|
+
def controller_name
|
122
|
+
@request.params[:controller].to_s
|
123
|
+
end
|
124
|
+
|
125
|
+
def action_name
|
126
|
+
@request.params[:action].to_s
|
127
|
+
end
|
128
|
+
|
129
|
+
def request_name
|
130
|
+
"request_#{controller_name}_#{action_name}"
|
131
|
+
end
|
132
|
+
|
133
|
+
protected
|
134
|
+
|
135
|
+
def setup_helpers
|
136
|
+
return if @helpers
|
137
|
+
@helpers = ProxyModule.new(self)
|
138
|
+
@helpers.include(self.class.helper_module(request.format))
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Reactive
|
2
|
+
class Request
|
3
|
+
attr_accessor :params
|
4
|
+
attr_reader :format
|
5
|
+
attr_accessor :treatment
|
6
|
+
|
7
|
+
def initialize(params = {})
|
8
|
+
parse_params(params.dup)
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_format
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_treatment
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
def parse_params(params)
|
21
|
+
@format = params.delete(:format) || default_format
|
22
|
+
@treatment = params.delete(:treatment) || default_treatment
|
23
|
+
@params = params.with_indifferent_access
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Reactive
|
2
|
+
class Response
|
3
|
+
attr_accessor :template, :body, :variables, :redirected_to, :handler_name, :treatment, :format
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@variables = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def clear
|
10
|
+
@body = @redirected_to = @handler_name = @treatment = @format = nil
|
11
|
+
@variables = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
namespace :app do
|
2
|
+
|
3
|
+
desc "Determine if an update is available"
|
4
|
+
task :update? do
|
5
|
+
require 'rubygems/source_info_cache'
|
6
|
+
if spec = Gem::SourceInfoCache.search("^#{APP_NAME}$").last
|
7
|
+
if spec.version > Gem.loaded_specs[APP_NAME].version
|
8
|
+
puts "New version available: #{spec.version} (Current version: #{Gem.loaded_specs[APP_NAME].version})"
|
9
|
+
else
|
10
|
+
puts "Sorry, no new version available"
|
11
|
+
end
|
12
|
+
else
|
13
|
+
puts "gem #{APP_NAME} not found on gem servers"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Update the application gem"
|
19
|
+
task :update do
|
20
|
+
# This tasks will be run on the deployed system.
|
21
|
+
# It should get the gem server name from a config file, then update the app gem with --no-dependencies
|
22
|
+
#
|
23
|
+
# WARNING: Between rake app:update and the following rake commands, the current directory has to be changed. Because we are in the app root directory and it may have been update.
|
24
|
+
# Thus we must CD to the new app root dir. Maybe, we should have a Rakefile in the root of /path/to/myapp that will delegate to the highest version.
|
25
|
+
puts `gem install #{APP_NAME} -i "#{APP_PATH}" --ignore-dependencies`
|
26
|
+
puts "Some gem dependencies may be missing, have a look with: rake gems"
|
27
|
+
#puts "Some gem dependencies are missing, have a look with: rake gems.\nYou may install them with: rake gems:install or rake gems:embed" unless Reactive::GemDependency.missing_gems.empty?
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
desc "List the gems that this reactive application depends on"
|
2
|
+
task :gems => 'gems:base' do
|
3
|
+
puts Reactive::GemDependency.report_gems_state(false)
|
4
|
+
end
|
5
|
+
|
6
|
+
namespace :gems do
|
7
|
+
task :base do
|
8
|
+
Rake::Task[:environment].invoke
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "List the gems and its dependencies that this reactive application depends on"
|
12
|
+
task :dependencies => :base do
|
13
|
+
puts Reactive::GemDependency.report_gems_state(true)
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Installs all required gems for this application."
|
17
|
+
task :install => :base do
|
18
|
+
require 'rubygems'
|
19
|
+
require 'rubygems/gem_runner'
|
20
|
+
Reactive.configuration.gems.each { |gem| puts gem.install unless gem.installed? }
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Embed the specified gem into the application gems directory."
|
24
|
+
task :embed => :base do
|
25
|
+
require 'rubygems'
|
26
|
+
require 'rubygems/gem_runner'
|
27
|
+
Reactive.configuration.gems.each do |gem|
|
28
|
+
next unless !gem.embedded? && (ENV['GEM'].blank? || ENV['GEM'] == gem.name)
|
29
|
+
puts gem.embed
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
namespace :embed do
|
34
|
+
desc "Embed the specified gems and its dependencies into the application gems directory"
|
35
|
+
task :dependencies => :embed do
|
36
|
+
require 'rubygems'
|
37
|
+
require 'rubygems/gem_runner'
|
38
|
+
Reactive.configuration.gems.inject([]) do |missing, gem|
|
39
|
+
gem.dependencies.inject(missing) {|missing, dependency| dependency.embedded? ? missing : missing << dependency}
|
40
|
+
end.uniq.each {|dependency| puts dependency.embed}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
$VERBOSE = nil
|
2
|
+
|
3
|
+
# Load reactive-core rakefile extensions
|
4
|
+
Dir["#{File.dirname(__FILE__)}/*.rake"].each { |ext| load ext }
|
5
|
+
|
6
|
+
# Load reactive-dev gem if available, the tasks will be loaded by the code below (plugins)
|
7
|
+
if spec = Gem.source_index.search(Gem::Dependency.new('reactive-dev', "= #{Reactive::VERSION::STRING}")).first
|
8
|
+
Gem.activate(spec.name, "= #{spec.version}")
|
9
|
+
Dir["#{spec.full_gem_path}/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
|
10
|
+
end
|
11
|
+
|
12
|
+
# Load any custom rakefile extensions
|
13
|
+
Dir[Reactive.path_for("lib/tasks/**/*.rake")].sort.each { |ext| load ext }
|
14
|
+
|
15
|
+
# Load reactive-core rakefile extensions and also the ones from the plugins
|
16
|
+
Reactive.configuration.gems.map(&:loaded_spec).compact.each do |spec|
|
17
|
+
Dir["#{spec.full_gem_path}/**/tasks/**/*.rake"].sort.each { |ext| load ext }
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems/source_info_cache'
|
2
|
+
|
3
|
+
module Reactive
|
4
|
+
module Updater
|
5
|
+
|
6
|
+
class Base
|
7
|
+
|
8
|
+
def current_version
|
9
|
+
Gem.loaded_specs[APP_NAME].version
|
10
|
+
end
|
11
|
+
|
12
|
+
def update_available?
|
13
|
+
(spec = Gem::SourceInfoCache.search("^#{APP_NAME}$").last) && (spec.version > current_version)
|
14
|
+
end
|
15
|
+
|
16
|
+
def last_available_version
|
17
|
+
spec = Gem::SourceInfoCache.search("^#{APP_NAME}$").last
|
18
|
+
spec && spec.version
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'reactive-core/updater/base'
|
2
|
+
|
3
|
+
module Reactive
|
4
|
+
module Updater
|
5
|
+
|
6
|
+
class Cli < Base
|
7
|
+
def initialize(args = [])
|
8
|
+
args.include?('--check') ? check_update : update
|
9
|
+
end
|
10
|
+
|
11
|
+
def check_update
|
12
|
+
if update_available?
|
13
|
+
puts "There's a new version available: #{} (Current version: #{}"
|
14
|
+
else
|
15
|
+
puts "No new version available (Current version: #{})"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def update
|
20
|
+
if update_available?
|
21
|
+
puts `gem install #{APP_NAME} -i "#{APP_PATH}" --ignore-dependencies`
|
22
|
+
exec(File.join(APP_PATH, '__FILE__')) # WARNING: TODO: How about windows? should we add .cmd suffix?
|
23
|
+
end
|
24
|
+
|
25
|
+
puts Reactive::GemDependency.report_gems_state
|
26
|
+
|
27
|
+
Reactive::GemDependency.missing_gems.each do |gem|
|
28
|
+
# gem.source = gem_source
|
29
|
+
case ask "#{gem.name} #{gem.requirement.to_s}: Install, embed or skip", "Ies"
|
30
|
+
when "i"
|
31
|
+
puts gem.install
|
32
|
+
when "e"
|
33
|
+
puts gem.embed
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def gem_source
|
39
|
+
"http://localhost:8808"
|
40
|
+
end
|
41
|
+
|
42
|
+
def ask(question, replies)
|
43
|
+
begin
|
44
|
+
print question + " (#{replies}) ?"
|
45
|
+
reply = gets.chomp.downcase
|
46
|
+
reply = replies[/[A-Z]/] || '?' if reply.empty?
|
47
|
+
end until replies.downcase.include? reply.downcase
|
48
|
+
reply
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|