Class: Keinaufwand::Sync::Record
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Keinaufwand::Sync::Record
- Defined in:
- lib/keinaufwand/sync_core/record.rb
Class Method Summary collapse
- .apply_json_conditions(relation, conditions) ⇒ Object
- .boot_keinaufwand_dynamics! ⇒ Object
- .configure_keinaufwand_schema!(schema) ⇒ Object
- .define_dynamic_association(association, config) ⇒ Object
- .define_dynamic_attribute(attribute) ⇒ Object
- .define_dynamic_foreign_key(association, type) ⇒ Object
- .define_dynamic_ids(association) ⇒ Object
- .exists?(conditions = :none) ⇒ Boolean
- .find(*ids) ⇒ Object
- .inherited(subclass) ⇒ Object
- .json_attribute?(attribute) ⇒ Boolean
- .json_bind_value(value) ⇒ Object
- .json_condition_binds(value) ⇒ Object
- .json_condition_sql(attribute, value) ⇒ Object
- .json_hash_conditions?(conditions) ⇒ Boolean
- .json_order_argument?(argument) ⇒ Boolean
- .json_order_sql(argument) ⇒ Object
- .json_value(attribute) ⇒ Object
- .json_value_expression(column, attribute) ⇒ Object
- .load_keinaufwand_schema ⇒ Object
- .model_schema ⇒ Object
- .postgres_cast(expression, attribute) ⇒ Object
- .schema ⇒ Object
- .schema_associations ⇒ Object
- .schema_attribute?(attribute) ⇒ Boolean
- .schema_attribute_type(attribute) ⇒ Object
- .schema_attributes ⇒ Object
- .split_json_conditions(conditions) ⇒ Object
- .sqlite_cast(expression, attribute) ⇒ Object
- .where_present(*attributes) ⇒ Object
Instance Method Summary collapse
- #asset_attribute?(attribute, value) ⇒ Boolean
- #build_inline_keinaufwand_record(attributes, target_class) ⇒ Object
- #build_inline_keinaufwand_records(value, target_class) ⇒ Object
- #cast_keinaufwand_value(attribute, value) ⇒ Object
- #derivative_value(value, size) ⇒ Object
- #id ⇒ Object
- #id=(value) ⇒ Object
- #load_keinaufwand_association(association, type, target_class, config) ⇒ Object
- #parse_keinaufwand_date(value) ⇒ Object
- #parse_keinaufwand_time(value) ⇒ Object
- #primary_key_values_present? ⇒ Boolean
- #read_keinaufwand_value(attribute) ⇒ Object
- #remember_transaction_record_state ⇒ Object
- #write_keinaufwand_column(attribute, value) ⇒ Object
Class Method Details
.apply_json_conditions(relation, conditions) ⇒ Object
196 197 198 199 200 |
# File 'lib/keinaufwand/sync_core/record.rb', line 196 def self.apply_json_conditions(relation, conditions) conditions.reduce(relation) do |scope, (attribute, value)| scope.where(json_condition_sql(attribute, value), *json_condition_binds(value)) end end |
.boot_keinaufwand_dynamics! ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/keinaufwand/sync_core/record.rb', line 95 def self.boot_keinaufwand_dynamics! return if abstract_class? || name.blank? || name == "Keinaufwand::Record" schema_attributes.each_key do |attribute| define_dynamic_attribute(attribute) end schema_associations.each do |association, config| define_dynamic_association(association, config) end rescue Errno::ENOENT nil end |
.configure_keinaufwand_schema!(schema) ⇒ Object
10 11 12 13 |
# File 'lib/keinaufwand/sync_core/record.rb', line 10 def self.configure_keinaufwand_schema!(schema) self.keinaufwand_schema = schema descendants.each(&:boot_keinaufwand_dynamics!) if schema && respond_to?(:descendants) end |
.define_dynamic_association(association, config) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/keinaufwand/sync_core/record.rb', line 129 def self.define_dynamic_association(association, config) association = association.to_s config = config.with_indifferent_access type = config[:type].to_s target_class_name = config[:class_name] || association.singularize.classify define_dynamic_foreign_key(association, type) if %w[belongs_to has_one].include?(type) define_dynamic_ids(association) if type == "has_and_belongs_to_many" define_method(association) do return instance_variable_get("@#{association}") if instance_variable_defined?("@#{association}") target_class = target_class_name.safe_constantize result = target_class ? load_keinaufwand_association(association, type, target_class, config) : nil instance_variable_set("@#{association}", result) end define_method("#{association}=") do |value| instance_variable_set("@#{association}", value) end end |
.define_dynamic_attribute(attribute) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/keinaufwand/sync_core/record.rb', line 109 def self.define_dynamic_attribute(attribute) attribute = attribute.to_s return if attribute == "id" define_method(attribute) do |variant = :large| value = read_keinaufwand_value(attribute) if asset_attribute?(attribute, value) value = derivative_value(value, variant) value = local_asset_url(value) if respond_to?(:local_asset_url) end value = localize_asset_urls(value) if attribute == "body" && respond_to?(:localize_asset_urls) cast_keinaufwand_value(attribute, value) end define_method("#{attribute}?") do value = public_send(attribute) value == true || value.present? end end |
.define_dynamic_foreign_key(association, type) ⇒ Object
151 152 153 154 155 156 157 158 |
# File 'lib/keinaufwand/sync_core/record.rb', line 151 def self.define_dynamic_foreign_key(association, type) key = "#{association}_id" return if schema_attribute?(key) define_method(key) do read_keinaufwand_value(key) end end |
.define_dynamic_ids(association) ⇒ Object
160 161 162 163 164 165 166 167 |
# File 'lib/keinaufwand/sync_core/record.rb', line 160 def self.define_dynamic_ids(association) key = "#{association.singularize}_ids" return if schema_attribute?(key) define_method(key) do Array(read_keinaufwand_value(key)) end end |
.exists?(conditions = :none) ⇒ Boolean
31 32 33 34 35 36 37 |
# File 'lib/keinaufwand/sync_core/record.rb', line 31 def self.exists?(conditions = :none) if composite_primary_key? && conditions != :none && !conditions.is_a?(Hash) && !conditions.is_a?(Array) return super(id: conditions) end super end |
.find(*ids) ⇒ Object
22 23 24 25 26 27 28 29 |
# File 'lib/keinaufwand/sync_core/record.rb', line 22 def self.find(*ids) return super if ids.empty? ids = ids.flatten return where(id: ids).index_by(&:id).values_at(*ids.map(&:to_i)) if ids.many? find_by!(id: ids.first) end |
.inherited(subclass) ⇒ Object
90 91 92 93 |
# File 'lib/keinaufwand/sync_core/record.rb', line 90 def self.inherited(subclass) super subclass.boot_keinaufwand_dynamics! end |
.json_attribute?(attribute) ⇒ Boolean
55 56 57 |
# File 'lib/keinaufwand/sync_core/record.rb', line 55 def self.json_attribute?(attribute) !column_names.include?(attribute.to_s) && schema_attribute?(attribute) end |
.json_bind_value(value) ⇒ Object
265 266 267 268 269 270 271 272 273 274 |
# File 'lib/keinaufwand/sync_core/record.rb', line 265 def self.json_bind_value(value) case value when Time, ActiveSupport::TimeWithZone value.iso8601 when Date value.iso8601 else value end end |
.json_condition_binds(value) ⇒ Object
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/keinaufwand/sync_core/record.rb', line 247 def self.json_condition_binds(value) case value when Array return [] if value.empty? [value.compact.map { |item| json_bind_value(item) }] when Range return [json_bind_value(value.begin)] if value.end.nil? return [json_bind_value(value.end)] if value.begin.nil? [json_bind_value(value.begin), json_bind_value(value.end)] when nil [] else [json_bind_value(value)] end end |
.json_condition_sql(attribute, value) ⇒ Object
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/keinaufwand/sync_core/record.rb', line 225 def self.json_condition_sql(attribute, value) expression = json_value(attribute) case value when Array return "1=0" if value.empty? return "(#{expression} IN (?) OR #{expression} IS NULL)" if value.include?(nil) "#{expression} IN (?)" when Range return "#{expression} >= ?" if value.end.nil? return "#{expression} #{value.exclude_end? ? '<' : '<='} ?" if value.begin.nil? return "#{expression} >= ? AND #{expression} < ?" if value.exclude_end? "#{expression} BETWEEN ? AND ?" when nil "#{expression} IS NULL" else "#{expression} = ?" end end |
.json_hash_conditions?(conditions) ⇒ Boolean
188 189 190 |
# File 'lib/keinaufwand/sync_core/record.rb', line 188 def self.json_hash_conditions?(conditions) conditions.is_a?(Hash) && conditions.any? { |key, _| json_attribute?(key) } end |
.json_order_argument?(argument) ⇒ Boolean
202 203 204 205 206 207 208 209 210 211 |
# File 'lib/keinaufwand/sync_core/record.rb', line 202 def self.json_order_argument?(argument) case argument when Symbol json_attribute?(argument) when Hash argument.keys.all? { |key| json_attribute?(key) } else false end end |
.json_order_sql(argument) ⇒ Object
213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/keinaufwand/sync_core/record.rb', line 213 def self.json_order_sql(argument) case argument when Symbol json_value(argument) when Hash argument.map do |attribute, direction| direction = direction.to_s.downcase == "desc" ? "DESC" : "ASC" "#{json_value(attribute)} #{direction}" end end end |
.json_value(attribute) ⇒ Object
39 40 41 42 43 |
# File 'lib/keinaufwand/sync_core/record.rb', line 39 def self.json_value(attribute) table = connection.quote_table_name(table_name) column = connection.quote_column_name("data") json_value_expression("#{table}.#{column}", attribute) end |
.json_value_expression(column, attribute) ⇒ Object
45 46 47 48 49 50 51 52 53 |
# File 'lib/keinaufwand/sync_core/record.rb', line 45 def self.json_value_expression(column, attribute) if connection.adapter_name.match?(/postgres/i) expression = "#{column} #>> #{connection.quote("{#{attribute.to_s.split(".").join(",")}}")}" postgres_cast(expression, attribute) else expression = "json_extract(#{column}, #{connection.quote("$.#{attribute}")})" sqlite_cast(expression, attribute) end end |
.load_keinaufwand_schema ⇒ Object
63 64 65 66 |
# File 'lib/keinaufwand/sync_core/record.rb', line 63 def self.load_keinaufwand_schema version = Keinaufwand::Api.configuration.version || "2026-07-02" self.keinaufwand_schema = Keinaufwand::Api::Schema.load(Keinaufwand::Api.configuration.schema_path(version)) end |
.model_schema ⇒ Object
68 69 70 |
# File 'lib/keinaufwand/sync_core/record.rb', line 68 def self.model_schema schema&.for(name.demodulize) end |
.postgres_cast(expression, attribute) ⇒ Object
169 170 171 172 173 174 175 176 177 178 |
# File 'lib/keinaufwand/sync_core/record.rb', line 169 def self.postgres_cast(expression, attribute) case schema_attribute_type(attribute) when "integer" then "(#{expression})::integer" when "float", "decimal" then "(#{expression})::numeric" when "date" then "(#{expression})::date" when "datetime", "time" then "(#{expression})::timestamp" when "boolean" then "(#{expression})::boolean" else expression end end |
.schema ⇒ Object
59 60 61 |
# File 'lib/keinaufwand/sync_core/record.rb', line 59 def self.schema keinaufwand_schema || load_keinaufwand_schema end |
.schema_associations ⇒ Object
76 77 78 |
# File 'lib/keinaufwand/sync_core/record.rb', line 76 def self.schema_associations model_schema&.fetch("associations", {}) || {} end |
.schema_attribute?(attribute) ⇒ Boolean
80 81 82 83 |
# File 'lib/keinaufwand/sync_core/record.rb', line 80 def self.schema_attribute?(attribute) attribute = attribute.to_s schema_attributes.key?(attribute) || schema_attributes.key?(attribute.split(".").first) end |
.schema_attribute_type(attribute) ⇒ Object
85 86 87 88 |
# File 'lib/keinaufwand/sync_core/record.rb', line 85 def self.schema_attribute_type(attribute) attribute = attribute.to_s (schema_attributes[attribute].presence || schema_attributes[attribute.split(".").first]).to_s end |
.schema_attributes ⇒ Object
72 73 74 |
# File 'lib/keinaufwand/sync_core/record.rb', line 72 def self.schema_attributes model_schema&.fetch("attributes", {}) || {} end |
.split_json_conditions(conditions) ⇒ Object
192 193 194 |
# File 'lib/keinaufwand/sync_core/record.rb', line 192 def self.split_json_conditions(conditions) conditions.partition { |key, _| json_attribute?(key) }.map(&:to_h) end |
.sqlite_cast(expression, attribute) ⇒ Object
180 181 182 183 184 185 186 |
# File 'lib/keinaufwand/sync_core/record.rb', line 180 def self.sqlite_cast(expression, attribute) case schema_attribute_type(attribute) when "integer" then "CAST(#{expression} AS INTEGER)" when "float", "decimal" then "CAST(#{expression} AS REAL)" else expression end end |
.where_present(*attributes) ⇒ Object
15 16 17 18 19 20 |
# File 'lib/keinaufwand/sync_core/record.rb', line 15 def self.where_present(*attributes) attributes.reduce(all) do |scope, attribute| expression = json_value(attribute) scope.where("#{expression} IS NOT NULL").where("#{expression} != ?", "") end end |
Instance Method Details
#asset_attribute?(attribute, value) ⇒ Boolean
373 374 375 |
# File 'lib/keinaufwand/sync_core/record.rb', line 373 def asset_attribute?(attribute, value) attribute.end_with?("_url") && value.is_a?(Hash) end |
#build_inline_keinaufwand_record(attributes, target_class) ⇒ Object
337 338 339 340 341 |
# File 'lib/keinaufwand/sync_core/record.rb', line 337 def build_inline_keinaufwand_record(attributes, target_class) return unless attributes.is_a?(Hash) target_class.new(id: attributes["id"] || attributes[:id], type: target_class.name, data: attributes.except("id", :id)) end |
#build_inline_keinaufwand_records(value, target_class) ⇒ Object
329 330 331 332 333 334 335 |
# File 'lib/keinaufwand/sync_core/record.rb', line 329 def build_inline_keinaufwand_records(value, target_class) if value.is_a?(Array) value.map { |attributes| build_inline_keinaufwand_record(attributes, target_class) }.compact elsif value.is_a?(Hash) build_inline_keinaufwand_record(value, target_class) end end |
#cast_keinaufwand_value(attribute, value) ⇒ Object
343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/keinaufwand/sync_core/record.rb', line 343 def cast_keinaufwand_value(attribute, value) case self.class.schema_attribute_type(attribute) when "datetime", "time" then parse_keinaufwand_time(value) || value when "date" then parse_keinaufwand_date(value) || value when "integer" then value.to_i if value.present? when "float" then value.to_f if value.present? when "decimal" then value.to_d if value.present? when "boolean" then value == true || value.to_s == "true" else value end end |
#derivative_value(value, size) ⇒ Object
377 378 379 380 381 |
# File 'lib/keinaufwand/sync_core/record.rb', line 377 def derivative_value(value, size) return value unless value.is_a?(Hash) value[size.to_s] || value[size.to_sym] || value.values.compact.first end |
#id ⇒ Object
276 277 278 |
# File 'lib/keinaufwand/sync_core/record.rb', line 276 def id read_attribute(:id) end |
#id=(value) ⇒ Object
280 281 282 283 284 285 286 287 288 |
# File 'lib/keinaufwand/sync_core/record.rb', line 280 def id=(value) if value.is_a?(Array) write_keinaufwand_column("id", value.first) write_keinaufwand_column("type", value.second) else write_keinaufwand_column("id", value) write_keinaufwand_column("type", self.class.name) if self[:type].blank? && self.class.name.present? end end |
#load_keinaufwand_association(association, type, target_class, config) ⇒ Object
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/keinaufwand/sync_core/record.rb', line 309 def load_keinaufwand_association(association, type, target_class, config) inline_value = read_keinaufwand_value(association) case type when "belongs_to", "has_one" target_id = read_keinaufwand_value("#{association}_id") local = target_class.find_by(id: target_id) if target_id.present? local || build_inline_keinaufwand_records(inline_value, target_class) when "has_many" foreign_key = config[:foreign_key] || "#{self.class.name.demodulize.underscore}_id" local = target_class.where(foreign_key => id) local.exists? ? local : build_inline_keinaufwand_records(inline_value, target_class) when "has_and_belongs_to_many" target_ids = Array(read_keinaufwand_value("#{association.singularize}_ids")) target_class.where(id: target_ids) else build_inline_keinaufwand_records(inline_value, target_class) end end |
#parse_keinaufwand_date(value) ⇒ Object
364 365 366 367 368 369 370 371 |
# File 'lib/keinaufwand/sync_core/record.rb', line 364 def parse_keinaufwand_date(value) return value if value.is_a?(Date) return if value.blank? Date.parse(value.to_s) rescue ArgumentError nil end |
#parse_keinaufwand_time(value) ⇒ Object
355 356 357 358 359 360 361 362 |
# File 'lib/keinaufwand/sync_core/record.rb', line 355 def parse_keinaufwand_time(value) return value if value.is_a?(Time) || defined?(ActiveSupport::TimeWithZone) && value.is_a?(ActiveSupport::TimeWithZone) return if value.blank? Time.zone ? Time.zone.parse(value.to_s) : Time.parse(value.to_s) rescue ArgumentError nil end |
#primary_key_values_present? ⇒ Boolean
290 291 292 |
# File 'lib/keinaufwand/sync_core/record.rb', line 290 def primary_key_values_present? Array(self.class.primary_key).all? { |column| @attributes.fetch_value(column).present? } end |
#read_keinaufwand_value(attribute) ⇒ Object
305 306 307 |
# File 'lib/keinaufwand/sync_core/record.rb', line 305 def read_keinaufwand_value(attribute) data[attribute.to_s] if data.is_a?(Hash) end |
#remember_transaction_record_state ⇒ Object
298 299 300 301 302 303 |
# File 'lib/keinaufwand/sync_core/record.rb', line 298 def remember_transaction_record_state super return unless self.class.composite_primary_key? && @_start_transaction_state @_start_transaction_state[:id] = self.class.primary_key.map { |column| @attributes.fetch_value(column) } end |
#write_keinaufwand_column(attribute, value) ⇒ Object
294 295 296 |
# File 'lib/keinaufwand/sync_core/record.rb', line 294 def write_keinaufwand_column(attribute, value) @attributes.write_from_user(attribute, value) end |