Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ group :test do
gem 'activerecord', '~> 4.0.0'
gem 'addressable'
gem 'database_cleaner'
# , git: "git://github.com/lanej/database_cleaner.git", branch: "datamapper-fix"
gem 'rspec-its'
end

Expand Down
5 changes: 2 additions & 3 deletions lib/ardm/active_record/associations.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


module Ardm
module ActiveRecord
module Associations
Expand Down Expand Up @@ -67,6 +65,7 @@ def dump_associations_hash(options)
end

def belongs_to(field, *args)
puts "#{self.name} belongs to #{field}"
options = args.shift || {}

if String === options || Class === options # belongs_to :name, 'Class', options: 'here'
Expand All @@ -85,7 +84,7 @@ def belongs_to(field, *args)
Ardm::ActiveRecord::Finalize.on_finalize do
assoc = reflect_on_association(field)
klass.class_eval do
# @todo String is a hack... hoping AR can convert strings to integers during save for integer keys.
puts "Adding belongs_to property #{assoc.foreign_key} #{assoc.klass.key.first.class}"
property assoc.foreign_key, assoc.primary_key_column.sql_type == "Integer" ? Integer : String, key: false
end
end
Expand Down
6 changes: 6 additions & 0 deletions lib/ardm/active_record/finalize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ def self.on_finalize(&block)
end

module ClassMethods
def on_finalize(&block)
block.call if @finalized
Ardm::ActiveRecord::Finalize.on_finalize << block
end

def finalize
@finalized = true
Ardm::ActiveRecord::Finalize.on_finalize.each { |f| f.call }
end
end
Expand Down
7 changes: 6 additions & 1 deletion lib/ardm/active_record/is/state_machine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ module StateMachine
extend ActiveSupport::Concern

included do
include AASM
if defined?(AASM)
include AASM
end
end

module ClassMethods
def is_state_machine(options, &block)
unless defined?(AASM)
STDERR.puts "WARNING: you need to load AASM yourself (not ardm gemspec)"
end
STDERR.puts "TODO: dm state machine on #{self}"
property options[:column], Ardm::Property::String, default: options[:initial]
aasm column: options[:column], &block
Expand Down
19 changes: 15 additions & 4 deletions lib/ardm/active_record/persistence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,23 @@ def saved?
!new_record?
end

def save_self(run_callbacks=true)
save(run_callbacks)
def save_self(execute_hooks = true)
save(execute_hooks)
end

def save(run_callbacks=true)
unless run_callbacks
# @todo: be more like DM:
# DM version of this method pasted here as a comment for your convenience
# # short-circuit if the resource is not dirty
# return saved? unless dirty_self?
#
# if execute_hooks
# new? ? create_with_hooks : update_with_hooks
# else
# _persist
# end
# clean?
def save(execute_hooks=true)
unless execute_hooks
raise Ardm::NotImplemented, "ActiveRecord doesn't support saving without callbacks"
end

Expand Down
252 changes: 202 additions & 50 deletions lib/ardm/active_record/relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,60 +100,208 @@ def apply_finder_options(options, *args)
relation = relation.send(finder, finders[finder])
end

debug = Proc.new do |x|
# un-comment here for ALL THE PUTS:
if ENV["DEBUG"]
puts x
end
end

conditions.each do |key, value|
if assoc = relation.reflect_on_association(key)
conditions.delete(key)
# strip out assocations
case assoc.macro
when :belongs_to
id = value.is_a?(Hash) ? value.with_indifferent_access[:id] : value
relation = if value.is_a?(::ActiveRecord::Relation)
if value.values.empty?
relation.where.not(assoc.foreign_key => nil)
else
relation.where(assoc.foreign_key => value)
end
else
relation.where(assoc.foreign_key => id)
end
when :has_one
foreign_class = assoc.options[:class_name].constantize
foreign_key = assoc.foreign_key
parent_key = assoc.options[:child_key] || klass.primary_key

if value.is_a?(::Array) && value.empty?
# @fixme: dm basically no-ops cause it knows you are stupid
return klass.where(klass.primary_key => nil)

if value.is_a?(Hash)
value = value.with_indifferent_access[:id]
end

unmodified_key = key
the_positive = true
if key.is_a?(Ardm::Query::Operator)
if assoc = relation.reflect_on_association(key.target)
debug.call "IT's an Operator #{key.target}.#{key.operator} on an association #{assoc.inspect}"
if key.operator == :not_eq
the_positive = false
else
raise "Fail. can't apply #{key.target}.#{key.operator} because #{key.target} is an association."
end
end
key = key.target
end
assoc = relation.reflect_on_association(key)

fallback = Proc.new do
debug.call "checking all of #{relation.size} things for #{key} == #{value.inspect}"
select_matching = relation.select do |x|
debug.call "given #{x.inspect}"
actual = x.send(key)
expected = value
debug.call "in #{the_positive} actual #{actual.inspect} \nvs expected #{expected.inspect}"
if expected.is_a?(::ActiveRecord::Relation)
if actual.is_a?(::ActiveRecord::Relation)
if the_positive
result = (actual.to_a == expected.to_a)
else
result = (actual.to_a != expected.to_a)
end
else
if the_positive
result = expected.to_a.include?(actual)
else
result = !expected.to_a.include?(actual)
end
end
elsif expected.is_a?(Fixnum)
if actual.is_a?(::ActiveRecord::Relation)
if the_positive
result = actual.map(&:id).include?(expected)
else
result = !actual.map(&:id).include?(expected)
end
else
if the_positive
result = (actual.try(:id) == expected)
else
result = (actual.try(:id) != expected)
end
end
elsif expected.nil?
if actual.is_a?(::ActiveRecord::Relation)
if the_positive
result = actual.empty?
else
result = !actual.empty?
end
else
if the_positive
result = actual.nil?
else
result = !actual.nil?
end
end
else
if actual.is_a?(::ActiveRecord::Relation)
if the_positive
result = actual.include?(expected)
else
result = !actual.include?(expected)
end
else
if the_positive
result = (actual == expected)
else
result = (actual != expected)
end
end
end
debug.call "result #{result}"
result
end
relation.where(id: select_matching)
end

set_relation = Proc.new do |new_relation_proc|
debug.call "the_positive #{the_positive}"
debug.call caller[0]
begin
new_relation = new_relation_proc.call
if ENV["DEBUG"]
selected = fallback.call.to_a
puts "comparing #{new_relation.to_a} with #{selected}"
unless new_relation.to_a == selected
puts "MATCHING FAILED HERE -- #{new_relation.to_a} vs #{selected}"
puts caller
end
end
relation = new_relation
rescue => e
if ENV["DEBUG"]
puts e.inspect
puts e.backtrace
puts "USING FALLBACK, you should really pry this spot and figure it out"
relation = fallback.call
else
raise e
end
end
end

if assoc
conditions.delete(unmodified_key)

relation = if value.is_a?(::ActiveRecord::Base)
relation.where(parent_key => value.send(assoc.foreign_key))
elsif value.is_a?(::ActiveRecord::Relation)
relation.where(parent_key => value.select(foreign_key))
elsif value.nil?
relation.where.not(parent_key => foreign_class.select(foreign_key).where.not(foreign_key => value))
else
relation.where(parent_key => foreign_class.select(foreign_key).where(value))
end
when :has_many
foreign_class = assoc.options[:class_name].constantize
foreign_key = assoc.foreign_key
parent_key = assoc.options[:child_key] || klass.primary_key

relation = if value.is_a?(::ActiveRecord::Relation)
relation.where(foreign_key => value)
else
relation.where(parent_key => foreign_class.select(foreign_class.primary_key).where.not(foreign_key => value))
end
# TODO: still need this?
# if value.is_a?(::Array) && value.empty?
# # @fixme: dm basically no-ops cause it knows you are stupid
# return klass.where(klass.primary_key => nil)
# end

if relation.klass == assoc.options[:class_name].try(:constantize)
debug.call "Special handling would be needed for #{key} => #{value} on #{relation.klass}. falling back to iteration and comparison"
relation = fallback.call
else
raise("unknown: #{assoc.inspect}")
# strip out assocations
case assoc.macro
when :belongs_to
if value.is_a?(::ActiveRecord::Relation)
if value.values.empty?
set_relation[->{relation.where.not(assoc.foreign_key => nil)}]
else
set_relation[->{relation.where(assoc.foreign_key => value)}]
end
else
if the_positive
set_relation[->{relation.where(assoc.foreign_key => value)}]
else
set_relation[->{relation.where.not(assoc.foreign_key => value)}]
end
end
when :has_one
foreign_class = assoc.options[:class_name].constantize
foreign_key = assoc.foreign_key
parent_key = assoc.options[:child_key] || klass.primary_key

if value.is_a?(::Array) && value.empty?
# @fixme: dm basically no-ops cause it knows you are stupid
return set_relation[->{klass.where(klass.primary_key => nil)}]
end

if value.is_a?(::ActiveRecord::Base)
set_relation[->{relation.where(parent_key => value.send(assoc.foreign_key))}]
elsif value.is_a?(::ActiveRecord::Relation)
if the_positive
set_relation[->{relation.where(parent_key => value.select(&foreign_key))}]
else
set_relation[->{relation.where.not(parent_key => value.select(&foreign_key))}]
end
elsif value.nil?
set_relation[->{relation.where.not(parent_key => foreign_class.select(&foreign_key).where.not(foreign_key => value))}]
else
set_relation[->{relation.where(parent_key => foreign_class.select(&foreign_key).where(value))}]
end
when :has_many
if assoc.options[:through]
debug.call "Special handling would be needed for has_many through. #{key} => #{value} on #{relation.klass}. falling back to iteration and comparison"
relation = fallback.call
else
foreign_class = assoc.options[:class_name].constantize
foreign_key = assoc.foreign_key
parent_key = assoc.options[:child_key] || klass.primary_key

if value.is_a?(::ActiveRecord::Relation)
set_relation[->{relation.where(foreign_key => value)}]
else
set_relation[->{relation.where(parent_key => foreign_class.select(foreign_class.primary_key).where.not(foreign_key => value))}]
end
end
else
raise("unknown: #{assoc.inspect}")
end
end
end
end

processed_conditions = {}

conditions.each do |key, value|
debug.call "remaining conditions #{key}"
key = key.is_a?(Ardm::Property) ? key.name : key

case key
Expand Down Expand Up @@ -181,13 +329,17 @@ def calculate(operation, column_name, options={})
super(operation, column_name, options)
end

def method_missing(meth, *a, &b)
if a.empty? && association = reflect_on_association(meth.to_sym)
case association.macro
when :belongs_to
association.klass.where(klass.primary_key => self.select(association.foreign_key))
when :has_many, :has_one
association.klass.where(association.foreign_key => self.clone)
def method_missing(meth, *args, &b)
association = reflect_on_association(meth.to_sym) ||
reflect_on_association(meth.to_s.singularize.to_sym) ||
reflect_on_association(meth.to_s.pluralize.to_sym)
if association
ids = self.map(&association.name)
result = association.klass.all(id: ids)
if args.empty?
result
else
result.all(*args)
end
else
super
Expand Down
Loading