reactive-record 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +29 -0
- data/app/controllers/reactive_record/application_controller.rb +4 -0
- data/app/controllers/reactive_record/reactive_record_controller.rb +22 -0
- data/config/routes.rb +5 -0
- data/lib/Gemfile +17 -0
- data/lib/reactive-record.rb +28 -0
- data/lib/reactive_record/active_record/aggregations.rb +38 -0
- data/lib/reactive_record/active_record/associations.rb +54 -0
- data/lib/reactive_record/active_record/base.rb +9 -0
- data/lib/reactive_record/active_record/class_methods.rb +113 -0
- data/lib/reactive_record/active_record/instance_methods.rb +76 -0
- data/lib/reactive_record/active_record/reactive_record/base.rb +287 -0
- data/lib/reactive_record/active_record/reactive_record/collection.rb +100 -0
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +274 -0
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +262 -0
- data/lib/reactive_record/engine.rb +13 -0
- data/lib/reactive_record/interval.rb +190 -0
- data/lib/reactive_record/serializers.rb +7 -0
- data/lib/reactive_record/server_data_cache.rb +250 -0
- data/lib/reactive_record/version.rb +3 -0
- metadata +191 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c14856b73501aab70804ad8351301b87b948fd75
|
4
|
+
data.tar.gz: b0aef64c154ca72e6af956a1966e0e77235bafec
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bcc0cdea12ce7c93e27b6179ed6c2842207cf7e046bef8da8eb46643a2adf6964fad1e6d4d0f3c75853bf661de19142f28c987352b4f16abe03fbacdab8464a2
|
7
|
+
data.tar.gz: f861152c0a4ca7fda801ccf5ee5b8603a7c50a40c8d19506d9c90c71d68c732b99e092da468aa951577341b9f2056c0a89c85d102fdfd192824b814ec92fc6ad
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
8
|
+
load 'rails/tasks/engine.rake'
|
9
|
+
Bundler::GemHelper.install_tasks
|
10
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
11
|
+
require 'rspec/core'
|
12
|
+
require 'rspec/core/rake_task'
|
13
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
14
|
+
RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
15
|
+
|
16
|
+
require 'opal/rspec/rake_task'
|
17
|
+
require 'bundler'
|
18
|
+
Bundler.require
|
19
|
+
|
20
|
+
# Add our opal/ directory to the load path
|
21
|
+
#Opal.append_path File.expand_path('../lib', __FILE__)
|
22
|
+
|
23
|
+
Opal::RSpec::RakeTask.new(:spec_opal) do |s|
|
24
|
+
s.sprockets.paths.tap { s.sprockets.clear_paths }[0..-2].each { |path| s.sprockets.append_path path}
|
25
|
+
s.main = 'sprockets_runner'
|
26
|
+
s.append_path 'spec-opal'
|
27
|
+
end
|
28
|
+
|
29
|
+
task :default => [:spec, :spec_opal]
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'reactive_record/server_data_cache'
|
2
|
+
|
3
|
+
module ReactiveRecord
|
4
|
+
|
5
|
+
class ReactiveRecordController < ApplicationController
|
6
|
+
|
7
|
+
def fetch
|
8
|
+
render :json => ReactiveRecord::ServerDataCache[params[:pending_fetches]]
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def save
|
13
|
+
render :json => ReactiveRecord::Base.save_records(params[:models], params[:associations])
|
14
|
+
end
|
15
|
+
|
16
|
+
def destroy
|
17
|
+
render :json => ReactiveRecord::Base.destroy_record(params[:model], params[:id], params[:vector])
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/config/routes.rb
ADDED
data/lib/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Declare your gem's dependencies in reactive_record.gemspec.
|
4
|
+
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
+
# development dependencies will be added by default to the :development group.
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
# jquery-rails is used by the dummy application
|
9
|
+
gem "jquery-rails"
|
10
|
+
|
11
|
+
# Declare any dependencies that are still in development here instead of in
|
12
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
13
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
14
|
+
# your gem to rubygems.org.
|
15
|
+
|
16
|
+
# To use debugger
|
17
|
+
# gem 'debugger'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
if RUBY_ENGINE == 'opal'
|
2
|
+
|
3
|
+
require "opal-react"
|
4
|
+
require "reactive_record/server_data_cache"
|
5
|
+
require "reactive_record/active_record/reactive_record/while_loading"
|
6
|
+
require "reactive_record/active_record/reactive_record/isomorphic_base"
|
7
|
+
require "reactive_record/active_record/aggregations"
|
8
|
+
require "reactive_record/active_record/associations"
|
9
|
+
require "reactive_record/active_record/reactive_record/base"
|
10
|
+
require "reactive_record/active_record/reactive_record/collection"
|
11
|
+
require "reactive_record/active_record/class_methods"
|
12
|
+
require "reactive_record/active_record/instance_methods"
|
13
|
+
require "reactive_record/active_record/base"
|
14
|
+
require "reactive_record/interval"
|
15
|
+
|
16
|
+
else
|
17
|
+
|
18
|
+
require "opal"
|
19
|
+
require "reactive_record/version"
|
20
|
+
require "reactive_record/engine"
|
21
|
+
require "reactive_record/server_data_cache"
|
22
|
+
require "reactive_record/active_record/reactive_record/isomorphic_base"
|
23
|
+
require "reactive_record/serializers"
|
24
|
+
|
25
|
+
Opal.append_path File.expand_path('../', __FILE__).untaint
|
26
|
+
Opal.append_path File.expand_path('../../vendor', __FILE__).untaint
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def self.reflect_on_all_aggregations
|
6
|
+
base_class.instance_eval { @aggregations ||= [] }
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.reflect_on_aggregation(attribute)
|
10
|
+
reflect_on_all_aggregations.detect { |aggregation| aggregation.attribute == attribute }
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
module Aggregations
|
16
|
+
|
17
|
+
class AggregationReflection
|
18
|
+
|
19
|
+
attr_reader :klass_name
|
20
|
+
attr_reader :attribute
|
21
|
+
|
22
|
+
def initialize(owner_class, macro, name, options = {})
|
23
|
+
owner_class.reflect_on_all_aggregations << self
|
24
|
+
@owner_class = owner_class
|
25
|
+
@klass_name = options[:class_name] || name.camelize
|
26
|
+
@attribute = name
|
27
|
+
end
|
28
|
+
|
29
|
+
def klass
|
30
|
+
@klass ||= Object.const_get(@klass_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def self.reflect_on_all_associations
|
6
|
+
base_class.instance_eval { @associations ||= superclass.instance_eval { (@associations && @associations.dup) || [] } }
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.reflect_on_association(attribute)
|
10
|
+
reflect_on_all_associations.detect { |association| association.attribute == attribute }
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
module Associations
|
16
|
+
|
17
|
+
class AssociationReflection
|
18
|
+
|
19
|
+
attr_reader :association_foreign_key
|
20
|
+
attr_reader :attribute
|
21
|
+
attr_reader :macro
|
22
|
+
|
23
|
+
def initialize(owner_class, macro, name, options = {})
|
24
|
+
owner_class.reflect_on_all_associations << self
|
25
|
+
@owner_class = owner_class
|
26
|
+
@macro = macro
|
27
|
+
@klass_name = options[:class_name] || (collection? && name.camelize.gsub(/s$/,"")) || name.camelize
|
28
|
+
@association_foreign_key = options[:foreign_key] || (macro == :belongs_to && "#{name}_id") || "#{@owner_class.name.underscore}_id"
|
29
|
+
@attribute = name
|
30
|
+
end
|
31
|
+
|
32
|
+
def inverse_of
|
33
|
+
unless @inverse_of
|
34
|
+
inverse_association = klass.reflect_on_all_associations.detect { | association | association.association_foreign_key == @association_foreign_key }
|
35
|
+
raise "Association #{@owner_class}.#{attribute} (foreign_key: #{@association_foreign_key}) has no inverse in #{@klass_name}" unless inverse_association
|
36
|
+
@inverse_of = inverse_association.attribute
|
37
|
+
end
|
38
|
+
@inverse_of
|
39
|
+
end
|
40
|
+
|
41
|
+
def klass
|
42
|
+
@klass ||= Object.const_get(@klass_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def collection?
|
46
|
+
[:has_many].include? @macro
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
def base_class
|
6
|
+
|
7
|
+
unless self < Base
|
8
|
+
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
|
9
|
+
end
|
10
|
+
|
11
|
+
if superclass == Base || superclass.abstract_class?
|
12
|
+
self
|
13
|
+
else
|
14
|
+
superclass.base_class
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def abstract_class?
|
20
|
+
defined?(@abstract_class) && @abstract_class == true
|
21
|
+
end
|
22
|
+
|
23
|
+
def primary_key
|
24
|
+
base_class.instance_eval { @primary_key_value || :id }
|
25
|
+
end
|
26
|
+
|
27
|
+
def primary_key=(val)
|
28
|
+
base_class.instance_eval { @primary_key_value = val }
|
29
|
+
end
|
30
|
+
|
31
|
+
def inheritance_column
|
32
|
+
base_class.instance_eval {@inheritance_column_value || "type"}
|
33
|
+
end
|
34
|
+
|
35
|
+
def inheritance_column=(name)
|
36
|
+
base_class.instance_eval {@inheritance_column_value = name}
|
37
|
+
end
|
38
|
+
|
39
|
+
def model_name
|
40
|
+
# in reality should return ActiveModel::Name object, blah blah
|
41
|
+
name
|
42
|
+
end
|
43
|
+
|
44
|
+
def find(id)
|
45
|
+
base_class.instance_eval {ReactiveRecord::Base.find(self, primary_key, id)}
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_by(opts = {})
|
49
|
+
base_class.instance_eval {ReactiveRecord::Base.find(self, opts.first.first, opts.first.last)}
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_missing(name, *args, &block)
|
53
|
+
if args.count == 1 && name =~ /^find_by_/ && !block
|
54
|
+
find_by(name.gsub(/^find_by_/, "") => args[0])
|
55
|
+
else
|
56
|
+
raise "#{self.name}.#{name}(#{args}) (called class method missing)"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def abstract_class=(val)
|
61
|
+
@abstract_class = val
|
62
|
+
end
|
63
|
+
|
64
|
+
def scope(name, body)
|
65
|
+
singleton_class.send(:define_method, name) { ReactiveRecord::Collection.new(self, nil, nil, self, name) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def all
|
69
|
+
ReactiveRecord::Collection.new(self)
|
70
|
+
end
|
71
|
+
|
72
|
+
[:belongs_to, :has_many, :has_one].each do |macro|
|
73
|
+
define_method(macro) do |name, opts = {}|
|
74
|
+
Associations::AssociationReflection.new(base_class, macro, name, opts)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def composed_of(name, opts = {})
|
79
|
+
Aggregations::AggregationReflection.new(base_class, :composed_of, name, opts)
|
80
|
+
end
|
81
|
+
|
82
|
+
[
|
83
|
+
"table_name=", "before_validation", "with_options", "validates_presence_of", "validates_format_of",
|
84
|
+
"accepts_nested_attributes_for", "after_create", "before_save", "before_destroy", "where", "validate",
|
85
|
+
"attr_protected", "validates_numericality_of", "default_scope", "has_attached_file", "attr_accessible",
|
86
|
+
"serialize"
|
87
|
+
].each do |method|
|
88
|
+
define_method(method.to_s) { |*args, &block| }
|
89
|
+
end
|
90
|
+
|
91
|
+
def _react_param_conversion(param, opt = nil)
|
92
|
+
# defines how react will convert incoming json to this ActiveRecord model
|
93
|
+
param_is_native = !param.respond_to?(:is_a?) rescue true
|
94
|
+
param = JSON.from_object param if param_is_native
|
95
|
+
if param.is_a? self
|
96
|
+
param
|
97
|
+
elsif param.is_a? Hash
|
98
|
+
if opt == :validate_only
|
99
|
+
ReactiveRecord::Base.infer_type_from_hash(self, param) == self
|
100
|
+
else
|
101
|
+
target = find(param[primary_key])
|
102
|
+
param.each { |key, value| param[key] = [value] }
|
103
|
+
ReactiveRecord::Base.load_from_json(param, target)
|
104
|
+
target
|
105
|
+
end
|
106
|
+
else
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
|
3
|
+
module InstanceMethods
|
4
|
+
|
5
|
+
def attributes
|
6
|
+
@backing_record.attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(hash = {})
|
10
|
+
if hash.is_a? ReactiveRecord::Base
|
11
|
+
@backing_record = hash
|
12
|
+
else
|
13
|
+
# standard active_record new -> creates a new instance, primary key is ignored if present
|
14
|
+
hash[primary_key] = nil
|
15
|
+
@backing_record = ReactiveRecord::Base.new(self.class, hash, self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def primary_key
|
20
|
+
self.class.primary_key
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
@backing_record.reactive_get!(primary_key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def id=(value)
|
28
|
+
@backing_record.id = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def model_name
|
32
|
+
# in reality should return ActiveModel::Name object, blah blah
|
33
|
+
self.class.model_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def revert
|
37
|
+
@backing_record.revert
|
38
|
+
end
|
39
|
+
|
40
|
+
def changed?
|
41
|
+
@backing_record.changed?
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(ar_instance)
|
45
|
+
@backing_record == ar_instance.instance_eval { @backing_record }
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(name, *args, &block)
|
49
|
+
if name =~ /_changed\?$/
|
50
|
+
@backing_record.changed?(name.gsub(/_changed\?$/,""))
|
51
|
+
elsif args.count == 1 && name =~ /=$/ && !block
|
52
|
+
attribute_name = name.gsub(/=$/,"")
|
53
|
+
@backing_record.reactive_set!(attribute_name, args[0])
|
54
|
+
elsif args.count == 0 && !block
|
55
|
+
@backing_record.reactive_get!(name)
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def save(&block)
|
62
|
+
@backing_record.save &block
|
63
|
+
end
|
64
|
+
|
65
|
+
def saving?
|
66
|
+
@backing_record.saving?
|
67
|
+
end
|
68
|
+
|
69
|
+
def destroy(&block)
|
70
|
+
@backing_record.destroy &block
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|