I have two models:
class Post < ApplicationRecord
has_one :metric, dependent: :destroy
end
class Metric < ApplicationRecord
belongs_to :post
end
And for some reason that I don't fully understand, I can't create a new metric through a post:
> post = Post.first 1
> post.metric # => Nil
> post.metric.create # NoMethodError (undefined method `create' for nil:NilClass)
Do I have to declare anything else any of my models to make this work?
You can't call create method on nil class.
Active Record Associations DOC
post = Post.first 1
post.build_metric # will be automatically assigned post_id to metrics but not saved
post.save
#or
post.create_metric # will be automatically assigned post_id to metrics and saved
Alternate solution:
Metric.create(post_id: post.id)
What you're doing is
post = Post.first 1
# You don't tell us what this evaluates to, but I'm assuming a post as the #metric call works
post.metric # => Nil
# You have nil
post.metric.create
# Now you're calling the create method on nil.
What you need to do is
Metric.create(post_id: Post.first.id, etc_attribute: etc_value, ...)
EDIT: Also what 7urkm3n said in his comment-- build_metric and create_metric are cleaner solutions that utilize Rails magic.
Related
I have issue when try to get parent model from child as below:
Post model
class Post < ApplicationRecord
has_many :post_votes, dependent: :destroy
accepts_nested_attributes_for :post_votes
end
PostVote model
class PostVote < ApplicationRecord
belongs_to :post, optional: true
end
Get parent funtion
def find_owner_post_vote_for_user(user)
#owner_post = Post.first
#owner_post_vote = PostVote.first
if user.id.present?
#owner_post_vote = PostVote.where(user_id: user.id)
#owner_post = #owner_post_vote.post
end
return #owner_post
end
Error log:
ActionView::Template::Error (undefined method `post' for #<PostVote::ActiveRecord_Relation:0x00007fffc23f9c78>):
Get child model #post.post_votes is OK, but get parent model is false.
Any can help me fix this problem? Thank so much!
PostVote.where(user_id: user.id) will return a collection of records. So, on the very next line when you do #owner_post_vote.post It's trying to find a post for a collection of records ActiveRecord_Relation.
if you want to use where, then you should iterate through the collection. Or, you can you find to get a single record. Then you can do #owner_post_vote.post.
Option 1:
if user.id.present?
#owner_post_vote = PostVote.find(user_id: user.id)
#owner_post = #owner_post_vote.post
end
Option 2:
if user.id.present?
#owner_post_vote = PostVote.where(user_id: user.id).first
#owner_post = #owner_post_vote.post
end
Option 3:
if user.id.present?
#owner_post_votes = PostVote.where(user_id: user.id)
#owner_post_votes.each do |post_vote|
# post_vote.post is accessible here
end
end
I have two models: Users and PaymentMethods, the association between this models is:
class User < ApplicationRecord
has_many :payment_methods, dependent: :destroy
end
class PaymentMethod < ApplicationRecord
belongs_to :user, optional: true
end
I want to loop in each user and see in an attribute of PaymentMethod, named 'period_end_date'. so I do this:
#users = User.all
#users.each do |u|
u.payment_methods.last.period_end_date
end
I'm getting this error => NoMethodError: undefined method `payment_methods' for User::ActiveRecord_Relation
The error is shown because I have 2 test users, in the first user there is still no data in the attribute 'period_end_date' and association exist, but is empty, in the second user there is data in the attributes, if I say, u.payment_methods.last.period_end_date I get => Wed, 13 Jun 2018 (only in the second user)
I want to filter in my loop only the users who has data in PaymentMethod attributes for get rid of => NoMethodError: undefined method `payment_methods' for User::ActiveRecord_Relation
How I do this?
thanks
I want to filter in my loop only the users who has data in PaymentMethod attributes for get rid of => NoMethodError: undefined method `payment_methods' for User::ActiveRecord_Relation
The actual problem seems to be you have users without payment methods (see my comment on your question).
You have some options, depending on how you're going to use the results.
1) You can filter out users without payment methods when you query them from the database like this:
#users = User.joins :payment_methods
2) If #users must include users that without payment methods, you can skip them when looping like this:
#users.map do |user|
next unless user.payment_methods.any?
user.payment_methods.last.period_end_date
end
3) You can guard by checking for payment_methods before calling .last.
User.all.map do |user|
user.payment_methods.last.period_end_date if user.payment_methods.any?
end
4) You can add a period_end_date method to the user
class User < ApplicationRecord
def period_end_date
payment_methods.limit(1).pluck :period_end_date
end
end
5) push #4 into the association by extending it with a helper method
class User < ApplicationRecord
has_many :payment_methods, class_name: 'PaymentMethod' do
def last_period_end_date
last.period_end_date if any?
end
end
end
which you can call like this
User.all.map do |user|
user.payment_methods.last_period_end_date
end
If you're really only concerned about PaymentMethods without a period_end_date then try this:
6) You can still filter users when you query them from the database
#users = User.joins(:payment_methods).where.not(payment_methods: { period_end_date: nil })
7) This can be simplified a bit by pushing the where.not conditions into a scope of the PaymentMethod class:
class PaymentMethod < ApplicationRecord
scope :period_ends, -> { where.not period_end_date: nil }
end
and merging it
#users = User.joins(:payment_methods).merge PaymentMethod.period_ends
Notes
payment_methods.last doesn't specify an order, you should set one (either as part of this chain, when you specify the association, or with a default scope) otherwise the order is up to your database and may be indeterminate.
chain .includes(:payment_methods) to eager load the payment methods and avoid n+1 queries
it sounds like a nil period_end_date could be invalid data. Consider adding a validation / database constraint to prevent this from happening
I have watched Sandi Metz' nothing is something presentation a couple times now. I realize I do nil checks all over the place in my rails projects, but I don't know how to avoid nil checks and how to do it the object oriented way when it comes to associations.
Consider these associations:
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :blog
end
#app/models/blog.rb
class Blog < ActiveRecord::Base
belongs_to :hash_tag
has_one :user
end
#app/models/hash_tag.rb
class HashTag < ActiveRecord::Base
has_one :blog
end
I grab a user:
#user = User.find(1)
And I want to find his blog:
#user.blog
=> nil
It returns nil here because this user happens to have no associated blog, so the following code would break the application if I did something like this for this user:
#user.blog.title
=> undefined method `title' for nil:NilClass
The simple fix is to do it the non-object-oriented way and just do a nil check (but this is what we want to avoid because otherwise nil checks are absolutely everywhere in the application):
#user.blog.title if #user.blog
Doing nil checks gets more cumbersome and procedural the deeper you go through associations like below:
#user = User.find(1)
if #user.blog && #user.blog.hash_tag #checking for nil twice
#user.blog.hash_tag.tag_name
end
What is the object-oriented way of avoiding nil checks for associations?
I am aware of rails' try method, though Metz did not seem to recommend the try method. Perhaps though when it comes to associations in rails: try is the best option?
There is programming design guideline called The Law of Demeter
This is how to apply it to your rails models:
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :blog
delegate :title, to: :blog, prefix: true, allow_nil: true
# then you can call user.blog_title
delegate :tag_name, to: :blog, prefix: true, allow_nil: true
# follow LoD
end
#app/models/blog.rb
class Blog < ActiveRecord::Base
belongs_to :hash_tag
delegate :tag_name, to: :hash_tag, allow_nil: true
has_one :user
end
And now you can do this:
#user = User.find(1)
#user.blog_title # no error even if there is no associated blog
#user.tag_name # no error even if there is no associatd blog or no associated hash_tag object
Please read following link for references:
http://apidock.com/rails/Module/delegate
http://samurails.com/tutorial/rails-delegate-dont-break-the-law-of-demeter/
There is a nifty gem andand which I use in cases like this. Normally you would need to write:
#user.blog && #user.blog.title
andand gem implements generic null pattern. andand extends Object with a method andand, which returns an object it was called on unless it is nil. In case of the nil, NullPatern object is returned to return nil for all method calls using method_missing magic.
#user.blog.andand.title
It is even more powerful when you need to check more conditions:
#user.blog && #user.blog.title && #user.blog.title.capitalize
becomes:
#user.blog.andand.title.andand.capitalize
I think this could be an options
I would use: rescue method
Caution: this method catches exceptions and responds accordingly; Don't use this if you need to respond to exceptions differently. I hope you understand
#user = User.find(1)
tag_name = #user.blog.hash_tag.tag_name rescue '' # respond with default value
Example:
2.1.1 :001 > var1 = nil
=> nil
2.1.1 :002 > my_val = var1.some_method
NoMethodError: undefined method `some_method' for nil:NilClass
from (irb):2
from /home/shiva/.rvm/rubies/ruby-2.1.1/bin/irb:11:in `<main>'
2.1.1 :003 > my_val = var1.some_method rescue ''
=> ""
Another option would be .try method; http://apidock.com/rails/Object/try
.try is defined in Rails so not be available out of Rails
#person.non_existing_method if #person.respond_to?(:non_existing_method)
# instead use
#person.try(:non_existing_method) # => returns nil if does not respond_to
try returns nil when called on nil regardless of whether it responds to the method:
nil.try(:to_i) # => nil, rather than 0
I'm using Mongoid version 2.8.1. I've noticed that the destroy method does not work when the model has a has_and_belongs_to_many relationship.
For example, I have two models
class Article
include Mongoid::Document
has_and_belongs_to_many :subjects
...
end
and
class Subject
include Mongoid::Document
has_and_belongs_to_many :articles
...
end
Now I want to delete an article document. So I tried
a = Article.find('someid1234')
this returns a valid object, then I do
>> a.destroy
=> true
>> a.errors.any?
=> false
>> a.errors.count
=> 0
But when I do
a.reload
I still get the object back!
If I use
a.delete
instead, it would work, but delete doesn't run callbacks, and I want to run callbacks.
I have nailed this down to the has_and_belongs_to_many relationship. Because of this relationship, destroy invokes a callback method.
Article._destroy_callbacks
=> [#<ActiveSupport::Callbacks::Callback:0x007fc4a0e71258 #klass=Shortlist, #kind=:after, #chain=[...], #per_key={:if=>[], :unless=>[]}, #options={:if=>[], :unless=>[]}, #raw_filter=#<Proc:0x007fc4a0e714d8#/opt/boxen/rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/mongoid-2.4.12/lib/mongoid/relations/synchronization.rb:160>, #filter="_callback_after_31(self)", #compiled_options="true", #callback_id=32>]
The method in question in /opt/boxen/rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/mongoid-2.4.12/lib/mongoid/relations/synchronization.rb:160
is
def synced_destroy(metadata)
tap do
set_callback(
:destroy,
:after
) do |doc|
doc.remove_inverse_keys(metadata)
end
end
end
After I call a.destroy, a's id is removed from its subjects' article_ids array fields. But article a is not destroyed. So it seems the callback executes correctly, but the object is not destroyed afterwards.
I debugged this problem by looking at mongoid's source code. The destroy method looks like
def destroy(options = {})
self.flagged_for_destroy = true
run_callbacks(:destroy) do
remove(options) # THIS IS NOT EXECUTED!
end.tap do
self.flagged_for_destroy = false
end
end
def remove(options = {})
Operations.remove(self, options).persist
end
alias :delete :remove
The comment is mine. This seems to be a bug with Mongoid, that destroy only executes the callbacks, and does not destroy the object itself.
However, when there's no callback methods for destroy (for example, on a model without
the has_and_belongs_to_many relationship), the object is destroyed correctly. Strange
Has anyone has experienced the same problem and if there's any workaround.
Thanks.
It could be that :article must be referenced instead of Article. Keep in mind that doing Article.new doesn't automatically give a relation. The mongoid relation doc says you need something like the following
class Person
include Mongoid::Document
embeds_many :addresses
end
person.addresses = [ address ]
a work around is add manual deletes to the modals
after_destroy :delete_self!
def delete_self!
if persisted?
self.delete
end
It seems that when a child object has a reference to its parent in its setter, it fails to get initialized unless the foreign key is given first in the parameter hash.
class Bot < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :bot
def name=(text)
write_attribute(:name, "#{self.bot.name}'s #{text}")
end
end
Item.new(:name => 'pitchfork', :bot_id => 1, ... )
# => undefined method `name' for nil:NilClass (NoMethodError)
Item.new(:bot_id => 1, :name => 'pitchfork', ... )
# => #<Item id: nil, bot_id: 1, name: "r2d2's pitchfork", ... >
Note that the order of hash keys is preserved in Ruby 1.9, but the point is, bot_id must be set before the accessor that has a reference to its parent.
So, the following code works too:
item = Item.new
item.bot_id = 1
item.attributes = { :name => 'pitchfork', ... }
What's really annoying is that the build method on has_many collection doesn't work either, which I think is the right place to patch if I'd have to.
Bot.find(1).items.build(:name => 'pitchfork')
# => undefined method `name' for nil:NilClass (NoMethodError)
What's the best idea to get around this, or patch this, or am I doing anything wrong here?
You could move the string merging to an after_update callback. That way you won't have to access the Bot model until after it's properly setup.
However, I would probably keep name as a simple string and then add a virtual attribute for the merged string. That way it's also updated if the name of Bot is changed.
class Item < ActiveRecord::Base
belongs_to :bot
def full_name
#full_name ||= "#{bot.name}'s #{name}"
end
end