Creating 2 different entities using one controller.create - ruby-on-rails

I need to create a new entity B whenever I create an entity A. To do this I tried adding a call to B.create inside the A.create method in a_controller. This however gives an error:
Missing template a/create
So my question is: how do I create an entity B from the A.create controller?

something like this?
def create
#A = A.new(params[:a])
#B = B.new(params[:b])
respond_to do |format|
if #A.save && #B.save
format.html { redirect_to #A, :notice => 'A was successfully created.' }
else
# render new with validation errors
format.html { render :action => "new" }
end
end
end
but if your objects are 'related', i.e. has_many or belongs_to then you might want something like
# project has_many tasks
def create
#project = Project.new(params[:project])
#project.tasks.new(params[:task])
if #project.save # this should save both objects and in the same transaction
....
end
and third option is to use accepts_nested_attributes_for - read more here: http://currentricity.wordpress.com/2011/09/04/the-definitive-guide-to-accepts_nested_attributes_for-a-model-in-rails-3/

Related

New method in a model or new service - which approach is better?

I am implementing a new app with following business process:
User fill in a registration form.
Once registration form is being saved, a new Training Company is created
User get an e-mail with a pdf with his registration form and unique url
User uses unique url to attach signed registration form Admin can accept or reject
What is the best approach to Training Company creation?
First solution: new service that creates a TrainingCompany
class TrainingCompanyService
def initialize(company_name)
#name = company_name
end
def create_new_training_company
TrainingCompany.new(company_name: #name).save
end
end
create action in RegistrationFormController:
def create
#registration_form = RegistrationForm.new(registration_form_params)
respond_to do |format|
if #registration_form.save
format.html { redirect_to #registration_form, notice: 'Registration form was successfully created.' }
TrainingCompanyService.new(#registration_form.company_name).create_new_training_company
RegistrationFormMailer.with(registration_form: #registration_form).after_registration_email.deliver_later
else
format.html { render :new }
end
end
end
Second solution: new method inside TrainingCompany model:
class RegistrationForm < ApplicationRecord
belongs_to :training_company, optional: true
has_one_attached :registration_form
has_secure_token :signed_form_upload_token
def create_new_training_company
TrainingCompany.new(company_name: self.company_name, registration_form_id: self.id).save
end
end
create action in RegistrationFormController:
def create
#registration_form = RegistrationForm.new(registration_form_params)
respond_to do |format|
if #registration_form.save && #registration_form.create_new_training_company
format.html { redirect_to #registration_form, notice: 'Registration form was successfully created.' }
RegistrationFormMailer.with(registration_form: #registration_form).after_registration_email.deliver_later
else
format.html { render :new }
end
end
end
Which solution would you choose and why? Personally i prefer the second one (new method inside the model)...

Rails calling create method from same controller instance variable

I have a few hours with something that is probably very easy.
I have a nested model
resources :grades do
resources :students
end
So I defined
before_action :set_grade, except: [:mass_input]
to my students_controller
def set_grade
#grade = Grade.find(params[:grade_id])
end
I'm very good with this, the problem is that now I'm using another action that takes :grade_id from another source, so I cant use set_grade, instead I'm passing the id with javascript. Works.
My problem appears here, when I try to call to create method, I'm probably doing it wrong ..
def mass_input
#grade = Grade.find(#data['grade'])
#data = JSON.parse(params[:form_data])
#is this create way ok or I'm overriding???
Student.create(:rut => #data['mass_students'][1][0], :nombre => #data['mass_students'][1][1], :apellido => #data['mass_students'][1][2])
end
This is my create action
def create
#student = Student.new(student_params)
#grade.students << #student
respond_to do |format|
if #student.save
format.html { redirect_to school_grade_path(#grade.school,#grade), notice: 'Alumno creado con éxito.' }
format.json { render :show, status: :created, location: #student }
else
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
end
By this way code works but this line is not working
#grade.students << #student
#grade is not passing from mass_input to create. I think I'm not calling create properly but I cant find how to do it , because is not redirecting neither
My mass_input action is working by this way
def mass_input
#grade = Grade.find(#data['grade'])
#data = JSON.parse(params[:form_data])
Student.create(:rut => #data['mass_students'][1][0], :nombre => #data['mass_students'][1][1], :apellido => #data['mass_students'][1][2])
grade.students << student
respond_to do |format|
if student.save
format.html { redirect_to school_grade_path(grade.school,grade), notice: 'Alumno creado con éxito.' }
format.json { render :show, status: :created, location: student }
else
format.html { render :new }
format.json { render json: student.errors, status: :unprocessable_entity }
end
end
end
but I think is AWFUL, I must use my own create action
Thanks!!
Oh... From my point of view you are doing smth strange... The fast solution for your issue would be smth like this:
1) Rewrite before action in a new way:
before_action :set_grade
And method set_grade:
def set_grade
#grade = Grade.find(params[:grade_id].presence || #data['grade'])
end
2) Set method for student params
def student_params
data = JSON.parse(params[:form_data])['mass_students']
#Transform data to be student params. For ex:
data.map{|_key, info| {:rut => info[0], :nombre => info[1], :apellido => info[2]}}
end
3) Rewrite mass_input method
def mass_input
respond_to do |format|
if (#students = #grade.students.create(student_params).all?(&:persisted?)
#some actions when everything is great.
else
#some actions if not of them valid (maybe redirect & show info about not created students)
end
end
end
But you should definetly read more rails guides... http://guides.rubyonrails.org/
Sorry, I couldn't comment it. So I can just post a reply, it is not an complete answer though. In the student controller
Try to use
#student = #grade.students.new
or
#student = Student.new
#student.grade = #grade or #student.grade_id = params[:grade_id]
So when you do #student.save, you won't need to do the line below, and it will still work
#grade.students << #student
Ruby on rails has conventions you should follow to simplify lots of things. The first thing I see here is that in your def mass_input, you are using
Student.create(...)
The method create, as it says, creates an object but also saves it into database. So you should have new instead of create because new does not save it to database, just instantiates it:
#student = Student.new
...inside def mass_input, and by default the submit action in your view will take your object to the create method (if the object is new it goes to create, other way it goes to update, thanks to Rails). For this you could take a look at http://guides.rubyonrails.org/action_controller_overview.html
About the line #grade.students << #student, I assume you are intending to add the newly created student to his grade. See this example of usage of nested resources when trying to create, edit or destroy http://railscasts.com/episodes/139-nested-resources. In any case, nested resources implies this:
class Grade < ActiveRecord::Base
has_many :student
end
class Student < ActiveRecord::Base
belongs_to :grade
end
So, in your model Student you should have a column to store the Grade of that student. And then in your params you should receive the actual grade and store it in the grade_id inside your #student.
If something is not clear, I suggest you to take a look at the nested resources guide http://guides.rubyonrails.org/routing.html#nested-resources
As a commentary, << is used to add "things" to the end of an array, i.e. if you want to quickly store in an array some info you use:
array = []
Student.all.each do |s|
array << s.name
end
It will store in the array all the names of your students. Obviously there is a simpler way to do this by doing this:
Student.pluck(:name)

Rails 4 & Controller action

I am trying to make an app in Rails 4.
I have three models - Project, Scope and Finalise.
The associations are:
Project has one scope
Scope belongs to project and has one finalise
Finalise belongs to Scope
Scope accepts nested attributes for Finalise.
Within Finalise, I have an attribute called :draft (boolean) and an attribute called :finalised_at (datetime)
I tried to write a function that would let me put a link on any project created with :draft saved as true. The link is meant to toggle :draft to false, which then displays a publication date (:finalised_at) on completed projects.
I am getting an error which is preventing me from creating a test project. The error is: undefined local variable or method `create_a_finalise' for - referring to the create action in my project controller. I think this has something to do with the create_a_finalise method being in my scope.rb model. I don't know how to fix this.
My finalise.rb has:
after_validation :set_publish_time
def set_publish_time
self.finalised_at = Time.now unless self.draft
end
My scope.rb has:
after_create :create_a_finalise
def create_a_finalise_dynamic(boolean)
self.finalise.create draft: boolean
end
My finalise_controller has:
def toggle_draft
#finalise = Finalise.find(params[:finalise_id])
#finalise.draft = false
if #finalise.save
redirect_to project_path(#finalise.scope.project), notice: 'Successfully Updated'
else
redirect_to project_path(#finalise.scope.project), alert: 'Not Updated'
end
end
The problematic part of my project_controller has:
def create
#authorise #project
#project = Project.new(project_params)
#project.creator_id = current_user.id
#project.users << current_user
respond_to do |format|
if #project.save
format.html { redirect_to #project }
format.json { render action: 'show', status: :created, location: #project }
else
format.html { render action: 'new' }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
Can anyone see what I need to do to be able to create a new project. There is something wrong with the create action in my projects controller.
Thank you
You are referring to create_a_finalise but it is named create_a_finalise_dynamic.
after_create :create_a_finalise
def create_a_finalise_dynamic
Change the name of the method to
def create_a_finalise
and leave off the argument.
Give the following a try-
# scope.rb
after_create :create_a_finalise
def create_a_finalise(val)
self.create_finalise(draft: val)
end

Refactoring related nested if

I'm trying to create 2 related models inside my create method, where the 2nd model is created using model1.model2s.build. The model*_params is just Rails 4 strong parameters.
So I have this set of code in my create method:
def create
#model1 = current_user.model1.build(model1_params)
if #model1.save
#model2 = #model1.model2s.build(model2_params)
if #model2.save
redirect_to model1_path(#model1)
else
render 'new'
end
else
render 'new'
end
end
As you can see there's an ugly nested if in the method, and it's not DRY as I'm forced to repeat render 'new' in order to capture save failures. This has been the only way I can get model2 to save, because it requires a relation to model1, and model1 must save first, in order for the id of model1 to be propagated to the build method.
My question therefore, is how can I refactor this set of code so that it doesn't require a nested if?
def create
#model1 = current_user.model1.build(model1_params)
return render("new") unless #model1.save
#model2 = #model1.model2s.build(model2_params)
return render("new") unless #model2.save
redirect_to model1_path(#model1)
end
You can easily make it a single if/else:
if #model1.save && #model1.model2s.build(model2_params).save
redirect_to #model1
else
render 'new'
end
Alternatively, exceptions:
begin
#model1 = current_user.model1.build(model1_params)
#model1.save!
#model2 = #model1.model2s.build(model2_params)
#model2.save!
redirect_to #model1
rescue ActiveRecord::RecordInvalid => e
render 'new'
end

Nice way of doing dual column validation

I'm using Rails 3 for this one. I've got a collections model, a user model and an intermediate subscription model. This way a user can subscribe to multiple collections, with a particular role. However, I don't want a user to be able to subscribe to the same collection twice.
So in my Subscription model I've got something like:
validate :subscription_duplicates
def subscription_duplicates
self.errors.add_to_base "This user is already subscribed" if Subscription.where(:user_id => self.user.id, :collection_id => self.collection.id)
end
However this seems ugly. Also, it breaks when I want to do something like the following in my collection controller:
def create
#collection = Collection.new(params[:collection])
#collection.subscriptions.build(:user => current_user, :role => Subscription::ROLES['owner'])
#collection.save
respond_with(#collection)
end
When I do the build the subscription does not have an id so I get a "Called id for nil" error.
Thanks for any guidance!
use validates_uniqueness_of
validates_uniqueness_of :user_id, :scope => :collection_id
First of all, your create action should always test if the object was saved, and if not then handle that (usually by re-rendering the new/edit page and showing the errors to the user).
A standard sort of create action would look like this (for a #post in this case):
def create
#post = Post.new(params[:post])
#created = #post.save
respond_to do |format|
if #created
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to #post }
format.xml { render :xml => #post, :status => :created, :location => #post }
format.js
else
format.html { render :action => :new } #or edit or wherever you got here from
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
format.js
end
end
end
Shingara's approach to avoiding duplicates should work fine for you.

Resources