diff --git a/lib/active_record/virtual_attributes.rb b/lib/active_record/virtual_attributes.rb index fec76aa..ae59db3 100644 --- a/lib/active_record/virtual_attributes.rb +++ b/lib/active_record/virtual_attributes.rb @@ -56,7 +56,7 @@ def virtual_column(name, type:, **options) virtual_attribute(name, type, **options) end - def virtual_attribute(name, type, through: nil, uses: nil, arel: nil, source: name, default: nil, **options) + def virtual_attribute(name, type, through: nil, uses: nil, arel: nil, source: name, default: nil, **options, &block) name = name.to_s reload_schema_from_cache @@ -77,6 +77,10 @@ def virtual_attribute(name, type, through: nil, uses: nil, arel: nil, source: na # Because we may not have loaded the class yet # And we definitely have not loaded the database yet arel ||= virtual_delegate_arel(source, to_ref) + elsif block_given? + define_method(name) do + has_attribute?(name) ? self[name] : instance_eval(&block) + end end define_virtual_include(name, uses) diff --git a/spec/db/models.rb b/spec/db/models.rb index 3046a85..24bdaa6 100644 --- a/spec/db/models.rb +++ b/spec/db/models.rb @@ -61,32 +61,19 @@ def books_with_authors virtual_total :total_named_books, :named_books alias v_total_named_books total_named_books - def nick_or_name - has_attribute?("nick_or_name") ? self["nick_or_name"] : nickname || name - end - - # sorry. no creativity on this one (just copied nick_or_name) - def name_no_group - has_attribute?("name_no_group") ? self["name_no_group"] : nickname || name - end # a (local) virtual_attribute without a uses, but with arel virtual_attribute :nick_or_name, :string, :arel => (lambda do |t| - t.grouping(Arel::Nodes::NamedFunction.new('COALESCE', [t[:nickname], t[:name]])) - end) - - # We did not support arel returning something other than Grouping. - # this is here to test what happens when we do - virtual_attribute :name_no_group, :string, :arel => (lambda do |t| Arel::Nodes::NamedFunction.new('COALESCE', [t[:nickname], t[:name]]) - end) - - def first_book_name - has_attribute?("first_book_name") ? self["first_book_name"] : books.first.name + end) do + nickname || name end - def first_book_author_name - has_attribute?("first_book_author_name") ? self["first_book_author_name"] : books.first.author_name + # This tests that we still support defining arel lambdas that return a Grouping. + virtual_attribute :name_no_group, :string, :arel => (lambda do |t| + t.grouping(Arel::Nodes::NamedFunction.new('COALESCE', [t[:nickname], t[:name]])) + end) do + nickname || name end def upper_first_book_author_name @@ -104,9 +91,14 @@ def book_with_most_bookmarks virtual_has_one :book_with_most_bookmarks, :uses => {:books => :bookmarks} # attribute using a relation - virtual_attribute :first_book_name, :string, :uses => [:books] + virtual_attribute :first_book_name, :string, :uses => [:books] do + books.first.name + end # attribute on a double relation - virtual_attribute :first_book_author_name, :string, :uses => {:books => :author_name} + virtual_attribute :first_book_author_name, :string, :uses => {:books => :author_name} do + books.first.author_name + end + # uses another virtual attribute that uses a relation virtual_attribute :upper_first_book_author_name, :string, :uses => :first_book_author_name # :uses points to a virtual_attribute that has a :uses with a hash