lib/calculated_attributes.rb in calculated_attributes-0.0.22 vs lib/calculated_attributes.rb in calculated_attributes-0.1.0
- old
+ new
@@ -1,226 +1,11 @@
require 'calculated_attributes/version'
require 'active_record'
-module CalculatedAttributes
- def calculated(*args)
- @config ||= CalculatedAttributes::Config.new
- @config.calculated(args.first, args.last) if args.size == 2
- @config
- end
+# Include patches.
+require 'calculated_attributes/rails_patches'
+require 'calculated_attributes/arel_patches'
+fail "Unsupported ActiveRecord version: #{ActiveRecord::VERSION::MAJOR}" unless [3, 4].include? ActiveRecord::VERSION::MAJOR
+require "calculated_attributes/rails_#{ActiveRecord::VERSION::MAJOR}_patches"
- class CalculatedAttributes
- class Config
- def calculated(title = nil, lambda = nil)
- @calculations ||= {}
- @calculations[title] ||= lambda if title && lambda
- @calculations
- end
- end
- end
-end
-ActiveRecord::Base.extend CalculatedAttributes
-
-ActiveRecord::Base.send(:include, Module.new do
- def calculated(*args)
- if self.class.respond_to? :scoped
- self.class.scoped.calculated(*args).find(id)
- else
- self.class.all.calculated(*args).find(id)
- end
- end
-
- def method_missing(sym, *args, &block)
- no_sym_in_attr =
- if @attributes.respond_to? :include?
- !@attributes.include?(sym.to_s)
- else
- !@attributes.key?(sym.to_s)
- end
- if no_sym_in_attr && (self.class.calculated.calculated[sym] || self.class.base_class.calculated.calculated[sym])
- Rails.logger.warn("Using calculated value without including it in the relation: #{sym}") if defined? Rails
- class_with_attr =
- if self.class.calculated.calculated[sym]
- self.class
- else
- self.class.base_class
- end
- if class_with_attr.respond_to? :scoped
- class_with_attr.scoped.calculated(sym).find(id).send(sym)
- else
- class_with_attr.all.calculated(sym).find(id).send(sym)
- end
- else
- super(sym, *args, &block)
- end
- end
-
- def respond_to?(method, include_private = false)
- no_sym_in_attr =
- if @attributes.respond_to? :include?
- !@attributes.include?(method.to_s)
- elsif @attributes.respond_to? :key?
- !@attributes.key?(method.to_s)
- else
- true
- end
- super || (no_sym_in_attr && (self.class.calculated.calculated[method] || self.class.base_class.calculated.calculated[method]))
- end
-end)
-
-ActiveRecord::Relation.send(:include, Module.new do
- def calculated(*args)
- projections = arel.projections
- args.each do |arg|
- lam = klass.calculated.calculated[arg] || klass.base_class.calculated.calculated[arg]
- sql = lam.call
- new_projection = sql.is_a?(String) ? Arel.sql("(#{sql})").as(arg.to_s) : sql.as(arg.to_s)
- new_projection.calculated_attr!
- projections.push new_projection
- end
- select(projections)
- end
-end)
-
-Arel::SelectManager.send(:include, Module.new do
- def projections
- @ctx.projections
- end
-end)
-
-module ActiveRecord
- module FinderMethods
- def construct_relation_for_association_find(join_dependency)
- calculated_columns = arel.projections.select { |p| p.is_a?(Arel::Nodes::Node) && p.calculated_attr? }
- relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns.concat(calculated_columns))
- join_dependency.calculated_columns = calculated_columns
- apply_join_dependency(relation, join_dependency)
- end
- end
-
- module AttributeMethods
- module ClassMethods
- # Generates all the attribute related methods for columns in the database
- # accessors, mutators and query methods.
- def define_attribute_methods
- case ActiveRecord::VERSION::MAJOR
- when 3
- unless defined?(@attribute_methods_mutex)
- msg = "It looks like something (probably a gem/plugin) is overriding the " \
- "ActiveRecord::Base.inherited method. It is important that this hook executes so " \
- "that your models are set up correctly. A workaround has been added to stop this " \
- "causing an error in 3.2, but future versions will simply not work if the hook is " \
- "overridden. If you are using Kaminari, please upgrade as it is known to have had " \
- "this problem.\n\n"
- msg << "The following may help track down the problem:"
-
- meth = method(:inherited)
- if meth.respond_to?(:source_location)
- msg << " #{meth.source_location.inspect}"
- else
- msg << " #{meth.inspect}"
- end
- msg << "\n\n"
-
- ActiveSupport::Deprecation.warn(msg)
-
- @attribute_methods_mutex = Mutex.new
- end
-
- # Use a mutex; we don't want two thread simaltaneously trying to define
- # attribute methods.
- @attribute_methods_mutex.synchronize do
- return if attribute_methods_generated?
- superclass.define_attribute_methods unless self == base_class
- columns_to_define =
- if defined?(calculated) && calculated.instance_variable_get('@calculations')
- calculated_keys = calculated.instance_variable_get('@calculations').keys
- column_names.reject { |c| calculated_keys.include? c.intern }
- else
- column_names
- end
- super(columns_to_define)
- columns_to_define.each { |name| define_external_attribute_method(name) }
- @attribute_methods_generated = true
- end
-
- when 4
- return false if @attribute_methods_generated
- # Use a mutex; we don't want two threads simultaneously trying to define
- # attribute methods.
- generated_attribute_methods.synchronize do
- return false if @attribute_methods_generated
- superclass.define_attribute_methods unless self == base_class
- columns_to_define =
- if defined?(calculated) && calculated.instance_variable_get('@calculations')
- calculated_keys = calculated.instance_variable_get('@calculations').keys
- column_names.reject { |c| calculated_keys.include? c.intern }
- else
- column_names
- end
- super(columns_to_define)
- @attribute_methods_generated = true
- end
- true
- end
- end
- end
- end
-
- module Associations
- class JoinDependency
- attr_writer :calculated_columns
-
- def instantiate(rows)
- primary_key = join_base.aliased_primary_key
- parents = {}
-
- records = rows.map do |model|
- primary_id = model[primary_key]
- parent = parents[primary_id] ||= join_base.instantiate(model)
- construct(parent, @associations, join_associations, model)
- @calculated_columns.each { |column| parent[column.right] = model[column.right] }
- parent
- end.uniq
-
- remove_duplicate_results!(active_record, records, @associations)
- records
- end
- end
- end
-end
-
-module ActiveRecord
- module AttributeMethods
- module Write
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
- # for fixnum and float columns are turned into +nil+.
- def write_attribute(attr_name, value)
- if ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR == 2
- write_attribute_with_type_cast(attr_name, value, true)
- else
- attr_name = attr_name.to_s
- attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
- @attributes_cache.delete(attr_name)
- column = column_for_attribute(attr_name)
-
- @attributes[attr_name] = type_cast_attribute_for_write(column, value)
- end
- end
- end
- end
-end
-
-
-module Arel
- module Nodes
- class Node
- def calculated_attr!
- @is_calculated_attr = true
- end
-
- def calculated_attr?
- @is_calculated_attr
- end
- end
- end
-end
+# Include model code.
+require 'calculated_attributes/model_methods'