Skip to content

Commit

Permalink
Merge branch 'edge' into issue-275
Browse files Browse the repository at this point in the history
  • Loading branch information
adamcreekroad committed Nov 19, 2019
2 parents 47ee071 + c589af1 commit edc7916
Show file tree
Hide file tree
Showing 16 changed files with 285 additions and 95 deletions.
2 changes: 1 addition & 1 deletion docs/dsl-isomorphic/hyper-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ For complete examples with *push* updates, see any of the apps in the `examples`

Depending on the architecture of your application, you may decide that some of your models should be Isomorphic and some should remain server-only. The consideration will be that your Isomorphic models will be compiled by Opal to JavaScript and accessible on he client (without the need for a boilerplate API) - Hyperstack takes care of the communication between your server-side models and their client-side compiled versions and you can use Policy to govern access to the models.

In order for Hyperstack to see your Models (and his make them Isomorphic) you need to move them to the `hyperstack/models` folder. Only models in this folder will be seen by Hyperstack and compiled to Javascript. Once a Model is on this folder it ill be accessable to both your client and server code.
In order for Hyperstack to see your Models (and make them Isomorphic) you need to move them to the `hyperstack/models` folder. Only models in this folder will be seen by Hyperstack and compiled to Javascript. Once a Model is on this folder it ill be accessable to both your client and server code.

| **Location of Models** | **Scope** |
| ------------------------- |---------------|
Expand Down
4 changes: 2 additions & 2 deletions ruby/hyper-component/hyper-component.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ Gem::Specification.new do |spec|

spec.add_dependency 'hyper-state', Hyperstack::Component::VERSION
spec.add_dependency 'hyperstack-config', Hyperstack::Component::VERSION
spec.add_dependency 'libv8', '~> 6.7.0'
spec.add_dependency 'mini_racer', '~> 0.2.4'
spec.add_dependency 'libv8', '~> 7.3.492.27.1'
spec.add_dependency 'mini_racer', '~> 0.2.6'
spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
spec.add_dependency 'opal-activesupport', '~> 0.3.1'
spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
Expand Down
4 changes: 2 additions & 2 deletions ruby/hyper-model/hyper-model.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1']
spec.add_development_dependency 'capybara'
spec.add_development_dependency 'chromedriver-helper', '1.2.0'
spec.add_development_dependency 'libv8'
spec.add_development_dependency 'mini_racer', '~> 0.2.4'
spec.add_development_dependency 'libv8', '~> 7.3.492.27.1'
spec.add_development_dependency 'mini_racer', '~> 0.2.6'
spec.add_development_dependency 'selenium-webdriver'
spec.add_development_dependency 'database_cleaner'
spec.add_development_dependency 'factory_bot_rails'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def self.reflect_on_association(attr)
end

def self.reflect_on_association_by_foreign_key(key)
reflection_finder { |assoc| assoc.association_foreign_key == key }
reflection_finder { |assoc| assoc.association_foreign_key == key && assoc.macro != :has_many }
end

def self.reflection_finder(&block)
Expand Down Expand Up @@ -161,6 +161,7 @@ def inverse_of(model = nil)
def find_inverse(model) # private
the_klass = klass(model)
the_klass.reflect_on_all_associations.each do |association|
next if association == self
next if association.association_foreign_key != @association_foreign_key
next if association.attribute == attribute
return association if association.polymorphic? || association.klass == owner_class
Expand Down
47 changes: 25 additions & 22 deletions ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def __hyperstack_preprocess_attrs(attrs)
end
dealiased_attrs = {}
attrs.each { |attr, value| dealiased_attrs[_dealias_attribute(attr)] = value }
dealiased_attrs
end

def find(*args)
Expand Down Expand Up @@ -297,11 +298,12 @@ def abstract_class=(val)
assoc = Associations::AssociationReflection.new(self, macro, name, opts)
if macro == :has_many
define_method(name) { @backing_record.get_has_many(assoc, nil) }
define_method("#{name}=") { |val| @backing_record.set_has_many(assoc, val) }
define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_has_many(assoc, val) }
else
define_method(name) { @backing_record.get_belongs_to(assoc, nil) }
define_method("#{name}=") { |val| @backing_record.set_belongs_to(assoc, val) }
define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_belongs_to(assoc, val) }
end
alias_method "#{name}=", "_hyperstack_internal_setter_#{name}"
assoc
end
end
Expand All @@ -310,11 +312,12 @@ def composed_of(name, opts = {})
reflection = Aggregations::AggregationReflection.new(base_class, :composed_of, name, opts)
if reflection.klass < ActiveRecord::Base
define_method(name) { @backing_record.get_ar_aggregate(reflection, nil) }
define_method("#{name}=") { |val| @backing_record.set_ar_aggregate(reflection, val) }
define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_ar_aggregate(reflection, val) }
else
define_method(name) { @backing_record.get_non_ar_aggregate(name, nil) }
define_method("#{name}=") { |val| @backing_record.set_non_ar_aggregate(reflection, val) }
define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_non_ar_aggregate(reflection, val) }
end
alias_method "#{name}=", "_hyperstack_internal_setter_#{name}"
end

def column_names
Expand All @@ -339,22 +342,16 @@ def server_method(name, default: nil)
vector = args.count.zero? ? name : [[name] + args]
@backing_record.get_server_method(vector, true)
end
define_method("_hyperstack_internal_setter_#{name}") do |val|
backing_record.set_attr_value(name, val)
end
end

# def define_attribute_methods
# columns_hash.each do |name, column_hash|
# next if name == primary_key
# column_hash[:serialized?] = true if ReactiveRecord::Base.serialized?[self][name]
#
# define_method(name) { @backing_record.get_attr_value(name, nil) } unless method_defined?(name)
# define_method("#{name}!") { @backing_record.get_attr_value(name, true) } unless method_defined?("#{name}!")
# define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) } unless method_defined?("#{name}=")
# define_method("#{name}_changed?") { @backing_record.changed?(name) } unless method_defined?("#{name}_changed?")
# define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? } unless method_defined?("#{name}?")
# end
# self.inheritance_column = nil if inheritance_column && !columns_hash.key?(inheritance_column)
# end

# define all the methods for each column. To allow overriding the methods they will NOT
# be defined if already defined (i.e. by the model) See the instance_methods module for how
# super calls are handled in this case. The _hyperstack_internal_setter_... methods
# are used by the load_from_json method when bringing in data from the server, and so therefore
# does not want to be overriden.

def define_attribute_methods
columns_hash.each do |name, column_hash|
Expand All @@ -363,10 +360,11 @@ def define_attribute_methods
# easier by keeping the columns_hash the same if there are no seralized strings
# see rspec ./spec/batch1/column_types/column_type_spec.rb:100
column_hash[:serialized?] = true if ReactiveRecord::Base.serialized?[self][name]

define_method(name) { @backing_record.get_attr_value(name, nil) } unless method_defined?(name)
define_method("#{name}!") { @backing_record.get_attr_value(name, true) } unless method_defined?("#{name}!")
define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) } unless method_defined?("#{name}=")
define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_attr_value(name, val) }
alias_method "#{name}=", "_hyperstack_internal_setter_#{name}" unless method_defined?("#{name}=")
define_method("#{name}_changed?") { @backing_record.changed?(name) } unless method_defined?("#{name}_changed?")
define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? } unless method_defined?("#{name}?")
end
Expand Down Expand Up @@ -399,18 +397,23 @@ def _react_param_conversion(param, opt = nil)
associations = reflect_on_all_associations

already_processed_keys = Set.new
old_param = param.dup

param = param.collect do |key, value|
next if already_processed_keys.include? key

model_name = model_id = nil

# polymorphic association is where the belongs_to side holds the
# id, and the type of the model the id points to

# belongs_to :duplicate_of, class_name: 'Report', required: false
# has_many :duplicates, class_name: 'Report', foreign_key: 'duplicate_of_id'

assoc = associations.detect do |poly_assoc|
if key == poly_assoc.polymorphic_type_attribute
model_name = value
already_processed_keys << poly_assoc.association_foreign_key
elsif key == poly_assoc.association_foreign_key
elsif key == poly_assoc.association_foreign_key && (poly_assoc.polymorphic_type_attribute || poly_assoc.macro == :belongs_to)
model_id = value
already_processed_keys << poly_assoc.polymorphic_type_attribute
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
module ActiveRecord
module InstanceMethods

# if methods are missing, then they must be a column, which we look up
# in the columns_hash.

# For effeciency all attributes will by default have all the methods defined,
# when the class is loaded. See define_attribute_methods class method.
# However a model may override the attribute methods definition, but then call
# super. Which will result in the method missing call.

# When loading data from the server we do NOT want to call overridden methods
# so we also define a _hyperstack_internal_setter_... method for each attribute
# as well as for belongs_to relationships, server_methods, and the special
# type and model_name methods. See the ClassMethods module for details.

def method_missing(missing, *args, &block)
column = self.class.columns_hash.detect { |name, *| missing =~ /^#{name}/ }
if column
Expand All @@ -17,6 +30,19 @@ def method_missing(missing, *args, &block)
end
end

# ignore load_from_json when it calls _hyperstack_internal_setter_id
def _hyperstack_internal_setter_id(*); end

# the system assumes that there is "virtual" model_name and type attribute so
# we define the internal setter here. If the user defines some other attributes
# or uses these names no harm is done since the exact same method would have been
# defined by the define_attribute_methods class method anyway.
%i[model_name type].each do |attr|
define_method("_hyperstack_internal_setter_#{attr}") do |val|
@backing_record.set_attr_value(:model_name, val)
end
end

def inspect
"<#{model_name}:#{ReactiveRecord::Operations::Base::FORMAT % to_key} "\
"(#{ReactiveRecord::Operations::Base::FORMAT % object_id}) "\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,8 +558,8 @@ def internal_replace(new_array)
end

def delete(item)
unsaved_children.delete(item)
notify_of_change(
Hyperstack::Internal::State::Mapper.bulk_update do
unsaved_children.delete(item)
if @owner && @association
inverse_of = @association.inverse_of
if (backing_record = item.backing_record) && item.attributes[inverse_of] == @owner && !@association.through_association?
Expand All @@ -569,8 +569,8 @@ def delete(item)
delete_internal(item) { @owner.backing_record.sync_has_many(@association.attribute) }
else
delete_internal(item)
end
)
end.tap { Hyperstack::Internal::State::Variable.set(self, :collection, collection) }
end
end

def delete_internal(item)
Expand Down Expand Up @@ -618,8 +618,22 @@ def empty?
count.zero?
end

def any?
!count.zero?
def any?(*args, &block)
# If there are any args passed in, then the collection is being used in the condition
# and we must load it all into memory.
return all.any?(*args, &block) if args&.length&.positive? || block.present?

# Otherwise we can just check the count for efficiency
!empty?
end

def none?(*args, &block)
# If there are any args passed in, then the collection is being used in the condition
# and we must load it all into memory.
return all.none?(*args, &block) if args&.length&.positive? || block.present?

# Otherwise we can just check the count for efficiency
empty?
end

def method_missing(method, *args, &block)
Expand Down
Loading

0 comments on commit edc7916

Please sign in to comment.