GitHub PushEvent at 2014-10-09 05:39:41
Repository: shoulda-matchers
Actor: mcmire
Payload: {"shas":[["72e2b3411e68ba3b69294d195ef1321cadd1e27d","4978aa66a7291e979a38f3b30da82a328129666c@gmail.com","Fix define_class/model to support namespaces\n\nYou can now say `define_class('Models::User')` and it will define a User\nclass inside of the Models module.\n\nSimilarly, you can also say `define_model('Models::User')` and it will\nset the table name of the model to `models_users` instead of just\n`users`.","Elliot Winkler",true],["4ad15205a5ef49dc3bc19ddb537bd457092a7a50","0b512577b2e5d8dbe72c68a4a0cd811f3eb06aba@liquidcodeworks.com","Fix uniqueness matcher when scope is a *_type attr\n\nSecondary author: Elliot Winkler <elliot.winkler@gmail.com>\n\nThis commit fixes `validate_uniqueness_of` when used with `scoped_to` so\nthat when one of the scope attributes is a polymorphic *_type attribute\nand the model has another validation on the same attribute, the matcher\ndoes not fail with an error.\n\nAs a part of the matching process, `validate_uniqueness_of` tries to\ncreate two records that have the same values for each of the attributes\npassed to `scoped_to`, except one of the attributes has a different\nvalue. This way, the second record should be valid because it shouldn't\nclash with the first one. It does this one attribute at a time.\n\nLet's say the attribute in question is a polymorphic *_type attribute.\nThe value of this attribute is intended to be the name of a model as a\nstring. Let's assume the first record has a meaningful value for this\nattribute already and we're trying to find a value for the second\nrecord. In order to produce such a value, `validate_uniqueness_of`\ngenerally calls #next (a common method in Ruby to generate a succeeding\nversion of an object sequentially) on the first object's corresponding\nvalue. So in the case of a *_type attribute, since it's a string, it\nwould call #next on that string. For instance, \"User\" would become\n\"Uses\".\n\nYou might have noticed a problem with this, which is \"what if Uses is\nnot a valid model?\" This is okay as long as there's nothing that is\ntrying to access the polymorphic association. Because as soon as this\nhappens, Rails will attempt to find a record using the polymorphic type\n-- in other words, it will try to find the model that the *_type\nattribute corresponds to. One of the ways this can happen is if the\n*_type attribute in question has a validation on it itself.\n\nLet's look at an example.\n\nGiven these models:\n\n``` ruby\nclass User < ActiveRecord::Base\nend\n\nclass Favorite < ActiveRecord::Base\n  belongs_to :favoriteable, polymorphic: true\n  validates :favoriteable, presence: true\n  validates :favoriteable_id, uniqueness: { scope: [:favoriteable_type] }\nend\n\nFactoryGirl.define do\n  factory :user\n\n  factory :favorite do\n    association :favoriteable, factory: :user\n  end\nend\n```\n\nand the following test:\n\n``` ruby\nrequire 'rails_helper'\n\ndescribe Favorite do\n  context 'validations' do\n    before { FactoryGirl.create(:favorite) }\n    it do\n      should validate_uniqueness_of(:favoriteable_id).\n        scoped_to(:favoriteable_type)\n    end\n  end\nend\n```\n\nprior to this commit, the test would have failed with:\n\n```\nFailures:\n\n  1) Favorite validations should require case sensitive unique value for favoriteable_id scoped to favoriteable_type\n     Failure/Error: should validate_uniqueness_of(:favoriteable_id).\n     NameError:\n       uninitialized constant Uses\n     # ./spec/models/favorite_spec.rb:6:in `block (3 levels) in <top (required)>'\n```\n\nHere, a Favorite record is created where `favoriteable_type` is set to\n\"Uses\", and then validations are run on that record. The presence\nvalidation on `favoriteable_type` is run which tries to access a \"Uses\"\nmodel. But that model doesn't exist, so the test raises an error.\n\nNow `validates_uniqueness_of` will set the *_type attribute to a\nmeaningful value. It still does this by calling #next on the first\nrecord's value, but then it makes a new model that is simply an alias\nfor the original model. Hence, in our example, Uses would become a model\nthat is aliased to User.","Adrià Planas",true]],"size":2,"ref":"refs/heads/ew-fix-for-203","head":"4ad15205a5ef49dc3bc19ddb537bd457092a7a50"}