# :nodoc: module Clear::Model::Relations::HasOneMacro macro __filter_relation_has_one__(self_class, relation, final, query) {% begin %} {% foreign_key = (relation[:foreign_key] || "#{method_name.stringify.underscore}_id").id relation_type = relation[:type] %} {{query}}.inner_join{ var( {{self_class}}.table, {{self_class}}.__pkey__ ) == var( {{relation_type}}.table, "{{foreign_key}}" ) } {% end %} end # :nodoc: # Write down the code for Has one relation macro generate(self_type, relation) {% begin %} {% foreign_key = (relation[:foreign_key] || "#{self_type.stringify.underscore.gsub(/::/, "_").id}_id").id method_name = relation[:name].id relation_type = relation[:type] primary = relation[:primary] nilable = relation[:nilable] %} __define_association_cache__({{method_name}}, {{relation_type}}) def self.__relation_filter_{{method_name}}__(query) query.inner_join({{self_type}}.table){ var( {{self_type}}.table, {{self_type}}.__pkey__ ) == var( {{relation_type}}.table, "{{foreign_key}}" ) } end RELATION_FILTERS["{{method_name}}"] = -> (x : Clear::SQL::SelectBuilder) { __relation_filter_{{method_name}}__(x) } # The method {{method_name}} is a `has_one` relation # to {{relation_type}} def {{method_name}} : {{ relation_type }}{{ nilable ? "?".id : "".id }} if cached = @_cached_{{method_name}} # Local cache cached elsif @cache.try &.active? "{{method_name}}" # eager load cache cache = @cache.not_nil! model = @_cached_{{method_name}} = cache.hit("{{method_name}}", self.__pkey_column__.to_sql_value, {{relation_type}} ).first? {% if !nilable %} raise Clear::SQL::RecordNotFoundError.new if model.nil? {% end %} model else # no cache: load again @_cached_{{method_name}} = {{relation_type}}.query .where{ var({{relation_type}}.table, "{{foreign_key}}") == self.__pkey__ } .{{ !nilable ? "first!".id : "first".id }} end end {% if nilable %} def {{method_name}}! : {{relation_type}} if model = self.{{method_name}}.nil? raise Clear::SQL::RecordNotFoundError.new end model end # / *! {% end %} def {{method_name}}=(model : {{ relation_type }}{{ nilable ? "?".id : "".id }}) @_cached_{{method_name}} = model save! unless persisted? model.try &.{{foreign_key}}=(self.__pkey__) model end class Collection def with_{{method_name}}(fetch_columns = false, &block : {{relation_type}}::Collection -> ) : self before_query do %foreign_key = {{"#{foreign_key}"}} %key = {{self_type}}.__pkey__ %table = {{self_type}}.table #SELECT * FROM foreign WHERE foreign_key IN ( SELECT primary_key FROM users ) sub_query = self.dup.clear_select.select("#{%table}.#{%key}") qry = {{relation_type}}.query.where{ var({{relation_type}}.table, %foreign_key).in?(sub_query) } block.call(qry) @cache.active "{{method_name}}" qry.each(fetch_columns: fetch_columns) do |mdl| @cache.set("{{method_name}}", mdl.{{foreign_key}}, [mdl]) end end self end # / with_* def with_{{method_name}}(fetch_columns = false) : self with_{{method_name}}(fetch_columns){} self end # / with_* end # / Collection {% end %} # / begin block end # / macro # # Return the related model `{{method_name}}`. # # # # This relation is of type one to zero or one [1, 0..1] # # between {{relation_type}} and {{self_type}} # # # # If the relation hasn't been cached, will call a `select` SQL operation. # # Otherwise, will try to find in the cache. # def {{method_name}} : {{relation_type}}? # %primary_key = {{(primary_key || "__pkey__").id}} # %foreign_key = {{foreign_key}} || ( self.class.table.to_s.singularize + "_id" ) # {{relation_type}}.query.where{ raw(%foreign_key) == %primary_key }.first # end # # Return the related model `{{method_name}}`, # # but throw an error if the model is not found. # def {{method_name}}! : {{relation_type}} # {{method_name}}.not_nil! # end end