1

I have a join table for a has many and belongs to many through, the join table including many other attributes has a timestamp, implementation wise there is no trouble,

User

class User < ApplicationRecord

  has_many :affiliations
  has_many :organizations, through: :affiliations

end

Organization

class Organization < ApplicationRecord

  has_many :affiliations
  has_many :users, through: :affiliations

end

Affiliation

class Affiliation < ApplicationRecord

  belongs_to :user
  belongs_to :organization

  has_many :xxxxxs
end

Affiliation stores not just the belongs, it itself holds information like what the user's rank and what not is in the organization. It is pretty much a strong model of its own.

For fixtures, I do not have a file for the jointable yet,

user.yml

user1:
  email: aaa@aaa.com
  organizations: org1

organization.yml

org1
  name: foo

but when I run tests using minitest, it gives me an error.

Error:
PublicControllerTest#test_should_get_index:
ActiveRecord::StatementInvalid: Mysql2::Error: Field 'created_at' doesn't have a default value: INSERT INTO `affiliations` (`user_id`, `dominion_id`) VALUES (794918725, 299359653)

Odd thing is, it occurs on tests that don't even use the said table,

class PublicControllerTest < ActionController::TestCase

  test "should get index" do
    get :index
    assert_response :success
  end
end

This action does absolutely nothing, at this point its just plain html

class PublicController < ApplicationController
  def index
  end
end

does nothing in the controller.

They go away when a remove the timestamps, but recording when the association was created is necessary information. Is there something I need to do in the tests?

I am using Rails edge (5.0.0rc1) is there any chance that this is causing the errors?

Saifis
  • 2,197
  • 1
  • 22
  • 36
  • Could you put the test by minitest which produces the error? And maybe exact line that causes it? – Pavel Bulanov Jun 10 '16 at 08:20
  • Also - does creation of the same jointable record in your "normal" Rails code works? I.e. is the error specific to minitest run only, and not other code? – Pavel Bulanov Jun 10 '16 at 08:23
  • added models, complete error, actual test, and the controller that it tests, and yes, as code in the project, the whole thing works without any problem – Saifis Jun 10 '16 at 12:17
  • And what if you completely remove get :index from test - will it fail? Can you check fixtures on whether they may contain wrong (old) data? – Pavel Bulanov Jun 10 '16 at 18:47
  • It seems having timestamps will cause all the test that exists fail regardless, as for fixtures I do not have any for affiliation at the moment, since current tests do not cover its actions, I just create the has_many through it. – Saifis Jun 11 '16 at 01:56
  • so you have "organizations: org1" for your user1 in fixtures - can this cause the issue? what if you remove that? because user can be connected to org only through your joint table, right? – Pavel Bulanov Jun 11 '16 at 08:55
  • still getting errors, for now I've removed timestamps to get on with writing test cases and a lot of stuff has changed, putting timestamps back is giving me a different error, ActiveRecord::Fixture::FormatError: ActiveRecord::Fixture::FormatError – Saifis Jun 13 '16 at 05:24
  • Btw, thank you for your time in looking into this, must appreciated. – Saifis Jun 13 '16 at 05:24
  • Oh wait, yes that did take care of it, ....I guess a has man through fixutres doesn't work as a normal association, If you could update the answer, I will mark it as the answer. – Saifis Jun 13 '16 at 05:30
  • Updated in main answer. Also see if [this](http://stackoverflow.com/questions/16548352/rails-dont-generate-create-at-for-fixture) question may help – Pavel Bulanov Jun 13 '16 at 05:47

2 Answers2

2

Update 3.

Having "organizations: org1" for your user1 in fixtures seed data - seems this is causing the issue, because user can be connected to organization only through your joint table.

I didn't find anything explicit in spec, but something relevant here

Fixtures bypass the normal Active Record object creation process. After reading them from YAML file, they are inserted into database directly using insert query. So they skip callbacks and validations check. This also has an interesting side-effect which can be used for drying up fixtures.

Update 2.

I was wrong at assumption that you can't have timestamps in has_and_belongs_to_many jointable managed by Rails. In fact, inside HasAndBelongsToMany Rails will create an ActiveRecord::Base class for that table - here

def through_model
   habtm = JoinTableResolver.build lhs_model, association_name, options
   join_model = Class.new(ActiveRecord::Base) {
     class << self;
     ...

And ActiveRecord::Base include Timestamp module

So your error should be caused by some other way of creating an entry in jointable other then standard Rails association.


Original.

I don't believe that you can have automatically managed timestamp fields in jointable for has_and_belongs_to_many relation in ActiveRecord. This didn't (intentionally) work in old Rails (e.g. 3.2 - link below), and it don't sound like it changed recently.

If you want to have extended join table, you may create a dedicated ActiveRecord model for it and use use has_many :through association. This way it will automatically support timestamps should you add it to table definition.

See https://github.com/rails/rails/issues/4653 for timestamps on HABTM jointable

AFAICT Rails 3.1 does not populate timestamps on a join table. The only difference is that in 3.2, when you add timestamps, they are marked as NOT NULL.

@veganstraightedge the timestamps didn't "work" in 3.1 - they just didn't raise an error when the join table was saved with them as null. the difference here is that in 3.2 timestamps are created with a NOT NULL constraint.

Basically, this can come from an idea that you don't have ActiveRecord model class for the jointable (update 2 - actually you have!), and timestamps are feature of ActiveRecord model. Timestamps in Rails 5.0rc1 hasn't changed a lot - sources - Timestamp is a module that extends ActiveRecord class.

By the way, it's now suggested to use create_join_table migration helper that will create "pure" table (two id's only, no timestamps): https://github.com/rails/rails/pull/4726

SO Question with similiar error - Rails 3.2 + MySQL: Error: Field 'created_at' doesn't have a default value: INSERT INTO

Rails 3.2 doesn't automatically populate the timestamp fields for join tables of :habtm relationships.


Alternatively (warning - theory!), you can try using either Association callbacks or Association extensions - http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Association callbacks

Similar to the normal callbacks that hook into the life cycle of an Active Record object, you can also define callbacks that get triggered when you add an object to or remove an object from an association collection.

class Project
  has_and_belongs_to_many :developers, after_add: :evaluate_velocity

  def evaluate_velocity(developer)
    ...
  end
end

Extensions

The extension argument allows you to pass a block into a has_and_belongs_to_many association. This is useful for adding new finders, > creators and other factory-type methods to be used as part of the association.

has_and_belongs_to_many :contractors do
  def find_or_create_by_name(name)
    first_name, last_name = name.split(" ", 2)
    find_or_create_by(first_name: first_name, last_name: last_name)
  end
end

Extensions can refer to the internals of the association proxy using these three attributes of the proxy_association accessor:

proxy_association.owner returns the object that the association is a part of. 
proxy_association.reflection returns the reflection object that describes the association. 
proxy_association.target returns the associated object for belongs_to or has_one, or the collection of associated objects for has_many or has_and_belongs_to_many.
Community
  • 1
  • 1
Pavel Bulanov
  • 933
  • 6
  • 13
  • sorry for the slow response, added some more info, I currently use a has many through, sorry updated the title – Saifis Jun 10 '16 at 12:19
0

Your relationships between models cannot be represented by a join table. They need to be represented by a regular, model table (and it needs id, created_at, updated_at). They should all be regular, ActiveRecord::Base models.

In this case, you have a join model, Affiliation, and not a join table

So for all three, you need a regular active record base table (containing id, created_at, updated_at)

Join tables are only used for has_and_belongs_to_many associations, and these do allow tables with no id, created_at, updated_at

Marcelo Ribeiro
  • 1,718
  • 1
  • 13
  • 27
  • I believe all of them are normal active record models, the "ApplicationRecord" is what models inherit from in Rails 5, no idea why but they changed the base object names http://blog.bigbinary.com/2015/12/28/application-record-in-rails-5.html – Saifis Jun 13 '16 at 05:33