How can I create all has_one relationships automatically? - ruby-on-rails

I have the following models:
class Post < ApplicationRecord
has_one: :basic_metric
has_one: :complex_metric
end
class BasicMetric < ApplicationRecord
belongs_to :post
end
class ComplexMetric < ApplicationRecord
belongs_to :post
end
Once a post is created, both basic_metric and complex_metric are nil:
p = Post.first
p.basic_metric # => nil
p.complex_metric # => nil
And because of how my app is going to work, the BasicMetricsController and ComplexMetricsController only have the update method. So I would like to know if there is a way to create them as soon as a post is created.

One very common way of accomplishing this is using ActiveRecord callbacks
class Post
after_create :create_metrics
private
def create_metrics
# methods created by has_one, suggested in the comments
create_basic_metric(additional_attrs)
create_complex_metric(additional_attrs)
end
end
Another option you have is to overwrite the method created by has_one, i.e.:
class Post
has_one: :basic_metric
has_one: :complex_metric
def basic_metric
super || create_basic_metric(additional_attrs)
end
def complex_metric
super || create_complex_metric(additional_attrs)
end
end
This way they won't be created automatically with any new post, but created on demand when the method is called.

Can you try this one,
post = Post.first
post.build_basic_metric
post.build_complex_metric
This will help you to build/create the has_one association object if record not saved by default use post.save at the end.
If you need this in modal you can use like this,
class Post
after_create :build_association_object
private
def create_metrics
self.build_basic_metric
self.build_complex_metric
# save # (optional)
end
end

Related

Run model method after user creates a object in controller

I'm looking for help with a problem I'm facing. I have a referral program and i would like to run a method when the user who was referred creates a Post. In my user.rb file I have:
def complete_referral!
update(referral_completed_at: Time.zone.now)
end
And my PostsController has:
def create
#post = current_user.posts.build(post_params)
#post.save
end
Some ideas I've tried was trying to run the complete_referral! method on post.rb like so:
after_create :complete_referral!
but that just raised an undefined method complete_referral!' for #<Post:0x00007ff65b0514d8> error.
Any ideas would be appreciated.
You could also use an association callback:
class Post < ApplicationRecord
belongs_to :referrer, class_name: 'User', optional: true
end
class User < ApplicationRecord
has_many :referred_posts,
class_name: 'Post',
foreign_key: 'referrer_id',
after_add: :complete_referral!
end
The callback is then called when you create a post from the association:
#post = User.find(some_param).referred_posts.new(post_params)
The reason I would use this instead of a regular callback is that it does not create a hard dependency from Post to User that you have to address in all your tests when you just want to create a post record.
You need to define the callback in the Post class, e.g.
class Post < ApplicationRecord
after_create :complete_referral!
# ...
private
def complete_referral!
user.complete_referral!
end
end
The other answers use callbacks, and I love callbacks in my models, if the callbacks are necessary to keep my data in a correct state. In this case I would prefer to do this in the controller, because only if a post is saved through the controller (by a user), do we want to call complete_referral! on the user, not when we create a post in the tests or in the console (or on import etc.etc.)
So in that case in the PostsController we write
def create
#post = current_user.posts.build(post_params)
if #post.save
current_user.complete_referral!
end
end

Get unsaved associations from inside the model

How can I access unsaved associations from inside a model method? For example:
class Post < ActiveRecord::Base
has_many :comments
def comment_dates
comments.pluck(:created_at).sort
end
end
The comments method called inside a model method returns just already saved associations. When I call the method from the object, e.g. post.comments it returns both saved and unsaved associations.
How can I access saved and unsaved associations from inside a model method? I need this to do some complex validation including associations.
How about something like this?
class Post < ActiveRecord::Base
has_many :comments
def comment_dates
comments.pluck(:created_at).sort
end
def comments_dates_no_query
comments.map(&:created_at).sort
end
def unsaved_comments
comments.reject(&:persisted?)
end
def saved_comments
comments.select(&:persisted?)
end
end
You can us it like
post.saved_comments
or
post.unsaved_comments
Hope that helps!

How to create your own methods like model.association.build in Rails?

I was wondering if there is some standard way to create methods like the build method, which is generated on a has_many association. For illustration, assuming the following setup
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
Rails automatically generates a post.comments.build method. Is there a standard way or The Rails Way to create my own method like this? I tried it by defining the method on the singleton class of the comments object, like so:
class Post < ActiveRecord::Base
after_initialize do
class << comments
def go
#do something where I can access the the 'owning' post object
end
end
end
end
But this code seemed to break after an ActiveRecord update. So, I was wondering if there is a better way.
You can add methods to the association by passing a block to the has_many method call and defining the method. An example taken from the docs:
has_many :employees 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

How to hide records, rather than delete them (soft delete from scratch)

Let's keep this simple. Let's say I have a User model and a Post model:
class User < ActiveRecord::Base
# id:integer name:string deleted:boolean
has_many :posts
end
class Post < ActiveRecord::Base
# id:integer user_id:integer content:string deleted:boolean
belongs_to :user
end
Now, let's say an admin wants to "delete" (hide) a post. So basically he, through the system, sets a post's deleted attribute to 1. How should I now display this post in the view? Should I create a virtual attribute on the post like this:
class Post < ActiveRecord::Base
# id:integer user_id:integer content:string deleted:boolean
belongs_to :user
def administrated_content
if !self.deleted
self.content
else
"This post has been removed"
end
end
end
While that would work, I want to implement the above in a large number of models, and I can't help feeling that copy+pasting the above comparative into all of my models could be DRYer. A lot dryer.
I also think putting a deleted column in every single deletable model in my app feels a bit cumbersome too. I feel I should have a 'state' table. What are your thoughts on this:
class State
#id:integer #deleted:boolean #deleted_by:integer
belongs_to :user
belongs_to :post
end
and then querying self.state.deleted in the comparator? Would this require a polymorphic table? I've only attempted polymorphic once and I couldn't get it to work. (it was on a pretty complex self-referential model, mind). And this still doesn't address the problem of having a very, very similar class method in my models to check if an instance is deleted or not before displaying content.
In the deleted_by attribute, I'm thinking of placing the admin's id who deleted it. But what about when an admin undelete a post? Maybe I should just have an edited_by id.
How do I set up a dependent: :destroy type relationship between the user and his posts? Because now I want to do this: dependent: :set_deleted_to_0 and I'm not sure how to do this.
Also, we don't simply want to set the post's deleted attributes to 1, because we actually want to change the message our administrated_content gives out. We now want it to say, This post has been removed because of its user has been deleted. I'm sure I could jump in and do something hacky, but I want to do it properly from the start.
I also try to avoid gems when I can because I feel I'm missing out on learning.
I usually use a field named deleted_at for this case:
class Post < ActiveRecord::Base
scope :not_deleted, lambda { where(deleted_at: nil) }
scope :deleted, lambda { where("#{self.table_name}.deleted_at IS NOT NULL") }
def destroy
self.update(deleted_at: DateTime.current)
end
def delete
destroy
end
def deleted?
self.deleted_at.present?
end
# ...
Want to share this functionnality between multiple models?
=> Make an extension of it!
# lib/extensions/act_as_fake_deletable.rb
module ActAsFakeDeletable
# override the model actions
def destroy
self.update(deleted_at: DateTime.current)
end
def delete
self.destroy
end
def undestroy # to "restore" the file
self.update(deleted_at: nil)
end
def undelete
self.undestroy
end
# define new scopes
def self.included(base)
base.class_eval do
scope :destroyed, where("#{self.table_name}.deleted_at IS NOT NULL")
scope :not_destroyed, where(deleted_at: nil)
scope :deleted, lambda { destroyed }
scope :not_deleted, lambda { not_destroyed }
end
end
end
class ActiveRecord::Base
def self.act_as_fake_deletable(options = {})
alias_method :destroy!, :destroy
alias_method :delete!, :delete
include ActAsFakeDeletable
options = { field_to_hide: :content, message_to_show_instead: "This content has been deleted" }.merge!(options)
define_method options[:field_to_hide].to_sym do
return options[:message_to_show_instead] if self.deleted_at.present?
self.read_attribute options[:field_to_hide].to_sym
end
end
end
Usage:
class Post < ActiveRecord::Base
act_as_fake_deletable
Overwriting the defaults:
class Book < ActiveRecord::Base
act_as_fake_deletable field_to_hide: :title, message_to_show_instead: "This book has been deleted man, sorry!"
Boom! Done.
Warning: This module overwrite the ActiveRecord's destroy and delete methods, which means you won't be able to destroy your record using those methods anymore. Instead of overwriting you could create a new method, named soft_destroy for example. So in your app (or console), you would use soft_destroy when relevant and use the destroy/delete methods when you really want to "hard destroy" the record.

accessing associations within before_add callback in Rails 3

In Rails 3.2 I have been looking for a way to traverse the associations of an object within the before_add callback.
So basically my use case is:
class User < ActiveRecord::Base
has_and_belongs_to_many :meetings
end
class Meeting < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :comments, :before_add => :set_owner_id
end
class Comment < ActiveRecord::Base
belongs_to :meeting
end
def set_owner_id(child)
child.owner_id = <<<THE USER ID for #user >>>
end
and I am creating a comment within the context of a user:
#user.meetings.first.comments.create
How do I traverse the associations from within the before_add callback to discover the id of #user? I want to set this at model level. I have been looking at proxy_association, but I may be missing something. Any ideas?
You should probably create the comment in the context of the meeting, no? Either way, you should handle this in the controller since you'll have no access to #user in your model.
#comment = Meeting.find(id).comments.create(owner_id: #user, ... )
But if you insist on your way, do this:
#comment = #user.meetings.first.comments.create(owner_id: #user.id)

Resources