Skip to content

Commit

Permalink
Merge pull request #1 from hyoshida/hotfix/association
Browse files Browse the repository at this point in the history
Fix problems for Active Record Association
  • Loading branch information
YOSHIDA Cake committed Jul 15, 2014
2 parents f7e0065 + b1f968d commit f64d8e9
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 53 deletions.
3 changes: 2 additions & 1 deletion lib/utusemi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def configure(&block)
end

def include_to_activerecord_base
# TODO: Organize name spaces
ActiveRecord::Base.send(:include, Core::InstanceMethods)
end

Expand All @@ -42,7 +43,7 @@ def prepend_to_activerecord_relation
end

def prepend_to_activerecord_eigenclass
activerecord_eigenclass.send(:prepend, Core::ActiveRecord::Querying)
activerecord_eigenclass.send(:prepend, Core::ActiveRecord::Base::ClassMethods)
# for rails 3.x
activerecord_eigenclass.send(:prepend, Core::ActiveRecord::RelationMethod) if Rails::VERSION::MAJOR == 3
# for association
Expand Down
86 changes: 57 additions & 29 deletions lib/utusemi/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,19 +132,21 @@ def utusemi_for_association(name, association, options = {})
# #=> [<products.titleが"test"であるレコード>]
#
module ActiveRecord
module Querying
include Base

case Rails::VERSION::MAJOR
when 4
delegate :utusemi, to: :all
when 3
delegate :utusemi, to: :scoped
module Base
module ClassMethods
include Utusemi::Core::Base

case Rails::VERSION::MAJOR
when 4
delegate :utusemi, to: :all
when 3
delegate :utusemi, to: :scoped
end
end
end

module QueryMethods
include Base
include Utusemi::Core::Base

def utusemi!(obj = nil, options = {})
super.tap { warning_checker unless Rails.env.production? }
Expand Down Expand Up @@ -264,6 +266,25 @@ def initialize(*args, &block)
utusemi!(utusemi_values[:type], utusemi_values[:options]) if utusemi_values[:flag]
super
end

# 用途
# cloneでは浅いコピーしか行われず@utusemi_valuesの内容が
# 書き変わってしまうので、これを解決するために@utusemi_valuesもdupする
def initialize_copy(original_obj)
@utusemi_values = original_obj.utusemi_values.dup
super
end

# 用途
# association_cacheの影響でAssociation#ownerでclone前のインスタンスしか取得できないため
# 別経路から実際の呼び出し元インスタンスを参照できるようにし、utusemi_valuesを取り出せるようにする
def association(name)
truthly_owner = self
association = super
eigenclass = class << association; self; end
eigenclass.send(:define_method, :truthly_owner) { truthly_owner }
association
end
end

# 用途
Expand All @@ -280,35 +301,29 @@ def initialize(*args, &block)
#
module Associations
def scope(*args)
utusemi_values = owner.utusemi_values
utusemi_values = truthly_owner.utusemi_values
return super unless utusemi_values[:flag]
super.utusemi!(@reflection.name.to_s.singularize, utusemi_values[:options])
end

def load_target(*args)
utusemi_values = truthly_owner.utusemi_values
return super unless utusemi_values[:flag]
super.each { |record| record.utusemi!(@reflection.name.to_s.singularize, utusemi_values[:options]) }
end
end

module AssociationMethods
def belongs_to(name, scope = nil, options = {})
check_deplicated_association_warning(:belongs_to, name, scope)
utusemi_flag = scope.try(:delete, :utusemi)
scope = utusemi_association_scope(:belongs_to, name, scope) if utusemi_flag
super if !utusemi_flag || !method_defined?(name)
define_utusemi_association_reader(name, utusemi_flag => true)
def belongs_to(name, *args)
utusemi_association(:belongs_to, name, *args) { |*a| super(*a) }
end

def has_one(name, scope = nil, options = {})
check_deplicated_association_warning(:has_one, name, scope)
utusemi_flag = scope.try(:delete, :utusemi)
scope = utusemi_association_scope(:has_one, name, scope) if utusemi_flag
super if !utusemi_flag || !method_defined?(name)
define_utusemi_association_reader(name, utusemi_flag => true)
def has_one(name, *args)
utusemi_association(:has_one, name, *args) { |*a| super(*a) }
end

def has_many(name, scope = nil, options = {}, &extension)
check_deplicated_association_warning(:has_many, name, scope)
utusemi_flag = scope.try(:delete, :utusemi)
scope = utusemi_association_scope(:has_many, name, scope) if utusemi_flag
super if !utusemi_flag || !method_defined?(name)
define_utusemi_association_reader(name, utusemi_flag => true)
def has_many(name, *args)
utusemi_association(:has_many, name, *args) { |*a| super(*a) }
end

private
Expand All @@ -320,7 +335,20 @@ def check_deplicated_association_warning(association_type, name, scope)
Rails.logger.warn "[Utusemi:WARNING] \"#{association_type} :#{name}\" is duplicated in #{self.name}."
end

def utusemi_association_scope(method_name, name, scope = {})
def utusemi_association(association_type, name, *args)
if args.empty?
yield name, *args
return define_utusemi_association_reader(name)
end
scope = args.shift
check_deplicated_association_warning(association_type, name, scope)
utusemi_flag = scope.try(:delete, :utusemi)
scope = utusemi_association_scope(association_type, name, scope) if utusemi_flag
yield name, scope, *args if !utusemi_flag || !method_defined?(name)
define_utusemi_association_reader(name, utusemi_flag => true)
end

def utusemi_association_scope(method_name, name, scope)
utusemi_map = Utusemi.config.map(name.to_s.singularize)
default_scope = { class_name: utusemi_map.class_name }
default_scope[:foreign_key] = utusemi_map.foreign_key if method_name == :belongs_to
Expand Down
1 change: 1 addition & 0 deletions spec/dummy/app/models/product.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
class Product < ActiveRecord::Base
has_many :stocks
end
3 changes: 3 additions & 0 deletions spec/dummy/app/models/stock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Stock < ActiveRecord::Base
belongs_to :product
end
9 changes: 9 additions & 0 deletions spec/dummy/db/migrate/20140712080810_create_stocks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class CreateStocks < ActiveRecord::Migration
def change
create_table :stocks do |t|
t.references :product
t.integer :units
t.timestamps
end
end
end
3 changes: 3 additions & 0 deletions spec/factories/products.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
FactoryGirl.define do
factory :product do
title 'My favorite product'

trait :with_stock
stocks { FactoryGirl.build_list(:stock, 1) }
end
end
5 changes: 5 additions & 0 deletions spec/factories/stocks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FactoryGirl.define do
factory :stock do
units 10
end
end
21 changes: 0 additions & 21 deletions spec/lib/utusemi/configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,2 @@
describe Utusemi::Configuration do
before do
Utusemi.configure do
map :sample do
name :title
end
end
class Product < ActiveRecord::Base; end
end

subject { Product }

it { should respond_to(:utusemi) }

context 'ActiveRecord::Base#utusemi' do
let(:product) { FactoryGirl.build(:product) }

subject { product.utusemi(:sample) }

it { should respond_to(:title) }
it { should respond_to(:name) }
end
end
70 changes: 70 additions & 0 deletions spec/lib/utusemi/core_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
describe Utusemi::Core do
# TODO: Implement the new syntax
#
# map(:sample_one) { ... }
# map(:sample_two) { ... }
# class Stock < ActiveRecord::Base
# belongs_to :product, utusemi: :sample_one
# end
# class Product < ActiveRecord::Base
# has_many :stocks, utusemi: :sample_two
# end
# product.utusemi.stocks.first
#
describe ActiveRecord::Associations do
before do
Utusemi.configure do
map(:product) { name :title }
map(:stock) { quantity :units }
end
end

describe '#scope' do
let(:product) { FactoryGirl.create(:product, :with_stock) }
subject { product.reload.utusemi(:product).stocks.first }
it { should respond_to(:units) }
it { should respond_to(:quantity) }
it { expect(subject.units).to eq(subject.quantity) }
end

describe '#load_target' do
let(:product) { FactoryGirl.build(:product, :with_stock) }
subject { product.utusemi(:product).stocks.first }
it { should respond_to(:units) }
it { should respond_to(:quantity) }
it { expect(subject.units).to eq(subject.quantity) }
end
end

describe ActiveRecord::Base::ClassMethods do
describe '::utusemi!' do
before { class TemporaryModel < ActiveRecord::Base; end }
before { subject.utusemi! }
subject { TemporaryModel }
it { expect(subject.utusemi_values).not_to be_empty }
end

describe '::utusemi' do
before { subject.utusemi }
subject { Product }
it { expect(subject.utusemi_values).to be_empty }
end
end

describe Utusemi::Core::InstanceMethods do
let(:product_first) { FactoryGirl.build(:product) }
let(:product_second) { FactoryGirl.build(:product) }

describe '#utusemi!' do
before { subject.utusemi! }
subject { product_first }
it { expect(subject.utusemi_values).not_to be_empty }
end

describe '#utusemi' do
before { subject.utusemi }
subject { product_second }
it { expect(subject.utusemi_values).to be_empty }
end
end
end
57 changes: 55 additions & 2 deletions spec/models/product_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,56 @@
describe Product, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
describe Product do
let(:product) { FactoryGirl.build(:product) }

before do
Utusemi.configure do
map :sample do |options|
name :title
caption options[:caption] || :none
end
end
end

it { should respond_to(:utusemi) }

# TODO: Implement the new syntax
#
# map(:product) { ... }
# Product.utusemi.first
#
describe '#utusemi(type)' do
subject { product.utusemi(:sample) }
it { should respond_to(:title) }
it { should respond_to(:name) }
end

describe '#utusemi(type, options)' do
subject { product.utusemi(:sample, caption: :title) }
it { expect(subject.caption).to eq(subject.title) }
end

describe '::utusemi(type)' do
before { FactoryGirl.create(:product, title: 'foobar') }
subject { described_class.utusemi(:sample) }

it '::where by alias column' do
expect(subject.where(name: 'foobar').count).to eq(1)
end

it '::order by alias column' do
expect { subject.order(:name) }.not_to raise_error
end

it 'call alias column from instance' do
expect(subject.first.name).to eq(subject.first.title)
end
end

describe '::utusemi(type, options)' do
before { FactoryGirl.create(:product, title: 'foobar') }
subject { described_class.utusemi(:sample, caption: :title) }

it 'call alias column from instance' do
expect(subject.first.caption).to eq(subject.first.title)
end
end
end
5 changes: 5 additions & 0 deletions spec/models/stock_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require 'rails_helper'

RSpec.describe Stock, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

0 comments on commit f64d8e9

Please sign in to comment.