I have a user and team models joined by a membership model.
One user can have many teams and each team can have many users etc.
class User < ApplicationRecord
has_many :memberships
has_many :teams, through: :memberships
End
class Team < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
End
class Membership < ApplicationRecord
belongs_to :user
belongs_to :team
end
I am looking for a way to simply create a memberships record however am missing something:
2.4.0 :026 > t = Team.last
Team Load (1.2ms) SELECT "teams".* FROM "teams" ORDER BY "teams"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<Team id: 42129, name: "Reds", description: "A good team", created_at: "2017-05-18 05:05:09", updated_at: "2017-05-18 05:05:09">
2.4.0 :027 > User.first.memberships
User Load (1.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
Membership Load (1.1ms) SELECT "memberships".* FROM "memberships" WHERE "memberships"."user_id" = $1 LIMIT $2 [["user_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
2.4.0 :028 > User.first.memberships << t
User Load (0.8ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
(0.4ms) BEGIN
(0.4ms) ROLLBACK
ActiveRecord::AssociationTypeMismatch: Membership(#23507760) expected, got #<Team id: 42129, name: "Reds", description: "A good team", created_at: "2017-05-18 05:05:09", updated_at: "2017-05-18 05:05:09"> which is an instance of Team(#33684520)
from (irb):28
2.4.0 :029 >
What am I missing here?
Update:
When I create a new memberships record and manually add the fk values I get this error when I try to save:
2.4.0 :037 > m
=> #<Membership id: nil, user_id: 1, team_id: 22641, created_at: nil, updated_at: nil>
2.4.0 :038 > m.save
(0.5ms) BEGIN
(0.4ms) ROLLBACK
NoMethodError: undefined method `class_name' for nil:NilClass
Did you mean? class_eval
from (irb):38
Update2
2.4.0 :022 > user = User.first
User Load (2.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 1, created_at: "2017-05-15 08:17:01", updated_at: "2017-05-19 02:54:30">
2.4.0 :023 > team = Team.first
Team Load (2.3ms) SELECT "teams".* FROM "teams" ORDER BY "teams"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Team id: 22641, name: "Reds", description: "This is a good team", created_at: "2017-05-18 01:41:00", updated_at: "2017-05-18 05:05:09">
2.4.0 :027 > Membership.delete_all
SQL (1.9ms) DELETE FROM "memberships"
=> 1
2.4.0 :029 > m=Membership.new(user: user, team: team)
=> #<Membership id: nil, user_id: 1, team_id: 22641, created_at: nil, updated_at: nil>
2.4.0 :031 > m.save
(1.0ms) BEGIN
SQL (2.3ms) INSERT INTO "memberships" ("user_id", "team_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["user_id", 1], ["team_id", 22641], ["created_at", "2017-05-22 03:37:22.803718"], ["updated_at", "2017-05-22 03:37:22.803718"]]
(2.1ms) COMMIT
=> true
2.4.0 :032 > Membership.delete_all
SQL (1.9ms) DELETE FROM "memberships"
=> 1
2.4.0 :033 > m=Membership.new
=> #<Membership id: nil, user_id: nil, team_id: nil, created_at: nil, updated_at: nil>
2.4.0 :034 > m.user_id=user.id
=> 1
2.4.0 :035 > m.team_id=team.id
=> 22641
2.4.0 :036 > m
=> #<Membership id: nil, user_id: 1, team_id: 22641, created_at: nil, updated_at: nil>
2.4.0 :037 > m.save
(0.5ms) BEGIN
User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.8ms) ROLLBACK
NoMethodError: undefined method `class_name' for nil:NilClass
Did you mean? class_eval
from (irb):37
The first error message you're getting (before the Update) is because ActiveRecord is expecting an instance of Membership, and it's getting a Team. User.first.teams << t should work for that example.
As far as the error you shared in the Update, I didn't encounter that when I tried it, and it seemed to work fine with the relationships you gave.
2.2.3 :009 > user = User.first
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> #<User id: 1>
2.2.3 :010 > team = Team.first
Team Load (0.2ms) SELECT "teams".* FROM "teams" ORDER BY "teams"."id" ASC LIMIT 1
=> #<Team id: 1>
2.2.3 :011 > membership = Membership.new(user: user, team: team)
=> #<Membership id: nil, user_id: 1, team_id: 1>
2.2.3 :012 > membership.save
(0.1ms) begin transaction
SQL (1.2ms) INSERT INTO "memberships" ("user_id", "team_id") VALUES (?, ?) [["user_id", 1], ["team_id", 1]]
(2.3ms) commit transaction
=> true
One thing to double check would be in how you're constructing the Membership record, and that there are no pluralization errors in the models. Passing in the actual User and Team instances to Membership.new or Membership.create should work.
Related
What the difference between:
self.implicit_order_column = 'id'
and
default_scope { order('id ASC') }
self.implicit_order_column lets you use another column then the primary key as the implicit ordering column. This effects the way that methods like .first and .last work:
User.class_eval do
self.implicit_order_column = 'created_at'
end
User.first
# => SELECT "users".* FROM "users" ORDER BY "users"."updated_at" ASC LIMIT $1 [["LIMIT", 1]]
User.last
# => SELECT "users".* FROM "users" ORDER BY "users"."updated_at" DESC LIMIT $1 [["LIMIT", 1]]
Setting self.implicit_order_column = 'id' is utterly pointless since the default is the primary key column anyways. The implicit_order_column is of course not used if you provide an explicit order. It does not actually change any other scopes spawned off the class.
default_scope on the other hand tacks on a default scope to any scopes that you spawn off the class.
irb(main):001:0> User.all
(0.5ms) SELECT sqlite_version(*)
User Load (0.1ms) SELECT "users".* FROM "users" LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, role: "admin", created_at: "2020-11-08 19:31:31", updated_at: "2020-11-08 19:31:47">]>
irb(main):002:1* User.class_eval do
irb(main):003:1* default_scope { order(id: :asc) }
irb(main):004:0> end
=> [#<Proc:0x00000000043703a8 (irb):3>]
irb(main):005:0> User.all
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, role: "admin", created_at: "2020-11-08 19:31:31", updated_at: "2020-11-08 19:31:47">]>
irb(main):006:0>
The difference here is not immediately apparent. But if you consider that in the SQL world a query with no order clause will not return the records in a determinate order (its implementation dependent) and here we are actually getting the records in a determinate order. Om many RDBMS the results will be hard to destinguish as they might return the records in the order they are modified (if they feel like it).
Seems brilliant until you realize how non-intuitive default_scope is and how many bugs it leads to down the line.
irb(main):006:0> User.all.order(:created_at)
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC, "users"."created_at" ASC LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, role: "admin", created_at: "2020-11-08 19:31:31", updated_at: "2020-11-08 19:31:47">]>
irb(main):007:0>
Or this example:
irb(main):001:1* User.class_eval do
irb(main):002:1* default_scope { where(admin: true) }
irb(main):003:0> end
=> [#<Proc:0x0000000002bde460 (irb):2>]
irb(main):004:0> User.new
(0.6ms) SELECT sqlite_version(*)
=> #<User id: nil, role: "visitor", created_at: nil, updated_at: nil, admin: true>
Yikes! default_scope is thus widely considered evil.
See:
https://github.com/rails/rails/pull/34480
https://rails-bestpractices.com/posts/2013/06/15/default_scope-is-evil/
https://www.reddit.com/r/rails/comments/ha6gqj/default_scope_is_evil/
After deploying my application I created a new user and noticed that the address bar was reporting number 2 at the end of the url: htt://...herokuapp.com/users/2.
So I checked with rails console in production environment first and then at Heroku. There seemed to be nothing irregular in my production environment (running sqlite3), so I ran heroku run console --sandbox and got the following results:
irb(main):001:0> User.count
(4.9ms) SELECT COUNT(*) FROM "users"
(4.9ms) SELECT COUNT(*) FROM "users"
=> 1
irb(main):002:0> User.first
User Load (2.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
User Load (2.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> #<User id: 2, name: "Mark Homburg", email: "mark.homburg#yahoo.com", created_at: "2015-12-20 13:56:04", updated_at: "2015-12-20 13:56:04", password_digest: "...">
irb(main):001:0> User.find(1)
User Load (1.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
User Load (1.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
ActiveRecord::RecordNotFound: Couldn't find User with 'id'=1
What is irregular at Heroku is the duplicate SELECT FROM "users" action after running User.count, User.first and User.find(1). As I said there was nothing irregular in the corresponding commands in my production environment:
>> User.find(1)
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example#railstutorial.org", created_at: "2015-12-20 10:08:22", updated_at: "2015-12-20 10:08:22", password_digest: "...">
In my opinion this could be explained by the presence of a duplicate users table in the database, as I tried to explain in Duplicate migrations in topic branch.
What is your opinion and what do you suggest I can do?
I ran this in a rails console:
u = User.first
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> #<User id: 4, name: "Administrator", email: "administrator#wdis.com", created_at: "2013-09-27 01:01:54", updated_at: "2013-09-29 23:52:11", password_digest: ".......", remember_token: "........", admin: true, num_credits: nil>
2.0.0p247 :002 > u.num_credits = 1
=> 1
2.0.0p247 :003 > u.num_credits
=> 1
2.0.0p247 :004 > u.save
(0.3ms) begin transaction
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."email") = LOWER('administrator#wdis.com') AND "users"."id" != 4) LIMIT 1
(0.2ms) rollback transaction
=> false
There must be some validation which is stopping this user to be saved. Just check your validations in User model. You can also check the errors on this user using u.errors.full_messages.to_sentence
This Question is more than a single question so breaking it up into more managable pieces: Rails Engines - simple possible engine to (1) add a model and (2) add the association in the containing class
I am testing out building a Rails engine and am curious whether I can add an association to a specific model in the hosting / container app.
The hosting app has a user model class (yes, this will never chnage) and my engine is called abc and I have a model in my engine called posts (so Abc::Post and the table is abc_posts). I'd like to add to the User class in the main app this association. As a drop dead simple try, I created in my engine:
#located in the engine at: abc/app/models/user.rb
class User < ActiveRecord::Base
has_many :abc_posts
end
the post file:
#located in the engine at: abc/app/models/abc/post.rb
module Abc
class Post < ActiveRecord::Base
attr_accessible :body, :header, :user_id
belongs_to :user
end
end
Via rails console, I was able to create records in the table (easy part) but the User class doesn't know about the association. Any ideas on how to get this done?
thx in advance
edit 1
I've tried using the decorators gem as used in forem (see comment below) and have this file:
#abc/app/decorators/lib/abc/user_class_decorator.rb
Object.const_get(User).class_eval do
has_many :abc_posts, :class_name => "Abc::Post", :foreign_key => "user_id"
end
I have included the decorators via:
lib/abc.rb
require "decorators"
but his doesn't seem to be working. Not sure if this is right strategy or whether syntax is even correct.
That should do the job - specify the class for the relationship:
class User < ActiveRecord::Base
has_many :posts, :class_name => "Abc::Post"
end
Hmmm, I created an example and it does work ...
class Parent < ActiveRecord::Base
has_many :children, :class_name => "Abc::Child"
end
The module with the class Child is in the model/abc.
module Abc
class Child < ActiveRecord::Base
belongs_to :parent
end
end
Here the journal
1.9.3-p194 :001 > Parent.create(:name => 'Mr Daddy')
(0.1ms) begin transaction
SQL (9.4ms) INSERT INTO "parents" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", Fri, 03 May 2013 10:49:54 UTC +00:00], ["name", "Mr Daddy"], ["updated_at", Fri, 03 May 2013 10:49:54 UTC +00:00]]
(1.9ms) commit transaction
=> #<Parent id: 1, name: "Mr Daddy", created_at: "2013-05-03 10:49:54", updated_at: "2013-05-03 10:49:54">
1.9.3-p194 :002 > Abc::Child.create(:name => 'Sammy boy', :parent => Parent.first )
Parent Load (0.3ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
(0.1ms) begin transaction
SQL (117.3ms) INSERT INTO "children" ("created_at", "name", "parent_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Fri, 03 May 2013 10:49:58 UTC +00:00], ["name", "Sammy boy"], ["parent_id", 1], ["updated_at", Fri, 03 May 2013 10:49:58 UTC +00:00]]
(2.1ms) commit transaction
=> #<Abc::Child id: 1, name: "Sammy boy", parent_id: 1, created_at: "2013-05-03 10:49:58", updated_at: "2013-05-03 10:49:58">
1.9.3-p194 :003 > Abc::Child.create(:name => 'Milly girl', :parent => Parent.first )
Parent Load (0.3ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
(0.2ms) begin transaction
SQL (0.8ms) INSERT INTO "children" ("created_at", "name", "parent_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Fri, 03 May 2013 10:50:15 UTC +00:00], ["name", "Milly girl"], ["parent_id", 1], ["updated_at", Fri, 03 May 2013 10:50:15 UTC +00:00]]
(2.7ms) commit transaction
=> #<Abc::Child id: 2, name: "Milly girl", parent_id: 1, created_at: "2013-05-03 10:50:15", updated_at: "2013-05-03 10:50:15">
1.9.3-p194 :004 > Parent.first.children.first
Parent Load (0.4ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
Abc::Child Load (0.3ms) SELECT "children".* FROM "children" WHERE "children"."parent_id" = ? ORDER BY "children"."id" ASC LIMIT 1 [["parent_id", 1]]
=> #<Abc::Child id: 1, name: "Sammy boy", parent_id: 1, created_at: "2013-05-03 10:49:58", updated_at: "2013-05-03 10:49:58">
1.9.3-p194 :005 > Parent.first.children.last
Parent Load (0.5ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
Abc::Child Load (0.4ms) SELECT "children".* FROM "children" WHERE "children"."parent_id" = ? ORDER BY "children"."id" DESC LIMIT 1 [["parent_id", 1]]
=> #<Abc::Child id: 2, name: "Milly girl", parent_id: 1, created_at: "2013-05-03 10:50:15", updated_at: "2013-05-03 10:50:15">
1.9.3-p194 :006 > Parent.first.children.count
Parent Load (0.3ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
(0.3ms) SELECT COUNT(*) FROM "children" WHERE "children"."parent_id" = ? [["parent_id", 1]]
=> 2
Ok..I hope I am doing something dumb (which is usually the case). I have been humming along, adding cache counters to my existing project, when all of the sudden reset_counters failed for pretty much everything. I checked out an old copy when things were working, and it still failed, so I started on a brand new demo project.
I assume this is my error because it suddenly stopped working..and I don't see anyone else having similar issues.
I am using ruby 1.9.3-p392, and rails 3.2.12.
The counters themselves work on both existing and new projects. you just can't use reset_counters.
So here is the problem on a brand new project:
class Post < ActiveRecord::Base
attr_accessible :name
has_many :comments
end
class Comment < ActiveRecord::Base
attr_accessible :name
belongs_to :post, :counter_cache => true
end
here we can see the counters increment:
irb(main):073:0* post = Post.create(:name => 'i am a post')
(0.3ms) BEGIN
SQL (0.4ms) INSERT INTO `posts` (`comments_count`, `created_at`, `name`, `updated_at`) VALUES (0, '2013-03-14 19:56:53', 'i am a post', '2013-03-14 19:56:53')
(0.5ms) COMMIT
=> #<Post id: 3, name: "i am a post", created_at: "2013-03-14 19:56:53", updated_at: "2013-03-14 19:56:53", comments_count: 0>
irb(main):074:0> post.comments << Comment.new(:name => 'i am a comment')
(0.2ms) BEGIN
SQL (0.3ms) INSERT INTO `comments` (`created_at`, `name`, `post_id`, `updated_at`) VALUES ('2013-03-14 19:57:18', 'i am a comment', 3, '2013-03-14 19:57:18')
Post Load (0.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 3 LIMIT 1
SQL (0.3ms) UPDATE `posts` SET `comments_count` = COALESCE(`comments_count`, 0) + 1 WHERE `posts`.`id` = 3
(0.4ms) COMMIT
Comment Load (0.2ms) SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 3
=> [#<Comment id: 3, name: "i am a comment", post_id: 3, created_at: "2013-03-14 19:57:18", updated_at: "2013-03-14 19:57:18">]
irb(main):075:0> post = Post.find(3)
Post Load (0.5ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 3 LIMIT 1
=> #<Post id: 3, name: "i am a post", created_at: "2013-03-14 19:56:53", updated_at: "2013-03-14 19:56:53", comments_count: 1>
running the code I get:
irb(main):001:0> Post.reset_counters(1,:comments_count)
Post Load (0.5ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1
NoMethodError: undefined method `options' for nil:NilClass
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/activerecord-3.2.12/lib/active_record/counter_cache.rb:22:in `block in reset_counters'
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/activerecord-3.2.12/lib/active_record/counter_cache.rb:19:in `each'
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/activerecord-3.2.12/lib/active_record/counter_cache.rb:19:in `reset_counters'
from (irb):1
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/railties-3.2.12/lib/rails/commands/console.rb:47:in `start'
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/railties-3.2.12/lib/rails/commands/console.rb:8:in `start'
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/railties-3.2.12/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
You're almost entirely correct. The parameter in reset_counters though should be the object name, not the column name. So in your case you want to:
1.9.3-p392 :005 > Post.reset_counters(1, :comments)
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1 [["id", 1]]
(0.1ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 1
(2.3ms) UPDATE "posts" SET "comments_count" = 1 WHERE "posts"."id" = 1
=> true