Reference an object's parent ruby rails - ruby-on-rails

I have an Opportunity model that has Links as a nested resource. I wrote a callback so that whenever I add a new link, the built-in "updated_at" attribute for my model opportunity is updated to equal Time.now. However, I'm not sure how to reference the Opportunity model. I want to do something like this:
This is what I would put in my Link model, which is a nested resource of my Opportunity model:
class Link < ActiveRecord::Base
belongs_to :opportunity
after_save :update_updated_at
def update_updated_at
#opportunity.updated_at = Time.now #this line is where I am unsure of how to reference the link's Opportunity parent
end
end
Thanks!

Links in ActiveRecord are always accessed through method names. There is no instance variable called #opportunity, so that's equivalent to calling updated_at= on nil.
What you probably want is:
def update_parent
return unless (self.opportunity)
self.opportunity.updated_at = Time.now
self.opportunity.save
end
From an implementation perspective this is a little rude as the Link object is bossing around the Opportunity one. That's usually something a controller should be doing.

If a Link object #link belongs_to an Opportunity object #opp, you can find #opp if you know #link via the ActiveRecord relation opportunity. See http://guides.rubyonrails.org/association_basics.html for more details.
Given a link record, find the parent opportunity record:
#opp = #link.opportunity
So you could write self.opportunity.updated_at = Time.now

Related

Rails preview update associations without saving to database

I want to preview what the model will look like when saved without currently saving to the database.
I am using #event.attributes = because that assigns but does not save attributes for #event to the database.
However, when I also try to assign the audiences association, Rails inserts new records into the audiences_events join table. Not cool. Is there a way to preview what these new associations will look like without inserting into the join table?
Model
class Event < ActiveRecord::Base
has_and_belongs_to_many :audiences # And vice versa for the Audience model.
end
Controller
class EventsController < ApplicationController
def preview
#event = Event.find(params[:id])
#event.attributes = event_params
end
private
def event_params
params[:event].permit(:name, :start_time, :audiences => [:id, :name]
end
end
Possible Solutions?
Possible solutions that I thought of, but don't know how to do:
Using some sort of method that assigns associations, but does not persist them.
disabling all database writes for this one action (I dont know how to do that).
Rolling back all database changes at the end of this action
Any help with these would be great!
UPDATE:
After the reading the great answers below, I ended up writing this service class that assigns the non-nested attributes to the Event model, then calls collection.build on each of the nested params. I made a little gist. Happy to receive comments/suggestions.
https://gist.github.com/jameskerr/69cedb2f30c95342f64a
In these docs you have:
When are Objects Saved?
When you assign an object to a has_and_belongs_to_many association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.
If you want to assign an object to a has_and_belongs_to_many association without saving the object, use the collection.build method.
Here is a good answer for Rails 3 that goes over some of the same issues
Rails 3 has_and_belongs_to_many association: how to assign related objects without saving them to the database
Transactions
Creating transactions is pretty straight forward:
Event.transaction do
#event.audiences.create!
#event.audiences.first.destroy!
end
Or
#event.transaction do
#event.audiences.create!
#event.audiences.first.destroy!
end
Notice the use of the "bang" methods create! and destroy!, unlike create which returns false create! will raise an exception if it fails and cause the transaction to rollback.
You can also manually trigger a rollback anywhere in the a transaction by raising ActiveRecord::Rollback.
Build
build instantiates a new related object without saving.
event = Event.new(name: 'Party').audiences.build(name: 'Party People')
event.save # saves both event and audiences
I know that this is a pretty old question, but I found a solution that works perfectly for me and hope it could save time to someone else:
class A
has_many :bs, class_name 'B'
end
class B
belongs_to :a, class_name: 'A'
end
a.bs.target.clear
new_bs.each {|new_b| a.bs.build new_b.attributes.except('created_at', 'updated_at', 'id') }
you will avoid autosave that Rails does when you do a.bs = new_bs

How to get the scoped attributes when creating a new object through a has_many association

When creating objects through a has_many association like User.first.books.create!(...), the new book gets the user_id automatically from the association.
Is there any way to get that user_id if I call my own create method? i.e. User.first.books.own_create_method
def self.own_create_method
# how to get the user object?
end
Thanks!
To define User.first.books.own_create_method you would use:
def self.own_create_method
book = build
# something custom you want to do with book
book.save
end
self. allows you to define class methods in Ruby.
Digging into ActiveRecord new method, I found that you can call scope_attributes and you'll get a hash with all the attributes that are scoped.
def self.own_create_method
attributes = scope_attributes
# attributes["user_id"] would be the user_id that is scoped by
...
end
Not sure if this is a good practice though...

Rails forms - Should I build `accepts_nested_attributes_for` associations in the Controller, Model, or View?

The Question
I have a parent that accepts_nested_attributes_for a child. So, when I have a form for the parent, I need to build the child so I can display form fields for it as well. What I want to know is: where should I build the child? In the Model, View, or Controller?
Why I Am Asking This
You may be shaking your head and thinking I'm a madman for asking a question like this, but here's the line of thinking that got me here.
I have a Customer model that accepts_nested_attributes_for a billing_address, like so:
class Customer
belongs_to :billing_address, class_name: 'Address'
accepts_nested_attributes_for :billing_address
end
When I present a form for a new Customer to the user, I want to make sure there is a blank billing_address, so that the user actually sees fields for the billing_address. So I have something like this in my controller:
def new
#customer = Customer.new
#customer.build_billing_address
end
However, if the user doesn't fill out any of the billing_address fields, but tries to submit an invalid form, they will be presented with a form that no longer has fields for the billing_address, unless I put something like this in the create action of my controller:
def create
#customer = Customer.new(params[:customer])
#customer.build_billing_address if #customer.billing_address.nil?
end
There is another issue, which is that if a user tries to edit a Customer, but that Customer doesn't have an associated billing_address already, they won't see fields for the billing_address. So I have to add somethign like this to the controller:
def edit
#customer = Customer.find(params[:id])
#customer.build_billing_address if #customer.billing_address.nil?
end
And something similar needs to happen in the controller's update method.
Anyway, this is highly repetitive, so I thought about doing something in the model. My initial thinking was to add a callback to the model's after_initialize event, like so:
class CustomerModel
after_initialize :build_billing_address, if: 'billing_address.nil?'
end
But my spidey sense started tingling. Who's to say I won't instantiate a Customer in some other part of my code in the future and have this wreak havoc in some unexpected ways.
So my current thinking is that the best place to do this is in the form view itself, since what I'm trying to accomplish is to have a blank billing_address for the form and the form itself is the only place in the code where I know for sure that I'm about to show a form for the billing_address.
But, you know, I'm just some guy on the Internet. Where should I build_billing_address?
Though this advice by Xavier Shay is from 2011, he suggests putting it in the view, "since this is a view problem (do we display fields or not?)":
app/helpers/form_helper.rb:
module FormHelper
def setup_user(user)
user.address ||= Address.new
user
end
end
app/views/users/_form.html.erb:
<%= form_for setup_user(#user) do |f| %>
Note that I had to change the helper method to the following:
def setup_user(user)
user.addresses.build if user.addresses.empty?
user
end
The controller remains completely unchanged.
If you know your model should always have a billing address, you can override the getter for this attribute in your model class as described in the docs:
def billing_address
super || build_billing_address
end
Optionally pass in any attributes to build_billing_address as required by your particular needs.
You would use build if you want to build up something and save it later. I would say, use it in nested routes.
def create
#address = #customer.billing_addresses.build(params[:billing_address])
if #address.save
redirect_to #customer.billing_addresses
else
render "create"
end
end
Something like that. I also use the build when I'm in the console.
You have to remember the principles of MVC, which is to create DRY(don't repeat yourself) code, which is efficiently distributed between the various moving parts of the app
accepts_nested_attributes_for Is Great For Keeping Things DRY
accepts_nested_attributes_for is a model function which allows you to pass data through an association to another model. The reason why it exists is to give you the ability to populate another model's data based on a single form, and is excellent for extending functionality without too much extra code
The problem you're citing is that if you want to use the code in other areas of the app, you'll end up having all sorts of problems
My rebuttal to that is in order to create as efficient an application as possible, you want to write as little code as possible - letting Rails handle everything. The accepts_nested_attributes_for function does allow you to do this, but obviously has a cost, in that you have to accommodate it every time you want to use it
My recommendation is to use what you feel is the most efficient code you can, but also keep to conventions; as this will ensure speed & efficiency
You should handle all these scenarios in controller, since it is not a responsibility of model.
Just in terms of keeping things DRY, you can write a method,
def build_customer(customer)
customer.build_billing_address if customer.billing_address.nil?
#add more code if needed
end
And inside controller you can call this method wherever it is needed. e.g.
def create
#customer = Customer.new(params[:customer])
if #customer.save
redirect_to #customer.billing_addresses
else
build_customer(#customer)
render "new"
end
end

Proper way to initialize nested fields in Rails forms

I'd like to understand what's the "proper" way to initialize the nested fields of a model.
Let's say you have some nested fields for a model:
class User
has_one :address
accepts_nested_attributes_for :address
end
And you need to initialize those attributes (address in this case) to use them in a fields_for call.
So far I've thought of three ways to do this.
First, after_initialize hook on the model:
class User
after_initialize :init_address
protected
def init_address
address ||= build_address
end
Then we have initialization in the controller:
class UsersController
def new
#user = User.new
#user.build_address
end
end
And finally, we can have a helper method to do it for us:
module FormHelpers
def setup_user(user)
user.address ||= user.build_address
user
end
end
# view
<%= form_for setup_user(#user)... %>
Is there anything resembling a standard or a "best practice" for this scenario? How do you do it and why?
I think that if the nested attribute doesn't make sense at all without the parent model, building and initialization of these nested models should be the responsibility of the parent model.
I don't see why the UsersController should care about how the #user.addresses are built or initialized. For me, giving the controller this responsibility, would probably imply that on create he should be the one that parsed and built the nested attributes (which, happens in the model).
I would go for the first approach.
i believe that build_address is already built in for rails after u declare a has_one association, so you don't need to write that bit urself.
and if the form is called only from the new action, what u really need is only the controller bit, and nothing else

Have a nested parent model pointing to the first child model

My models are like this: a discussion has_many posts (nested resource).
I want to add a starter_post_id column to the discussions table, and have it record the 'thread starter post id'. The discussion is created along with the post in the nested form, and that when the logic should be called, because other posts to that discussion will be replies not starter posts.
I am not sure what I need to do after the add_column db migration.
Do I need a belongs_to :post in my Discussion model?
What's the order of creation for these nested objects. e.g. parent's creation ends before child's starts? or will the parent constructor call the child constructor?
Which model should the starter post assignment logic go to? This is related to Q2 since both objects needs to be initiated, but preferably before the DB call.
I would use the created_at field from you post model to determine the starter_post of a discussion. No need for any columns.
Add something like this in your discussion model
def starter_post
self.posts.order("created_at ASC").first()
end
If you use this in you discussion.rb :
has_many :posts , :order => "created_at ASC"
you can then simply use :
def starter_post
self.posts.first()
end
I tried before_save and it won't work because at that point in time the discussion has no way to get hold of the starter post object. I was pointed out to use after_create instead.
def after_create
self.starter_post_id = self.posts.first.id
self.save!
end
This will cause one extra sql query, but it is better than doing it at the post model.
I used belongs_to so I can use discussion.start_post_id, but I guess it is optional.

Resources