a newbie here. Just started to learn development. Any help would be greatly appreciated
I have two models Project and Task. Each project will have 7 tasks. I want rails to auto create my 7 tasks after I create a project.
My Task Controller
def create
#task = Task.new(task_params)
respond_to do |format|
if #task.save
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render :show, status: :created, location: #task }
else
format.html { render :new }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
def task_params
params.require(:task).permit(:title, :description)
end
There are several ways you can do this.
1. Via callbacks
You can use callbacks in the Project model. Personally I do not recommend this approach since it this is not the intended use of callbacks, but it may work for you.
class Project < class Attachment < ActiveRecord::Base
after_create :create_tasks
private
def create_tasks
# Logic here to create the tasks. For example:
# tasks.create!(title: "Some task")
end
end
2. Nested attributes
You can build the child objects into the form and Rails will automatically create the child objects for you. Check out accepts_nested_attributes_for. This is more involved than using callbacks.
3. Use a form object
A form object can be a nice middle ground between callbacks and accepts_nested_attributes_for, but it raises the complexity a notch. Read up more about form objects here. There is also a nice Rails Casts episode on the topic, but it requires subscription.
There are other ways to do this as well, so it's up to you to find the right approach.
Another option would be to use Observer. This is more like callback.
But this is a great way to reduce the clutter that normally comes when the model class is burdened with functionality that doesn't pertain to the core responsibility of the class
class ProjectObserver < ActiveRecord::Observer
def after_create(project)
#add the logic to create tasks
end
end
Related
I am struggling to get this working. I have three models
Student
Classroomattnd
Classroom
Using the has_many :through relationship. All my relationships are defined correctly and I have setup the nested form using the accepts_nested_attributes.
So when creating a new student I want to select from a list of classrooms instead of creating a new classroom. The form part also works fine the part I am not getting is when I create the student it complains about the following error.
Couldn't find Classrooom with ID=3 for Student with ID=
I have searched around for few days now but can not get the answer I need to get this working.
def new
#student = Student.new
#student.classrooms.build
end
def edit
end
def create
#student = Student.new(student_params)
respond_to do |format|
if #student.save
format.html { redirect_to #student, notice: 'Student was successfully created.' }
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
Can someone help here, someone must of face this issue before?
Also in the rails console when I run the following it works:
classroom = Classroom.last
student = Student.create(name: 'Dave', classrooms:[classroom])
Your parameter handling isn't supporting nesting. You can look at request parameters in your server log or inspect the fieldnames of your generated form to be sure of your target. It's going to be something along the lines of
def student_params
params.require(:student).permit(:student => [:name, :classroom => [:id, :name]])
end
Or maybe as below. In this second case I'm not assuming everything in the form is nested under a student container. Also note the switch from classroom to classroom_attributes which is a change I have sometimes needed to make even though the form above is what the docs indicate.
def student_params
params.require(:name).permit(:classroom_attributes => [:id, :name])
end
Hopefully that gives you a notion of how to tailor your parameter definition to what your form is generating. Also note your error messages give you indication of what part of your definition is failing, eg the missing Student id in the error you quote.
I've read through the following tutorial and found the curious line:
notice that the create function is written in such a way that there has be a #post before creating a #comment.
You can see the supporting controller code:
Class CommentsController < ApplicationController
----
def create
#post = Post.find(current_post)
#comment = #post.comments.create(post_params) ## 'Essential stuff'
respond_to do |format|
if #comment.save
format.html { redirect_to action: :index, notice: 'Comment was successfully created.' }
format.json { render :show, status: :created, location: #comment }
else
format.html { render :new }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
----
end
Indeed, "current_post" implies that the post was created BEFORE the comment.
But what if I want both to be created simultaneously? For example, suppose my USER has_many EMAILS, and each EMAIL belongs_to a USER. Then, when creating a new user, I may want to have an expandable form that allows the user to add one, two, three, or twenty emails while creating his account.
How could this be done?
Nested Attributes is the rails way of doing what you want to achieve.
Checkout http://railscasts.com/episodes/196-nested-model-form-part-1
You need to consider using nested form, have a look into this gem, very easy to implement. It will allow the user to add multiple emails as required.
I'm working with validations in rails, stuff like:
validates_presence_of :some_field
I've noticed that if the validation fails, all changes are overwritten with existing values from the database. This makes some sense, as the page is basically being reloaded (as I gather from my development log), however this increases the risk of user error/frustration, as a single error in one field will require the hapless fellow to re-enter the changes he made to all fields.
My question: How can I get rails to reload the data that was just submitted if validation fails? That way, the user can correct the mistake without needing to re-enter the rest of his revisions.
Thanks for any advice.
Edit:
My update method, as requested, is as follows:
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
if #company.update(company_params)
redirect_to incorporations_index_path
else
redirect_to edit_incorporation_path(#incorporation)
end
end
Full disclosure regarding my controller: the above update is from my incorporations_controller even though I'm updating my Company model. Company has_one :incorporation. I did this because, in the larger context of my app, it made my associations much cleaner.
Update your controller to this
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
respond_to do |format|
if #company.update(company_params)
format.html { redirect_to({:action => "index"})}
else
format.html{render :edit}
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
To add to the correct answer, you can clean up your code quite a bit:
def update
#incorporation = Incorporation.find params[:id]
respond_to do |format|
if #incorporation.update company_params
format.html { redirect_to({:action => "index"})}
else
format.html { render :edit }
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
If you're using accepts_nested_attributes_for, you definitely should not hack the associated objects on the front-end.
You should look up fat model, skinny controller (let the model do the work):
#app/models/company.rb
class Company < ActiveRecord::Base
before_update :set_name
attr_accessor :name_string, :name_suffix
private
def set_name
if name_string && name_suffix
self[:name] = "#{name_string} #{name_suffix}"
else
self[:name] = "Company #{id} (Untitled)"
end
end
end
This will allow you to populate the name of the `company. To edit your nested/associated objects directly is an antipattern; a hack which will later come back to haunt you.
The key from the answer is: render :edit
Rendering the edit view means that your current #company / #incorporation data is maintained.
Redirecting will invoke a new instance of the controller, overriding the #incorporation, hence what you see on your front-end.
I currently have two views (new.html.erb and retirement_accounts_new.html.erb) in the Accounts both using the same create and update methods.
Here's how they're defined in the controller:
# GET /accounts/new
def new
#account = current_user.accounts.build
end
# GET /retirement/accounts/new
def retirement_accounts_new
#account = current_user.accounts.build
end
And here's the same create method they share:
def create
#account = current_user.accounts.build(account_params)
if #account.save
redirect_to accounts_path, notice: 'Account was successfully created.'
else
render action: 'new'
end
end
Is there a way to make that redirect_to accounts_path conditional based on which view is rendering the form?
I would like retirement_accounts_new on save/update to redirect_to retirement_accounts
It sounds like this might be a design issue. Are Accounts and RetirementAccounts significantly different? Will they share much of the same logic, but not all? If so, I think I would avoid using conditional logic in the controller and solve it using inheritance.
The idea here is that retirement_accounts would be considered a new resource in your routes file:
resources :retirement_accounts
Then you manually create a new controller for it (skip the rails generate... command). Save this file as app/controllers/retirement_accounts_controller.rb:
class RetirementAccountsController < AccountsController
end
Notice how it inherits from AccountsController instead of ApplicationController. Even in this empty state, RetirementAccountsController shares all of the logic of AccountsController, including the new and create methods, plus all of the view files to which they refer. To make the necessary modifications for the retirement accounts, you simply need to override the appropriate actions and views.
You can delete your retirement_accounts_new action, since it is identical to the new action. Move the view for retirement_accounts_new to app/views/retirement_accounts/new.html.erb, so that template will be rendered when new is called on the RetirementAccountsController.
As for the conditional create method, you can make a private method on both controllers that will determine where the post-create redirect should point:
class AccountsController < ApplicationController
# ...
def create
#account = current_user.accounts.build(account_params)
if #account.save
redirect_to post_create_redirect_path, notice: 'Account was successfully created.'
else
render action: 'new'
end
end
private
def post_create_redirect_path
accounts_path
end
end
class RetirementAccountsController < AccountsController
private
def post_create_redirect_path
retirement_accounts_path
end
end
If RetirementAccount < Account as a single table inheritance model then the thing you are asking would happen by default,
plan B would be to use explicit url_for in the redirect such as:
redirect_to url_for(controller: params[:controller], action: :show, id: #account.id), notice: 'Account was successfully created.'
Looking at the api doc this should work too:
redirect_to :action => "show", :id => #account.id,notice: 'Account was successfully created.'
Check out http://apidock.com/rails/ActionController/Base/redirect_to - there's probably an answer for you there somewhere :)
PS I have assumed that the the retirement account and account actions are in different controllers. If they're not in different controllers and not different model classes then you can put a hidden tag in the new form - but this is bad&ugly
Best solution is probably STI model and 2 separate resources for the 2 classes and everything will work out of the box. If this isn't an option, at least separate the controllers and make things clean that way, it's much better to reuse views then to reuse controllers
When you generate a rails scaffold using a command like rails g scaffold Thing is there any way to avoid getting that annoying
respond_to do |format|
format.html # index.html.erb
format.json { render json: #things }
end
stuff in your controller?
I'm trying to teach a class on Rails and I'd like to start by having them generate a scaffold, but with all the json formatting it's much more complicated than it needs to be. I'd be much happier if they could generate a scaffold that created a controller like this:
class ThingsController < ApplicationController
def index
#things = Thing.all
end
def show
#thing = Thing.find(params[:id])
end
def new
#thing = Thing.new
end
def edit
#thing = Thing.find(params[:id])
end
def create
#thing = Thing.new(params[:thing])
if #thing.save
redirect_to #thing, notice: 'Thing was successfully created.'
else
render: "new"
end
end
end
def update
#thing = Thing.find(params[:id])
if #thing.update_attributes(params[:thing])
redirect_to #thing, notice: 'Thing was successfully updated.'
else
render: "edit"
end
end
end
def destroy
#thing = Thing.find(params[:id])
#thing.destroy
redirect_to things_url
end
end
Comment out gem jbuilder in your Gemfile and respond_to blocks won't be generated.
Just clone the file
https://github.com/rails/rails/blob/v5.2.2/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
to your
lib/rails/generators/rails/scaffold_controller/templates/controller.rb
path in your application and customize what you want. Also, you can write your own generators for scaffolding ( http://guides.rubyonrails.org/generators.html ).
I think you'd be missing an opportunity. For one thing, you'd be teaching non-standard Rails, so your students might be confused when they see the normal version in their own installations.
More importantly, the controllers are formatted that way for a reason. Rails puts an emphasis on REST, which encourages access to resources via multiple data formats. Many modern apps are de-emphasizing slower server-rendered html/erb responses in favor of json APIs. I realize this is a little over a year after your OP, and you have limited time in class, just adding some thoughts for anyone who might happen by. I think you could wave your hand over the respond_to and tell them it's setting you up for some future possibilities.
You'll notice that the JSON response is coded directly into the template for the rails generator here:
https://github.com/rails/rails/blob/master/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
I think something to note is that the scaffold generator is really intended to illustrate and moreover educate on how the Rails stack works, it shows how you can edit the controller to provide many different formats to suit your needs!