RAILS: How to make new collection.build in a callback? - ruby-on-rails

How to create a new record in after_save using other model?
I tried this line which resulted "undefined method `journals' for nil:NilClass"
e.g.
resources :users do
resource :profile
resources :journals
end
class User < ActiveRecord::Base
has_one :profile
has_many :journals
end
class Profile < ActiveRecord::Base
belongs_to :user
after_save :create_new_journal_if_none
private
def create_new_journal_if_none
if user.journals.empty? ????
user.journals.build() ????
end
end
end
class Journals < ActiveRecord::Base
belong_to :user
end

Nested models are going to be saved as well once parent saves, so it's easy to use before_create block and build a nested resource here.
class Profile < ActiveRecord::Base
belongs_to :user
before_create do
user.journals.build unless user.journals.any?
end
end
This line of code will create a profile and a journal assigned with the User
User.find(1).create_profile(name :test)

Related

Grab all of a User's Active Posts

My instance method of active_posts is not working on my User model.
I am grabbing a user record, then for that record I need to join the blogs and posts table, and then filter by the post's active attribute in order to return only the active posts for that user.
class User < ApplicationRecord
has_many :blogs
has_many :posts, through: :blogs
def active_posts
joins(blogs: :posts).merge(Post.active).distinct
end
end
class Blog < ApplicationRecord
belongs_to :user
has_many :posts
end
class Post < ApplicationRecord
belongs_to :blog
belongs_to :user, through: :blog
scope :active, -> {where(active: true}
end
I run the following:
User.first.active_posts
And here is the error:
NoMethodError
Undefined method 'joins' on the user record
Just realized my error.
Just call posts.active within the active_posts method.
def active_posts
posts.active
end
joins is used for a collection. So you would use joins on a class method in your model, not within an instance method. Simple error.

ActiveRecord polymorphic association with unique constraint

I have a site that allows users to log in via multiple services (LinkedIn, Email, Twitter, etc..).
I have the below structure set up to model a User and their multiple identities. Basically a user can have multiple identieis, but only one of a given type (e.g. can't have 2 Twitter identiteis).
I decided to set it up as a polymorphic relationship, as drawn below. Basically there's a middle table identities that maps a User entry to multiple *_identity tables.
The associations are as follows (shown only for LinkedInIdentity, but can be extrapolated)
# /app/models/user.rb
class User < ActiveRecord::Base
has_many :identities
has_one :linkedin_identity, through: :identity, source: :identity, source_type: "LinkedinIdentity"
...
end
# /app/models/identity
class Identity < ActiveRecord::Base
belongs_to :user
belongs_to :identity, polymorphic: true
...
end
# /app/models/linkedin_identity.rb
class LinkedinIdentity < ActiveRecord::Base
has_one :identity, as: :identity
has_one :user, through: :identity
...
end
The problem I'm running into is with the User model. Since it can have multiple identities, I use has_many :identities. However, for a given identity type (e.g. LinkedIn), I used has_one :linkedin_identity ....
The problem is that the has_one statement is through: :identity, and there's no singular association called :identity. There's only a plural :identities
> User.first.linkedin_identity
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :identity in model User
Any way around this?
I would do it like so - i've changed the relationship name between Identity and the others to external_identity, since saying identity.identity is just confusing, especially when you don't get an Identity record back. I'd also put a uniqueness validation on Identity, which will prevent the creation of a second identity of the same type for any user.
class User < ActiveRecord::Base
has_many :identities
has_one :linkedin_identity, through: :identity, source: :identity, source_type: "LinkedinIdentity"
end
# /app/models/identity
class Identity < ActiveRecord::Base
#fields: user_id, external_identity_id
belongs_to :user
belongs_to :external_identity, polymorphic: true
validates_uniqueness_of :external_identity_type, :scope => :user_id
...
end
# /app/models/linkedin_identity.rb
class LinkedinIdentity < ActiveRecord::Base
# Force the table name to be singular
self.table_name = "linkedin_identity"
has_one :identity
has_one :user, through: :identity
...
end
EDIT - rather than make the association for linkedin_identity, you could always just have a getter and setter method.
#User
def linkedin_identity
(identity = self.identities.where(external_identity_type: "LinkedinIdentity").includes(:external_identity)) && identity.external_identity
end
def linkedin_identity_id
(li = self.linkedin_identity) && li.id
end
def linkedin_identity=(linkedin_identity)
self.identities.build(external_identity: linkedin_identity)
end
def linkedin_identity_id=(li_id)
self.identities.build(external_identity_id: li_id)
end
EDIT2 - refactored the above to be more form-friendly: you can use the linkedin_identity_id= method as a "virtual attribute", eg if you have a form field like "user[linkedin_identity_id]", with the id of a LinkedinIdentity, you can then do #user.update_attributes(params[:user]) in the controller in the usual way.
Here is an idea that has worked wonderfully over here for such as case. (My case is a tad diffferent since all identites are in the same table, subclasses of the same base type).
class EmailIdentity < ActiveRecord::Base
def self.unique_for_user
false
end
def self.to_relation
'emails'
end
end
class LinkedinIdentity < ActiveRecord::Base
def self.unique_for_user
true
end
def self.to_relation
'linkedin'
end
end
class User < ActiveRecord::Base
has_many :identities do
[LinkedinIdentity EmailIdentity].each do |klass|
define_method klass.to_relation do
res = proxy_association.select{ |identity| identity.is_a? klass }
res = res.first if klass.unique_for_user
res
end
end
end
end
You can then
#user.identities.emails
#user.identities.linkedin

Rials:When we use fields_for, how to initiate association_build if we are not sure how many association should be created?

In such case:
class User < ActiveRecord::Base
has_many :companies
accepts_nested_attributes_for :companies
end
class Company < ActiveRecord::Base
belongs_to :users
end
This is nomal case, we're sure how many association we should create:
# user_controller
def new
#user = User.new
2.times { #user.companies.build }
end
But what if we don't know how many association should be created?

Rails 4 & ActiveRecord: prevent dependent destroy if owner exists

Given the following classes:
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
end
class Profile < ActiveRecord::Base
belongs_to :user
end
How do I prevent ActiveRecord from destroying a profile which owner user exists? I mean, it should not be possible to destroy a profile if there's a user who owns it.
I did in this way:
class User < ActiveRecord::Base
has_one :profile
after_destroy :destroy_profile
private
def destroy_profile
profile.destroy
end
end
class Profile < ActiveRecord::Base
belongs_to :user
before_destroy :check_owner_user
def check_owner_user
unless user.nil?
raise CustomException.new("Cannot delete while its owner user exists.")
end
end
end
It seems to me an over worked solution. Does Rails or ActiveRecord provide a better and more concise solution?

Getting undefined method qouted_table_name on associations

I'm having problems fixing association that is working on a rails 2 application. I'm this error on my local machine:
undefined method `quoted_table_name' for Enrollment:Module
I can't seem to figure out where the error is.
Model:
class Enrollment < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :enrollment
accepts_nested_attributes_for :enrollment
end
Controller:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_enrollment
end
end
UPDATE:
Fixed the problem because the name of the model is the same as the name of the rails application.
Did you run rake db:migrate to make sure the tables exist
Make the following change since the nested attributes clause is misplaced
class Enrollment < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :enrollment
# This line belongs here right?
accepts_nested_attributes_for :enrollment
end

Resources