I have a checkbox that if checked allows my child resource called Engineer to be created. I'm trying to create it through my model since that's where I can call the after_save method.
Here is my code:
models/user.rb
class User < ActiveRecord::Base
has_many :armies
has_many :engineers
end
models/army.rb
class Army < ActiveRecord::Base
has_many :engineers
attr_reader :siege
after_save :if_siege
private
def if_siege
if self.siege
Engineer.create!( :user_id => current_user.id, :army_id => self.id )
end
end
end
models/engineer.rb
class Engineer < ActiveRecord::Base
belongs_to :user
belongs_to :army
end
controllers/armies_controller.rb
def new
#army = Army.new
end
def create
#army = current_user.armies.build(params[:army])
if #army.save
redirect_to new_army_path
else
render :new
end
end
end
This gives me an error though for my if_siege method:
undefined local variable or method `current_user'
How can I fix this or is there another way to do this? Not sure if this should go in the controller or model but I only can wrap my head around putting this in the model.
Thanks.
Add belongs_to :user to the Army model
In Army#if_siege, update Engineer.create! as follows
Engineer.create!( :user_id => self.user.id, :army_id => self.id )
First, the current_user object won't exist within the context of the Model layer unless your authentication is doing something to make it available. This is usually a non Threadsafe approach though. Maybe for you this isn't the issue.
Current User Instantiation
Having said that, one way (perhaps not the ideal way) to address this is by creating an attr_accessor in the model on the object called Army. Then set the current_user to this in the Army new action in the controller where the current_user instance is available.
# in the Army model
attr_accessor :the_user
# in the Army Controller
#army = Army.new(:the_user => current_user.id)
You will also have to add a hidden field to store this value in your view to carry this through to the create action.
Just an observation, but I'm fairly sure in the "if_seige" method the self calls are redundant. self should already be scoped to the Army object in that method.
Related
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
tl;dr:
the Devise::RegistrationsController doesn't instantiate associated polymorphic models, what's the best way to solve that?
setup:
I have an address model which I am associating with different entities (e.g. a person, a business) and thus am using a polymorphic relationship:
class Person < ActiveRecord::Base
has_one :address, :as => :addressable
accepts_nested_attributes_for :address # to make fields_for work
end
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
I have used the Person model as the Devise model but run into issues where the Address object is not instantiated or saved when signing up for a new account.
The Person controller, which to my knowledge is replaced by the Devise RegistrationController (routing: new_person_registration GET /person/sign_up(.:format) devise/registrations#new) had code to instantiate the respective objects which used to do the trick before I started using Devise:
class PeopleController < ApplicationController
[...]
def create
#person = Person.new(params[:person])
#person.address = Address.new(params[:address])
[...some checking...]
#person.save
#person.address.save
end
def new
#person = Person.new
#person.address = Address.new
end
end
Now it seems that the Devise::RegistrationsController doesn't create the Address object.
One suggested resolution was to use callbacks to initialize the objects, thus my person model has:
before_create :instantiate_all
before_save :save_all
def save_all
self.address.save
self.save
end
def instantiate_all
if self.address.nil?
self.address = Address.new
end
end
With that I still get
NoMethodError (undefined method `address' for nil:NilClass):
app/models/person.rb:25:in `save_all'
I was looking into overriding the RegistrationController but it seems that by calling super I will get the whole block of the respective parent method executed and thus won't be able to insert my desired action.
Lastly, overriding the controller, copying its code and adding my code just seems too wrong to be the only way to get this done.
I'd appreciate your guys' thoughts on that.
class Party < ActiveRecord::Base
belongs_to :hostess, class_name: 'Person', foreign_key: 'hostess_id'
validates_presence_of :hostess
end
class Person < ActiveRecord::Base
has_many :parties, foreign_key: :hostess_id
end
When creating a new Party, the view lets the user select an existing Hostess, or enter a new one. (This is done with jQuery autocomplete to look up existing records.) If an existing record is chosen, params[:party][:hostess_id] will have the correct value. Otherwise, params[:party][:hostess_id] is 0 and params[:party][:hostess] has the data to create a new Hostess (e.g., params[:party][:hostess][:first_name], etc.)
In the Parties controller:
def create
if params[:party][:hostess_id] == 0
# create new hostess record
if #hostess = Person.create!(params[:party][:hostess])
params[:party][:hostess_id] = #hostess.id
end
end
#party = Party.new(params[:party])
if #party.save
redirect_to #party, :notice => "Successfully created party."
else
#hostess = #party.build_hostess(params[:party][:hostess])
render :action => 'new'
end
end
This is working fine when I pass in an existing Hostess, but it's not working when trying to create the new Hostess (fails to create the new Hostess/Person and thus fails on creating the new Party). Any suggestions?
Given the models you provided, you can have this setup in a cleaner way using a few rails tools like inverse_of, accepts_nested_attributes_for, attr_accessor, and callbacks.
# Model
class Party < ActiveRecord::Base
belongs_to :hostess, class_name: 'Person', foreign_key: 'hostess_id', inverse_of: :parties
validates_presence_of :hostess
# Use f.fields_for :hostess in your form
accepts_nested_attributes_for :hostess
attr_accessor :hostess_id
before_validation :set_selected_hostess
private
def set_selected_hostess
if hostess_id && hostess_id != '0'
self.hostess = Hostess.find(hostess_id)
end
end
end
# Controller
def create
#party = Party.new(params[:party])
if #party.save
redirect_to #party, :notice => "Successfully created party."
else
render :action => 'new'
end
end
We're doing quite a few things here.
First of all, we're using inverse_of in the belongs_to association, which allows you to validate presence of the parent model.
Second, we're using accepts_nested_attributes_for which allows you to pass params[:party][:hostess] into the party model and let it build the hostess for you.
Third, we're setting up an attr_accessor for :hostess_id, which cleans up controller logic quite a bit, allowing the model to decide what to do whether it receives hostess object or the hostess_id value.
Fourth, we're making sure to override hostess with an existing hostess in case we got a proper hostess_id value. We do this by assigning hostess in the before_validation callback.
I didn't actually check if this code works, but hopefully it reveals enough information to solve your problem and exposes more helpful tools lurking in rails.
My app has three models:
Thread (has_many :thread_apps)
ThreadApp (belongs_to :thread, has_many :forms, :as => :appable)
Form (belongs_to :app)
ThreadApp Fields: thread_id, form_id, appable_id, appable_type
What I want to be able to do is when creating a form, ensure that a ThreadApp record is also created to make the association:
Here is what I have:
class FormsController < ApplicationController
def create
#thread = Thread.find(params[:thread_id])
#thread_app = #thread.thread_apps.new
#form = #thread_app.forms.build(params[:form].merge(:user_id => current_user.id))
#form.save
....
This is saving the form nicely, but the thread_app associated is not being made? Any ideas why?
Thank you
callings model.save does not save associations unless you tell it to
you can set autosave
class Form < ActiveRecord::Base
belongs_to :thread_app , :autosave => true
end
or call save on it in the controller
#thread_app.save
or you can take it out of the controller entirely
and do this with a callback
class Form < ActiveRecord::Base
before_create :create_thread_app
def create_thread_app
self.thread_app ||= ThreadApp.create(...)
end
end
or after_create, _before_validation_on_create, or any other call back would work
--UPDATE--
this might make a difference using create inse=tead of new, and appables that you specified as 'as'
class FormsController < ApplicationController
def create
#thread = Thread.find(params[:thread_id])
#thread_app = #thread.thread_apps.create
#form = #thread_app.appables.build(params[:form].merge(:user_id => current_user.id))
#form.save
....
Instead of:
#thread_app = #thread.thread_apps.new
You should have:
#thread_app = #thread.thread_apps.create
Below I have outlined the structure of a polymorphic association.
In VacationsController I put some comments inline describing my current issue. However, I wanted to post this to see if my whole approach here is a little off. You can see in business_vacations_controller and staff_vacations_controller that I've had to make 'getters' for the model and controller so that I can access them from within vacations_model so I know which type of object I'm dealing with. Although it works, it's starting to feel a little questionable.
Is there a better 'best practice' for what I'm trying to accomplish?
models
vacation.rb
class Vacation < ActiveRecord::Base
belongs_to :vacationable, :polymorphic => true
end
business.rb
class Business < ActiveRecord::Base
has_many :vacations, :as => :vacationable
end
staff.rb
class Staff < ActiveRecord::Base
has_many :vacations, :as => :vacationable
end
business_vacation.rb
class BusinessVacation < Vacation
end
staff_vacation.rb
class StaffVacation < Vacation
end
controllers
business_vacations_controller.rb
class BusinessVacationsController < VacationsController
private
def controller_str
"business_schedules"
end
def my_model
BusinessVacation
end
def my_model_str
"business_vacation"
end
end
staff_vacations_controller.rb
class StaffVacationsController < VacationsController
private
def controller_str
"staff_schedules"
end
def my_model
StaffVacation
end
def my_model_str
"staff_vacation"
end
end
vacations_controller.rb
class VacationsController < ApplicationController
def create
# Build the vacation object with either an instance of BusinessVacation or StaffVacation
vacation = #class.new(params[my_model_str])
# Now here's the current issue -- I want to save the object on the association. So if it's a 'BusinessVacation' object I want to save something like:
business = Business.find(vacation.vacationable_id)
business.vacations.build
business.save
# But if it's a 'StaffVacation' object I want to save something like:
staff = Staff.find(vacation.vacationable_id)
staff.vacations.build
staff.save
# I could do an 'if' statement, but I don't really like that idea. Is there a better way?
respond_to do |format|
format.html { redirect_to :controller => controller_str, :action => "index", :id => vacation.vacationable_id }
end
end
private
def select_class
#class = Kernel.const_get(params[:class])
end
end
It feels like a lot of hoops to jump through in the VacationsController to make it aware of the context. Is there a reason that the StaffVacationsController and BusinessVacationsController couldn't each have a #create action and the views would submit to whichever is appropriate? These actions would already know the model context and be able to redirect to the appropriate url afterward.