Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

association methods are now generated in modules #3636

Merged
merged 6 commits into from

9 participants

@joshsusser

Instead of generating association methods directly in the model
class, they are generated in an anonymous module which
is then included in the model class. There is one such module
for each association. The only subtlety is that the
generated_attributes_methods module (from ActiveModel) must
be forced to be included before association methods are created
so that attribute methods will not shadow association methods.

The advantage to this approach is that it is now possible to override
a generated method and still call the original generated method via super.

This change is relatively straightforward from a code organization
perspective, but it has a high potential for causing grief in applications
that make assumptions about how association methods are generated
or ordering of method definitions. Therefore it probably needs some
stress-testing in real applications to see how it goes.

@justinko

Oh man this is much cleaner. Nice stuff.

@maxim

I agree, this always felt awkward from practical perspective. Although depends how you look at it. attr_accessor (and the like) don't include modules either. Wouldn't it be less surprising to let associations behave as accessor declarations? Or why not go further and make accessor methods into modules as well? Just curious where would the line be.

@joshsusser

@maxim: Surprise! Attribute accessor methods are already generated in their own module. I wasn't aware of that either until I dug into the code for this change. With this change, association methods will be consistent with that.

@courtenay

Any time you can get rid of code that looks like class_eval <<-RUBY, __FILE__, __LINE__ + 1 you're having a good day.

@maxim

@joshsusser Weird, doesn't seem to work for me. Unless it's something on the master?

(Trip is an existing ActiveRecord model)

Loading development environment (Rails 3.1.1)
>> class Trip
>> attr_accessor :foo
>> def foo
>> super
>> end
>> end
=> nil
>> Trip.new.foo
NoMethodError: super: no superclass method `foo' for #<Trip:0x007ff54ef8c8c0>
@joshsusser

@maxim: Maybe attr_accessor doesn't work the same way as accessor methods for actual attributes in the database. I'd have to think about whether it makes sense to modularize attr_accessor methods too.

@maxim

@joshsusser not arguing against it though, just curious. It's "simple vs easy" type of thing.

@solnic

@joshsusser It's better to create one such module for a model and then define new readers/writers every time an association is created and include it again. This way you will avoid having a lot of anonymous modules.

@jonleighton
Collaborator

Thanks for doing this - have been meaning to for a while and never got around to it. Will review within the next week.

@joshsusser

Since @solnic and others have asked... the reason for using a module for each association instead of one module for all of them is that it was the simplest way to code it, and there doesn't seem to be any cost for having lots of modules except for a bit of memory used. I believe all current Ruby implementations optimize method lookup so that having a deep inheritance hierarchy costs little to nothing at runtime.

@solnic

@joshsusser the reason I pointed this out was that we did this optimization in DataMapper project and as far as I remember it had a noticeable impact on specs performance. It probably doesn't really matter until you create A LOT of objects. Nevertheless it's a fantastic improvement. I hope it'll get merged in soon.

@josevalim
Owner
@JEG2

It's worth noting that things like attr_accessor and this pull request are different use cases.

For example, if you wanted to use attr_reader (a cousin of attr_accessor, which are both provided by Ruby, not Rails) it is because you are defining a class and want to take a shortcut instead of writing out a rote method definition. This shortcut works when you just want to access the instance variable. If you wanted to do more, say cast the value on the way out, you would just skip the shortcut and define the method normally. It doesn't make sense to have Ruby define the wrong version and you override it in the same class definition.

Now, the code Josh changed is a different scenario. Rails is giving you ways to describe your schema. It will then use that description to build the needed interface methods. It does make perfect sense that you could then need to override those, adding additional behavior to the raw database access Rails is providing.

That's why this change from Josh is such a great idea. I'm all for it.

@josevalim
Owner
@joshsusser

Doing an optimization to bundle all accessor methods in a single module wouldn't be too terrible, but I'd like to prove that this change won't cause problems first before investing that effort. I already have some ideas how to do that optimization, shouldn't be too hard.

@maxim

@JEG2 I like this perspective. No more concerns here. : )

@jonleighton
Collaborator

@joshsusser

I agree with what others have said about using only one module. Ideally we'd have a single module that we can just throw methods into wherever in Active Record they get generated. As you said, the attributes stuff already generates methods in its own module. There's also stuff like nested attributes, composed_of, etc, that all generate methods, and would all benefit from this sort of change in the future.

So I wonder if you could incorporate into your patch a change that defines a method on ActiveRecord::Base which generates/includes this module (like the generated_attribute_methods method in ActiveModel::AttributeMethods). Then, we could redefine generated_attribute_methods in AR to reference this module also.

Perhaps you can generate this module in such a way that it is a named constant, so that it's obvious what it is when people inspect Model.ancestors.

Please also add a CHANGELOG entry for this.

It would also be great to add to the docs to indicate to users that they can extend the generated methods using super.

...ecord/associations/builder/has_and_belongs_to_many.rb
@@ -15,14 +15,10 @@ module ActiveRecord::Associations::Builder
def define_destroy_hook
name = self.name
- model.send(:include, Module.new {
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def destroy_associations
- association(#{name.to_sym.inspect}).delete_all
- super
- end
- RUBY
- })
+ mixin.send(:define_method, :destroy_associations) do
+ association(name).delete_all
+ super()
@jonleighton Collaborator

no need for parens here

Actually, the parens on super() are required to make Ruby 1.9 happy. You can't do super with implicit args in a method created in define_method like that. Ruby 1.9 gets confused about arity - is it the arity of the block or the containing method?

@jonleighton Collaborator

successfully out-geeked ;)

(I created a test to play with this if anyone is reading and interested: https://gist.github.com/1368727)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jonleighton
Collaborator

Another thing: please could you add an explicit test that extending one of these methods via super actually works.

@joshsusser

@jonleighton: Good feedback. I think putting all the association methods in one module is a good way to go. If we're comfortable with this level of change and the potential for disruption I can go ahead and work on that. But I do not think it's a good idea to combine all AR generated methods into a single module for a model class. That will force a particular order for generating methods that may have name clashes. The example in the Rails test fixtures is computers(:workstation).developer - "developer" is the name of both the foreign key field and the belongs_to association. The current order in which those methods get generated breaks the association unless the association methods model/class inherits from the attribute methods module. I think it's better to have a module for attribute methods and a different module for association methods. Offhand I'm not sure what to do for the other kinds of generated methods you mentioned - they might work in one of those modules, or might want to go in another. But I think some kind of precedence ordering should be used, not just the temporal order of method generation.

@jonleighton
Collaborator

Ok, yes, I agree with you about having the attributes module higher up the ancestor chain than the associations module, because we always want attributes to get overridden by other stuff if necessary.

But I think it's okay in theory to use the same module for associations, nested attributes, composed of etc. If those things are clashing with each other I actively want us to be redefining methods in such a way as to trigger a warning in ruby. So maybe you can name you single-module thinger in a generic way in anticipation of the flood of pull requests we are imminently going to receive for those other things.

joshsusser added some commits
@joshsusser joshsusser association methods are now generated in modules
Instead of generating association methods directly in the model
class, they are generated in an anonymous module which
is then included in the model class. There is one such module
for each association. The only subtlety is that the
generated_attributes_methods module (from ActiveModel) must
be forced to be included before association methods are created
so that attribute methods will not shadow association methods.
7cba6a3
@joshsusser joshsusser add test for super-ing to association methods 9cdf33a
@joshsusser

I took a crack at generating all association methods in a single module per model class. It was pretty easy, right up until I ran into the has_and_belongs_to_many destroy_associations hook. HABTM associations have been generating a module per association for quite a while. They do it so that multiple HABTMs in a class can all have their destroy_associations hooks run in series. Note that all those methods have the same name - they are all in separate modules that are ancestors of the same class, and each one calls the next using super. So to change this patch to use one module per class, I'll have to come up with another way to handle this case. Either that or we can punt on this one case - since HABTM isn't used very often, maybe multiple modules won't offend anyone.

Also, I wanted to respond to a few comments above.

1) Programmatically creating modules is not the same as loading a bunch of helper modules from the file system so there shouldn't be any significant impact to startup time. I haven't noticed any running tests.

2) As I mentioned before, having a bunch of modules should have negligible impact on performance. All current Ruby VMs optimize method dispatch so inheritance depth doesn't matter, except maybe while you have cold caches the first time you run a piece of code. That might have a tiny impact on how long tests take to run, but I doubt it would be statistically significant. If you think you are measuring a performance impact from number of modules, you're probably noticing something else.

Anyway, I did add an explicit test that shows a model method using super to call the association method.

@emmanuel

@joshsusser

Nice work!

You might consider making the module 'smart', ie., don't just call Module#define_method on it over and over, give the Module itself methods that define the desired methods in the including (target) class/module. I've just started experimenting with this technique.

It's a bit of a brain-twist at first: you define Module instance methods which in turn dynamically define instance methods in the including class, but it works great. Most importantly, it lets you collect the dynamic method definition logic in one place and name it with intention-revealing selectors :D.

Also worth noting: if you define #inspect on the module, you'll get meaningful output from FooModel.ancestors (more meaningful than #<Module 0x00000>).

@joshsusser

@emmanuel: Cleaning that up is next on my list. I thought about enhancing the module behavior to show the association name. I at least want to make define_method public so there aren't all those sends making the code ugly.

@emmanuel

Making Module#define_method public would help the code read a bit more clearly.

That said, I think it's better still to treat the Module (@mixin) as an object with its own encapsulation. In this case, its responsibility is to define methods according to certain params (ie., Association::Builder#define_*).

In any case, a win for ActiveRecord.

@jonleighton
Collaborator

I think just leave the destroy_associations hook as it is for now, we can fix that up at a later date.

joshsusser added some commits
@joshsusser joshsusser use GeneratedFeatureMethods module for associations 61bcc31
@joshsusser joshsusser changelog & docs for GeneratedFeatureMethods 10834e9
@joshsusser joshsusser avoid warnings
This change uses Module.redefine_method as defined in ActiveSupport.
Making Module.define_method public would be as clean in the code, and
would also emit warnings when redefining an association. That is pretty
messy given current tests, so I'm leaving it for someone else to decide
what approach is better.
124c97f
@joshsusser

I think this change is ready to go. One named module for all associations, docs, and changelog. There is still a separate module for each habtm destroy_associations method, as we agreed. There's a discussion to be had about whether to emit warnings when redefining an association, but that's probably a different change than this one. For now, I fell back to using Module.redefine_method since it is consistent with the old way of defining association methods.

@josevalim josevalim commented on the diff
activerecord/lib/active_record/base.rb
@@ -450,6 +450,20 @@ module ActiveRecord #:nodoc:
:having, :create_with, :uniq, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
+ def inherited(child_class) #:nodoc:
+ # force attribute methods to be higher in inheritance hierarchy than other generated methods
+ child_class.generated_attribute_methods
+ child_class.generated_feature_methods
+ super
+ end
+
+ def generated_feature_methods
@josevalim Owner

Do we set this constant in order to have a readable output on Model.ancestors?

@josevalim Owner

If I am not wrong, we were used to do the same thing for generated_attribute_methods and it caused memory leaks in development. I will try to find the relevant commits.

Yes, the module gets named MyModel::GeneratedFeatureMethods. But ActiveModel doesn't name the attributes module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@josevalim
Owner

Awesome, I took a quick look, everything looks great to me!

activerecord/test/cases/associations_test.rb
@@ -273,3 +274,24 @@ class OverridingAssociationsTest < ActiveRecord::TestCase
)
end
end
+
+class GeneratedMethodsTest < ActiveRecord::TestCase
+ fixtures :developers, :computers, :posts, :comments
+ def test_association_methods_override_attribute_methods_of_same_name
+ assert_equal(developers(:david), computers(:workstation).developer)
+ # this next line will fail if the attribute methods module is generated lazily
+ # after the association methods module is generated
+ assert_equal(developers(:david), computers(:workstation).developer)
+ assert_equal(developers(:david).id, computers(:workstation)[:developer])
+ end
+
+ def test_model_method_overrides_association_method
+ Post.class_eval <<-"RUBY"
@jonleighton Collaborator

Please could you define this stuff directly in models/post.rb? I know it makes the test easier to read by defining it right here, but it's altering global state in a single test which is something we should be avoiding generally.

Other than that looks good to merge to me.

Makes sense to me. Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jonleighton jonleighton merged commit 2169603 into rails:master
@jonleighton
Collaborator

merged! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 16, 2011
  1. @joshsusser

    association methods are now generated in modules

    joshsusser authored
    Instead of generating association methods directly in the model
    class, they are generated in an anonymous module which
    is then included in the model class. There is one such module
    for each association. The only subtlety is that the
    generated_attributes_methods module (from ActiveModel) must
    be forced to be included before association methods are created
    so that attribute methods will not shadow association methods.
  2. @joshsusser
Commits on Nov 28, 2011
  1. @joshsusser
  2. @joshsusser
  3. @joshsusser

    avoid warnings

    joshsusser authored
    This change uses Module.redefine_method as defined in ActiveSupport.
    Making Module.define_method public would be as clean in the code, and
    would also emit warnings when redefining an association. That is pretty
    messy given current tests, so I'm leaving it for someone else to decide
    what approach is better.
Commits on Nov 30, 2011
  1. @joshsusser
This page is out of date. Refresh to see the latest.
View
6 activerecord/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 3.2.0 (unreleased) ##
+* Generated association methods are created within a separate module to allow overriding and
+ composition using `super`. For a class named `MyModel`, the module is named
+ `MyModel::GeneratedFeatureMethods`. It is included into the model class immediately after
+ the `generated_attributes_methods` module defined in ActiveModel, so association methods
+ override attribute methods of the same name. *Josh Susser*
+
* Implemented ActiveRecord::Relation#explain. *fxn*
* Add ActiveRecord::Relation#uniq for generating unique queries.
View
20 activerecord/lib/active_record/associations.rb
@@ -196,6 +196,26 @@ def association_instance_set(name, association)
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
# <tt>Project#categories.delete(category1)</tt>
#
+ # === Overriding generated methods
+ #
+ # Association methods are generated in a module that is included into the model class,
+ # which allows you to easily override with your own methods and call the original
+ # generated method with +super+. For example:
+ #
+ # class Car < ActiveRecord::Base
+ # belongs_to :owner
+ # belongs_to :old_owner
+ # def owner=(new_owner)
+ # self.old_owner = self.owner
+ # super
+ # end
+ # end
+ #
+ # If your model class is <tt>Project</tt>, the module is
+ # named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods module is
+ # is included in the model class immediately after the (anonymous) generated attributes methods
+ # module, meaning an association will override the methods for an attribute with the same name.
+ #
# === A word of warning
#
# Don't create associations that have the same name as instance methods of
View
10 activerecord/lib/active_record/associations/builder/association.rb
@@ -16,6 +16,10 @@ def initialize(model, name, options)
@model, @name, @options = model, name, options
end
+ def mixin
+ @model.generated_feature_methods
+ end
+
def build
validate_options
reflection = model.create_reflection(self.class.macro, name, options, model)
@@ -36,16 +40,14 @@ def define_accessors
def define_readers
name = self.name
-
- model.redefine_method(name) do |*params|
+ mixin.redefine_method(name) do |*params|
association(name).reader(*params)
end
end
def define_writers
name = self.name
-
- model.redefine_method("#{name}=") do |value|
+ mixin.redefine_method("#{name}=") do |value|
association(name).writer(value)
end
end
View
6 activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -25,14 +25,14 @@ def add_counter_cache_callbacks(reflection)
name = self.name
method_name = "belongs_to_counter_cache_after_create_for_#{name}"
- model.redefine_method(method_name) do
+ mixin.redefine_method(method_name) do
record = send(name)
record.class.increment_counter(cache_column, record.id) unless record.nil?
end
model.after_create(method_name)
method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
- model.redefine_method(method_name) do
+ mixin.redefine_method(method_name) do
record = send(name)
record.class.decrement_counter(cache_column, record.id) unless record.nil?
end
@@ -48,7 +48,7 @@ def add_touch_callbacks(reflection)
method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
touch = options[:touch]
- model.redefine_method(method_name) do
+ mixin.redefine_method(method_name) do
record = send(name)
unless record.nil?
View
4 activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -58,7 +58,7 @@ def define_readers
super
name = self.name
- model.redefine_method("#{name.to_s.singularize}_ids") do
+ mixin.redefine_method("#{name.to_s.singularize}_ids") do
association(name).ids_reader
end
end
@@ -67,7 +67,7 @@ def define_writers
super
name = self.name
- model.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
+ mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
association(name).ids_writer(ids)
end
end
View
6 activerecord/lib/active_record/associations/builder/has_many.rb
@@ -28,7 +28,7 @@ def configure_dependency
def define_destroy_dependency_method
name = self.name
- model.send(:define_method, dependency_method_name) do
+ mixin.redefine_method(dependency_method_name) do
send(name).each do |o|
# No point in executing the counter update since we're going to destroy the parent anyway
counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
@@ -45,7 +45,7 @@ class << o
def define_delete_all_dependency_method
name = self.name
- model.send(:define_method, dependency_method_name) do
+ mixin.redefine_method(dependency_method_name) do
send(name).delete_all
end
end
@@ -53,7 +53,7 @@ def define_delete_all_dependency_method
def define_restrict_dependency_method
name = self.name
- model.send(:define_method, dependency_method_name) do
+ mixin.redefine_method(dependency_method_name) do
raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty?
end
end
View
11 activerecord/lib/active_record/associations/builder/has_one.rb
@@ -44,18 +44,17 @@ def dependency_method_name
end
def define_destroy_dependency_method
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
- def #{dependency_method_name}
- association(#{name.to_sym.inspect}).delete
- end
- eoruby
+ name = self.name
+ mixin.redefine_method(dependency_method_name) do
+ association(name).delete
+ end
end
alias :define_delete_dependency_method :define_destroy_dependency_method
alias :define_nullify_dependency_method :define_destroy_dependency_method
def define_restrict_dependency_method
name = self.name
- model.redefine_method(dependency_method_name) do
+ mixin.redefine_method(dependency_method_name) do
raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil?
end
end
View
6 activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -16,15 +16,15 @@ def define_accessors
def define_constructors
name = self.name
- model.redefine_method("build_#{name}") do |*params, &block|
+ mixin.redefine_method("build_#{name}") do |*params, &block|
association(name).build(*params, &block)
end
- model.redefine_method("create_#{name}") do |*params, &block|
+ mixin.redefine_method("create_#{name}") do |*params, &block|
association(name).create(*params, &block)
end
- model.redefine_method("create_#{name}!") do |*params, &block|
+ mixin.redefine_method("create_#{name}!") do |*params, &block|
association(name).create!(*params, &block)
end
end
View
14 activerecord/lib/active_record/base.rb
@@ -450,6 +450,20 @@ class << self # Class methods
:having, :create_with, :uniq, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
+ def inherited(child_class) #:nodoc:
+ # force attribute methods to be higher in inheritance hierarchy than other generated methods
+ child_class.generated_attribute_methods
+ child_class.generated_feature_methods
+ super
+ end
+
+ def generated_feature_methods
@josevalim Owner

Do we set this constant in order to have a readable output on Model.ancestors?

@josevalim Owner

If I am not wrong, we were used to do the same thing for generated_attribute_methods and it caused memory leaks in development. I will try to find the relevant commits.

Yes, the module gets named MyModel::GeneratedFeatureMethods. But ActiveModel doesn't name the attributes module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ unless const_defined?(:GeneratedFeatureMethods, false)
+ include const_set(:GeneratedFeatureMethods, Module.new)
+ end
+ const_get(:GeneratedFeatureMethods)
+ end
+
# Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
View
22 activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -77,7 +77,7 @@ class DeveloperWithCounterSQL < ActiveRecord::Base
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
- :parrots, :pirates, :treasures, :price_estimates, :tags, :taggings
+ :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings
def setup_data_for_habtm_case
ActiveRecord::Base.connection.execute('delete from countries_treaties')
@@ -445,6 +445,26 @@ def test_destroy_all
assert david.projects(true).empty?
end
+ def test_destroy_associations_destroys_multiple_associations
+ george = parrots(:george)
+ assert !george.pirates.empty?
+ assert !george.treasures.empty?
+
+ assert_no_difference "Pirate.count" do
+ assert_no_difference "Treasure.count" do
+ george.destroy_associations
+ end
+ end
+
+ join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}")
+ assert join_records.empty?
+ assert george.pirates(true).empty?
+
+ join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}")
+ assert join_records.empty?
+ assert george.treasures(true).empty?
+ end
+
def test_deprecated_push_with_attributes_was_removed
jamis = developers(:jamis)
assert_raise(NoMethodError) do
View
16 activerecord/test/cases/associations_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/computer'
require 'models/developer'
require 'models/project'
require 'models/company'
@@ -273,3 +274,18 @@ def test_has_one_association_redefinition_reflections_should_differ_and_not_inhe
)
end
end
+
+class GeneratedMethodsTest < ActiveRecord::TestCase
+ fixtures :developers, :computers, :posts, :comments
+ def test_association_methods_override_attribute_methods_of_same_name
+ assert_equal(developers(:david), computers(:workstation).developer)
+ # this next line will fail if the attribute methods module is generated lazily
+ # after the association methods module is generated
+ assert_equal(developers(:david), computers(:workstation).developer)
+ assert_equal(developers(:david).id, computers(:workstation)[:developer])
+ end
+
+ def test_model_method_overrides_association_method
+ assert_equal(comments(:greetings).body, posts(:welcome).first_comment)
+ end
+end
View
9 activerecord/test/cases/base_test.rb
@@ -69,6 +69,15 @@ def setup
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
+ def test_generated_methods_modules
+ modules = Computer.ancestors
+ assert modules.include?(Computer::GeneratedFeatureMethods)
+ assert_equal(Computer::GeneratedFeatureMethods, Computer.generated_feature_methods)
+ assert(modules.index(Computer.generated_attribute_methods) > modules.index(Computer.generated_feature_methods),
+ "generated_attribute_methods must be higher in inheritance hierarchy than generated_feature_methods")
+ assert_not_equal Computer.generated_feature_methods, Post.generated_feature_methods
+ end
+
def test_column_names_are_escaped
conn = ActiveRecord::Base.connection
classname = conn.class.name[/[^:]*$/]
View
1  activerecord/test/models/author.rb
@@ -128,7 +128,6 @@ def testing_proxy_target
belongs_to :author_address, :dependent => :destroy
belongs_to :author_address_extra, :dependent => :delete, :class_name => "AuthorAddress"
- has_many :post_categories, :through => :posts, :source => :categories
has_many :category_post_comments, :through => :categories, :source => :post_comments
has_many :misc_posts, :class_name => 'Post',
View
4 activerecord/test/models/post.rb
@@ -24,6 +24,10 @@ def greeting
belongs_to :author_with_posts, :class_name => "Author", :foreign_key => :author_id, :include => :posts
belongs_to :author_with_address, :class_name => "Author", :foreign_key => :author_id, :include => :author_address
+ def first_comment
+ super.body
+ end
+ has_one :first_comment, :class_name => 'Comment', :order => 'id ASC'
has_one :last_comment, :class_name => 'Comment', :order => 'id desc'
scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} }
Something went wrong with that request. Please try again.