module ActiveRecord module ClassMethods alias _new_without_sti_type_cast new def new(*args, &block) _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type end def base_class unless self < Base raise ActiveRecordError, "#{name} doesn't descend from ActiveRecord" end if superclass == Base || superclass.abstract_class? self else superclass.base_class end end def abstract_class? defined?(@abstract_class) && @abstract_class == true end def primary_key @primary_key_value ||= (self == base_class) ? :id : base_class.primary_key end def primary_key=(val) @primary_key_value = val.to_s end def inheritance_column return nil if @no_inheritance_column @inheritance_column_value ||= if self == base_class @inheritance_column_value || 'type' else superclass.inheritance_column.tap { |v| @no_inheritance_column = !v } end end def inheritance_column=(name) @no_inheritance_column = !name @inheritance_column_value = name end def model_name @model_name ||= ActiveModel::Name.new(self) end def __hyperstack_preprocess_attrs(attrs) if inheritance_column && self < base_class && !attrs.key?(inheritance_column) attrs = attrs.merge(inheritance_column => model_name.to_s) end dealiased_attrs = {} attrs.each { |attr, value| dealiased_attrs[_dealias_attribute(attr)] = value } end def find(id) find_by(primary_key => id) end def find_by(attrs = {}) attrs = __hyperstack_preprocess_attrs(attrs) # r = ReactiveRecord::Base.find_locally(self, attrs, new_only: true) # return r.ar_instance if r (r = __hyperstack_internal_scoped_find_by(attrs)) || return r.backing_record.sync_attributes(attrs).set_ar_instance! end def enum(*args) # when we implement schema validation we should also implement value checking end def serialize(attr, *args) ReactiveRecord::Base.serialized?[self][attr] = true end def _dealias_attribute(new) if self == base_class _attribute_aliases[new] || new else _attribute_aliases[new] ||= superclass._dealias_attribute(new) end end def _attribute_aliases @_attribute_aliases ||= {} end def alias_attribute(new_name, old_name) ['', '=', '_changed?'].each do |variant| define_method("#{new_name}#{variant}") { |*args, &block| send("#{old_name}#{variant}", *args, &block) } end _attribute_aliases[new_name] = old_name end # ignore any of these methods if they get called on the client. This list should be trimmed down to include only # methods to be called as "macros" such as :after_create, etc... SERVER_METHODS = [ :regulate_relationship, :regulate_scope, :attribute_type_decorations, :defined_enums, :_validators, :timestamped_migrations, :lock_optimistically, :lock_optimistically=, :local_stored_attributes=, :lock_optimistically?, :attribute_aliases?, :attribute_method_matchers?, :defined_enums?, :has_many_without_reactive_record_add_changed_method, :has_many_with_reactive_record_add_changed_method, :belongs_to_without_reactive_record_add_changed_method, :belongs_to_with_reactive_record_add_changed_method, :cache_timestamp_format, :composed_of_with_reactive_record_add_changed_method, :schema_format, :schema_format=, :error_on_ignored_order_or_limit, :error_on_ignored_order_or_limit=, :timestamped_migrations=, :dump_schema_after_migration, :dump_schema_after_migration=, :dump_schemas, :dump_schemas=, :warn_on_records_fetched_greater_than=, :belongs_to_required_by_default, :default_connection_handler, :connection_handler=, :default_connection_handler=, :skip_time_zone_conversion_for_attributes, :skip_time_zone_conversion_for_attributes=, :time_zone_aware_types, :time_zone_aware_types=, :protected_environments, :skip_time_zone_conversion_for_attributes?, :time_zone_aware_types?, :partial_writes, :partial_writes=, :composed_of_without_reactive_record_add_changed_method, :logger, :partial_writes?, :after_initialize, :record_timestamps, :record_timestamps=, :after_find, :after_touch, :before_save, :around_save, :belongs_to_required_by_default=, :default_connection_handler?, :before_create, :around_create, :before_update, :around_update, :after_save, :before_destroy, :around_destroy, :after_create, :after_destroy, :after_update, :_validation_callbacks, :_validation_callbacks?, :_validation_callbacks=, :_initialize_callbacks, :_initialize_callbacks?, :_initialize_callbacks=, :_find_callbacks, :_find_callbacks?, :_find_callbacks=, :_touch_callbacks, :_touch_callbacks?, :_touch_callbacks=, :_save_callbacks, :_save_callbacks?, :_save_callbacks=, :_create_callbacks, :_create_callbacks?, :_create_callbacks=, :_update_callbacks, :_update_callbacks?, :_update_callbacks=, :_destroy_callbacks, :_destroy_callbacks?, :_destroy_callbacks=, :record_timestamps?, :pre_synchromesh_scope, :pre_synchromesh_default_scope, :do_not_synchronize, :do_not_synchronize?, :logger=, :maintain_test_schema, :maintain_test_schema=, :scope, :time_zone_aware_attributes, :time_zone_aware_attributes=, :default_timezone, :default_timezone=, :_attr_readonly, :warn_on_records_fetched_greater_than, :configurations, :configurations=, :_attr_readonly?, :table_name_prefix=, :table_name_suffix=, :schema_migrations_table_name=, :internal_metadata_table_name, :internal_metadata_table_name=, :primary_key_prefix_type, :_attr_readonly=, :pluralize_table_names=, :protected_environments=, :ignored_columns=, :ignored_columns, :index_nested_attribute_errors, :index_nested_attribute_errors=, :primary_key_prefix_type=, :table_name_prefix?, :table_name_suffix?, :schema_migrations_table_name?, :internal_metadata_table_name?, :protected_environments?, :pluralize_table_names?, :ignored_columns?, :store_full_sti_class, :store_full_sti_class=, :nested_attributes_options, :nested_attributes_options=, :store_full_sti_class?, :default_scopes, :default_scope_override, :default_scopes=, :default_scope_override=, :nested_attributes_options?, :cache_timestamp_format=, :cache_timestamp_format?, :reactive_record_association_keys, :_validators=, :has_many, :belongs_to, :composed_of, :belongs_to_without_reactive_record_add_is_method, :_rollback_callbacks, :_commit_callbacks, :_before_commit_callbacks, :attribute_type_decorations=, :_commit_callbacks=, :_commit_callbacks?, :_before_commit_callbacks?, :_before_commit_callbacks=, :_rollback_callbacks=, :_before_commit_without_transaction_enrollment_callbacks?, :_before_commit_without_transaction_enrollment_callbacks=, :_commit_without_transaction_enrollment_callbacks, :_commit_without_transaction_enrollment_callbacks?, :_commit_without_transaction_enrollment_callbacks=, :_rollback_callbacks?, :_rollback_without_transaction_enrollment_callbacks?, :_rollback_without_transaction_enrollment_callbacks=, :_rollback_without_transaction_enrollment_callbacks, :_before_commit_without_transaction_enrollment_callbacks, :aggregate_reflections, :_reflections=, :aggregate_reflections=, :pluralize_table_names, :public_columns_hash, :attributes_to_define_after_schema_loads, :attributes_to_define_after_schema_loads=, :table_name_suffix, :schema_migrations_table_name, :attribute_aliases, :attribute_method_matchers, :connection_handler, :attribute_aliases=, :attribute_method_matchers=, :_validate_callbacks, :_validate_callbacks?, :_validate_callbacks=, :_validators?, :_reflections?, :aggregate_reflections?, :include_root_in_json, :_reflections, :include_root_in_json=, :include_root_in_json?, :local_stored_attributes, :default_scope, :table_name_prefix, :attributes_to_define_after_schema_loads?, :attribute_type_decorations?, :defined_enums=, :suppress, :has_secure_token, :generate_unique_secure_token, :store, :store_accessor, :_store_accessors_module, :stored_attributes, :reflect_on_aggregation, :reflect_on_all_aggregations, :_reflect_on_association, :reflect_on_all_associations, :clear_reflections_cache, :reflections, :reflect_on_association, :reflect_on_all_autosave_associations, :no_touching, :transaction, :after_commit, :after_rollback, :before_commit, :before_commit_without_transaction_enrollment, :after_create_commit, :after_update_commit, :after_destroy_commit, :after_commit_without_transaction_enrollment, :after_rollback_without_transaction_enrollment, :raise_in_transactional_callbacks, :raise_in_transactional_callbacks=, :accepts_nested_attributes_for, :has_secure_password, :has_one, :has_and_belongs_to_many, :before_validation, :after_validation, :serialize, :primary_key, :dangerous_attribute_method?, :get_primary_key, :quoted_primary_key, :define_method_attribute, :reset_primary_key, :primary_key=, :define_method_attribute=, :attribute_names, :initialize_generated_modules, :column_for_attribute, :define_attribute_methods, :undefine_attribute_methods, :instance_method_already_implemented?, :method_defined_within?, :dangerous_class_method?, :class_method_defined_within?, :attribute_method?, :has_attribute?, :generated_attribute_methods, :attribute_method_prefix, :attribute_method_suffix, :attribute_method_affix, :attribute_alias?, :attribute_alias, :define_attribute_method, :update_counters, :locking_enabled?, :locking_column, :locking_column=, :reset_locking_column, :decorate_attribute_type, :decorate_matching_attribute_types, :attribute, :define_attribute, :reset_counters, :increment_counter, :decrement_counter, :validates_absence_of, :validates_length_of, :validates_size_of, :validates_presence_of, :validates_associated, :validates_uniqueness_of, :validates_acceptance_of, :validates_confirmation_of, :validates_exclusion_of, :validates_format_of, :validates_inclusion_of, :validates_numericality_of, :define_callbacks, :normalize_callback_params, :__update_callbacks, :get_callbacks, :set_callback, :set_callbacks, :skip_callback, :reset_callbacks, :deprecated_false_terminator, :define_model_callbacks, :validate, :validators, :validates_each, :validates_with, :clear_validators!, :validators_on, :validates, :_validates_default_keys, :_parse_validates_options, :validates!, :_to_partial_path, :sanitize, :sanitize_sql, :sanitize_conditions, :quote_value, :sanitize_sql_for_conditions, :sanitize_sql_array, :sanitize_sql_for_assignment, :sanitize_sql_hash_for_assignment, :sanitize_sql_for_order, :expand_hash_conditions_for_aggregates, :sanitize_sql_like, :replace_named_bind_variables, :replace_bind_variables, :raise_if_bind_arity_mismatch, :replace_bind_variable, :quote_bound_value, :all, :default_scoped, :valid_scope_name?, :scope_attributes?, :before_remove_const, :ignore_default_scope?, :unscoped, :build_default_scope, :evaluate_default_scope, :ignore_default_scope=, :current_scope, :current_scope=, :scope_attributes, :base_class, :abstract_class?, :finder_needs_type_condition?, :sti_name, :descends_from_active_record?, :abstract_class, :compute_type, :abstract_class=, :table_name, :columns, :table_exists?, :columns_hash, :column_names, :attribute_types, :prefetch_primary_key?, :sequence_name, :quoted_table_name, :_default_attributes, :type_for_attribute, :inheritance_column, :attributes_builder, :inheritance_column=, :reset_table_name, :table_name=, :reset_column_information, :full_table_name_prefix, :full_table_name_suffix, :reset_sequence_name, :sequence_name=, :next_sequence_value, :column_defaults, :content_columns, :readonly_attributes, :attr_readonly, :create, :create!, :instantiate, :find, :type_caster, :arel_table, :find_by, :find_by!, :initialize_find_by_cache, :generated_association_methods, :arel_engine, :arel_attribute, :predicate_builder, :collection_cache_key, :relation_delegate_class, :initialize_relation_delegate_cache, :enum, :collecting_queries_for_explain, :exec_explain, :i18n_scope, :lookup_ancestors, :human_attribute_name, :references, :uniq, :maximum, :none, :exists?, :second, :limit, :order, :eager_load, :update, :delete_all, :destroy, :ids, :many?, :pluck, :third, :delete, :fourth, :fifth, :forty_two, :second_to_last, :third_to_last, :preload, :sum, :take!, :first!, :last!, :second!, :offset, :select, :fourth!, :third!, :third_to_last!, :fifth!, :where, :first_or_create, :second_to_last!, :forty_two!, :first, :having, :any?, :one?, :none?, :find_or_create_by, :from, :first_or_create!, :first_or_initialize, :except, :find_or_create_by!, :find_or_initialize_by, :includes, :destroy_all, :update_all, :or, :find_in_batches, :take, :joins, :find_each, :last, :in_batches, :reorder, :group, :left_joins, :left_outer_joins, :rewhere, :readonly, :create_with, :distinct, :unscope, :calculate, :average, :count_by_sql, :minimum, :lock, :find_by_sql, :count, :cache, :uncached, :connection, :connection_pool, :establish_connection, :connected?, :clear_cache!, :clear_reloadable_connections!, :connection_id, :connection_config, :clear_all_connections!, :remove_connection, :connection_specification_name, :connection_specification_name=, :retrieve_connection, :connection_id=, :clear_active_connections!, :sqlite3_connection, :direct_descendants, :benchmark, :model_name, :with_options, :attr_protected, :attr_accessible ] def method_missing(name, *args, &block) if args.count == 1 && name.start_with?("find_by_") && !block find_by(name.sub(/^find_by_/, '') => args[0]) elsif [].respond_to?(name) all.send(name, *args, &block) elsif name.end_with?('!') send(name.chop, *args, &block).send(:reload_from_db) rescue nil elsif !SERVER_METHODS.include?(name) raise "#{self.name}.#{name}(#{args}) (called class method missing)" end end # client side AR # Any method that can be applied to an array will be applied to the result # of all instead. # Any method ending with ! just means apply the method after forcing a reload # from the DB. def create(*args, &block) new(*args).save(&block) end def scope(name, *args) opts = _synchromesh_scope_args_check(args) scope_description = ReactiveRecord::ScopeDescription.new(self, name, opts) singleton_class.send(:define_method, name) do |*vargs| all.build_child_scope(scope_description, *name, *vargs) end end def default_scope(*args, &block) opts = _synchromesh_scope_args_check([*block, *args]) @_default_scopes ||= [] @_default_scopes << opts end def all ReactiveRecord::Base.default_scope[self] ||= begin root = ReactiveRecord::Collection .new(self, nil, nil, self, 'all') .extend(ReactiveRecord::UnscopedCollection) (@_default_scopes || [{ client: _all_filter }]).inject(root) do |scope, opts| scope.build_child_scope(ReactiveRecord::ScopeDescription.new(self, :all, opts)) end end end def _all_filter # provides a filter for the all scopes taking into account STI subclasses # note: within the lambda `self` will be the model instance defining_class_is_base_class = base_class == self defining_model_name = model_name.to_s lambda do # have to delay computation of inheritance column since it might # not be defined when class is first defined ic = self.class.inheritance_column defining_class_is_base_class || !ic || self[ic] == defining_model_name end end def unscoped ReactiveRecord::Base.unscoped[self] ||= ReactiveRecord::Collection .new(self, nil, nil, self, 'unscoped') .extend(ReactiveRecord::UnscopedCollection) end def finder_method(name) ReactiveRecord::ScopeDescription.new(self, "_#{name}", {}) [name, "#{name}!"].each do |method| singleton_class.send(:define_method, method) do |*vargs| collection = all.apply_scope("_#{method}", *vargs) collection.first end end end def abstract_class=(val) @abstract_class = val end # def scope(name, body) # singleton_class.send(:define_method, name) do | *args | # args = (args.count == 0) ? name : [name, *args] # ReactiveRecord::Base.class_scopes(self)[args] ||= ReactiveRecord::Collection.new(self, nil, nil, self, args) # end # singleton_class.send(:define_method, "#{name}=") do |collection| # ReactiveRecord::Base.class_scopes(self)[name] = collection # end # end # def all # ReactiveRecord::Base.class_scopes(self)[:all] ||= ReactiveRecord::Collection.new(self, nil, nil, self, "all") # end # # def all=(collection) # ReactiveRecord::Base.class_scopes(self)[:all] = collection # end [:belongs_to, :has_many, :has_one].each do |macro| define_method(macro) do |*args| # is this a bug in opal? saying name, scope=nil, opts={} does not work! name = args.first opts = (args.count > 1 and args.last.is_a? Hash) ? args.last : {} 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) } else define_method(name) { @backing_record.get_belongs_to(assoc, nil) } define_method("#{name}=") { |val| @backing_record.set_belongs_to(assoc, val) } end assoc end end 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) } else define_method(name) { @backing_record.get_non_ar_aggregate(name, nil) } define_method("#{name}=") { |val| @backing_record.set_non_ar_aggregate(reflection, val) } end end def column_names ReactiveRecord::Base.public_columns_hash.keys end def columns_hash ReactiveRecord::Base.public_columns_hash[name] || {} end def server_methods @server_methods ||= {} end def server_method(name, default: nil) server_methods[name] = { default: default } define_method(name) do |*args| vector = args.count.zero? ? name : [[name] + args] @backing_record.get_server_method(vector, nil) end define_method("#{name}!") do |*args| vector = args.count.zero? ? name : [[name] + args] @backing_record.get_server_method(vector, true) end end def define_attribute_methods columns_hash.each do |name, column_hash| next if name == primary_key define_method(name) { @backing_record.get_attr_value(name, nil) } define_method("#{name}!") { @backing_record.get_attr_value(name, true) } define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) } define_method("#{name}_changed?") { @backing_record.changed?(name) } define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? } end self.inheritance_column = nil if inheritance_column && !columns_hash.key?(inheritance_column) end def _react_param_conversion(param, opt = nil) param = Native(param) param = JSON.from_object(param.to_n) if param.is_a? Native::Object result = if param.is_a? self param elsif param.is_a? Hash if opt == :validate_only klass = ReactiveRecord::Base.infer_type_from_hash(self, param) klass == self || klass < self else # TODO: investigate saving .changes here and then replacing the # TODO: changes after the load is complete. In other words preserve the # TODO: changed values as changes while just updating the synced values. target = if param[primary_key] ReactiveRecord::Base.find(self, primary_key => param[primary_key]).tap do |r| r.backing_record.loaded_id = param[primary_key] end else new end associations = reflect_on_all_associations param = param.collect do |key, value| assoc = associations.detect do |association| association.association_foreign_key == key end if assoc if value [assoc.attribute, { id: [value] }] else [assoc.attribute, [nil]] end else [key, [value]] end end # TODO: verify wrapping with load_data was added so broadcasting works in 1.0.0.lap28 ReactiveRecord::Base.load_data do ReactiveRecord::ServerDataCache.load_from_json(Hash[param], target) end target.cast_to_current_sti_type end end result end end end