Files
prism/test/test_orm_nested_attributes.rb

167 lines
5.8 KiB
Ruby

require 'minitest/autorun'
require 'prism'
require 'tmpdir'
class OrmNestedAttributesTest < Minitest::Test
def setup
@dir = Dir.mktmpdir('prism-nested-attrs-store')
@store = Prism::Orm::Store.new(File.join(@dir, 'test.pstore'))
@store.create_table(:posts, %w[id title])
@store.create_table(:comments, %w[id body post_id])
@store.create_table(:accounts, %w[id owner_id plan])
@store.create_table(:users, %w[id name])
post_class = Class.new(Prism::Orm::Model) do
table_name :posts
has_many :comments, class_name: 'Comment', foreign_key: 'post_id', dependent: :destroy
accepts_nested_attributes_for :comments, allow_destroy: true, reject_if: :all_blank, limit: 3
end
comment_class = Class.new(Prism::Orm::Model) do
table_name :comments
belongs_to :post, class_name: 'Post', foreign_key: 'post_id'
validates :body, presence: true, length: { minimum: 2 }
end
user_class = Class.new(Prism::Orm::Model) do
table_name :users
has_one :account, class_name: 'Account', foreign_key: 'owner_id'
accepts_nested_attributes_for :account
end
account_class = Class.new(Prism::Orm::Model) do
table_name :accounts
belongs_to :owner, class_name: 'User', foreign_key: 'owner_id'
validates :plan, presence: true
end
[post_class, comment_class, user_class, account_class].each { |klass| klass.store = @store }
Object.const_set(:Post, post_class)
Object.const_set(:Comment, comment_class)
Object.const_set(:User, user_class)
Object.const_set(:Account, account_class)
end
def teardown
FileUtils.remove_entry(@dir)
%i[Post Comment User Account].each { |name| Object.send(:remove_const, name) if Object.const_defined?(name) }
end
def test_builds_and_saves_new_children_of_a_new_parent
post = Post.create!(
title: 'hello',
comments_attributes: [{ body: 'first' }, { body: 'second' }]
)
assert_equal 2, Comment.where(post_id: post.id).count
bodies = Comment.where(post_id: post.id).map(&:body).sort
assert_equal %w[first second], bodies
end
def test_accepts_indexed_hash_form_like_a_rails_form_builder
post = Post.create!(
title: 'hello',
comments_attributes: { '0' => { body: 'first' }, '1' => { body: 'second' } }
)
assert_equal 2, Comment.where(post_id: post.id).count
end
def test_updates_an_existing_child_by_id
post = Post.create!(title: 'hello')
comment = Comment.create!(body: 'original', post_id: post.id)
post.update!(comments_attributes: [{ id: comment.id, body: 'edited' }])
assert_equal 'edited', comment.reload.body
end
def test_allow_destroy_removes_the_child
post = Post.create!(title: 'hello')
comment = Comment.create!(body: 'doomed', post_id: post.id)
post.update!(comments_attributes: [{ id: comment.id, _destroy: '1' }])
assert_nil Comment.store.find(Comment.table_name, comment.id)
end
def test_destroy_flag_is_ignored_without_allow_destroy
no_destroy_post_class = Class.new(Prism::Orm::Model) do
table_name :posts
has_many :comments, class_name: 'Comment', foreign_key: 'post_id'
accepts_nested_attributes_for :comments
end
no_destroy_post_class.store = @store
post = no_destroy_post_class.create!(title: 'hello')
comment = Comment.create!(body: 'kept', post_id: post.id)
post.update!(comments_attributes: [{ id: comment.id, _destroy: '1', body: 'still here' }])
refute_nil Comment.store.find(Comment.table_name, comment.id)
assert_equal 'still here', comment.reload.body
end
def test_reject_if_all_blank_skips_empty_new_records
post = Post.create!(title: 'hello', comments_attributes: [{ body: '' }, { body: 'real' }])
assert_equal 1, Comment.where(post_id: post.id).count
assert_equal 'real', Comment.where(post_id: post.id).first.body
end
def test_limit_raises_when_exceeded
assert_raises(Prism::Orm::NestedAttributes::TooManyRecords) do
Post.create!(title: 'hello', comments_attributes: [{ body: 'a' }, { body: 'b' }, { body: 'c' }, { body: 'd' }])
end
end
def test_invalid_nested_record_blocks_the_whole_save_and_reports_errors
post = Post.new(title: 'hello', comments_attributes: [{ body: 'good enough' }, { body: 'x' }])
refute post.save
assert post.new_record?
assert post.errors.key?(:comments)
assert_equal 0, Comment.count
end
def test_id_pointing_at_another_parents_child_is_rejected_not_hijacked
post_a = Post.create!(title: 'a')
post_b = Post.create!(title: 'b')
foreign_comment = Comment.create!(body: 'belongs to a', post_id: post_a.id)
refute post_b.update(comments_attributes: [{ id: foreign_comment.id, body: 'hijacked' }])
assert_equal 'belongs to a', foreign_comment.reload.body
assert_equal post_a.id, foreign_comment.reload.post_id
end
def test_id_on_a_brand_new_parent_is_rejected
post = Post.new(title: 'hello', comments_attributes: [{ id: 999, body: 'ghost' }])
refute post.save
assert post.errors.key?(:comments)
end
def test_has_one_nested_attributes_builds_the_single_record
user = User.create!(name: 'ada', account_attributes: { plan: 'pro' })
account = Account.find_by(owner_id: user.id)
refute_nil account
assert_equal 'pro', account.plan
end
def test_has_one_nested_attributes_updates_the_existing_record
user = User.create!(name: 'ada')
account = Account.create!(plan: 'free', owner_id: user.id)
user.update!(account_attributes: { id: account.id, plan: 'pro' })
assert_equal 'pro', account.reload.plan
end
def test_does_not_support_associations_other_than_has_many_or_has_one
assert_raises(ArgumentError) do
Class.new(Prism::Orm::Model) do
table_name :comments
belongs_to :post, class_name: 'Post', foreign_key: 'post_id'
accepts_nested_attributes_for :post
end
end
end
end