Rails Associations: belongs_to has_many confusion - ruby-on-rails

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.

Related

Rails store to different tables within the same controller

I have two models, Livestock and History
a livestock has many histories and history belongs to livestock
This is the create method inside the LivestockController
# POST /livestocks
# POST /livestocks.json
def create
#livestock = Livestock.new(livestock_params.permit!)
respond_to do |format|
if #livestock.save
format.html { redirect_to #livestock }
flash[:success] = "Livestock was successfully created"
format.json { render :show, status: :created, location: #livestock }
else
format.html { render :new }
format.json { render json: #livestock.errors, status: :unprocessable_entity }
end
end
end
I wanted to create a record in the histories table with
history = History.new(livestock_id: #livestock.id, event: "Purchased", event_date: #livestock.purchase_date, image: #livestock.image)
history.save!
inside the create method
How can I do it? I can't put it in the create method because it says
Validation failed: Livestock must exist
apparently #livestock has not yet have the id attribute
Edit:
it still raises the same exception when I put it after
if #livestock.save
However I found a work around by using the session variable. Since it is redirected to the show page, I created the following inside the create method
session[:created] = "created"
And in my show method
# GET /livestocks/1
# GET /livestocks/1.json
def show
if session[:created] == "created"
history = History.new(livestock_id: params[:id], event: "Purchased", event_date: #livestock.purchase_date, image: #livestock.image)
history.save!
session.delete(:created)
end
end
Now I am wondering what are consequences if I use this approach.
Livestock record is created when you call save (and there is no validation error). So one option is to create the history inside this if condition:
if #livestock.save
Another option is to use after_create callback in the livestock model that will create a history object right after creating livestock. You have to be careful because the callback might be called when you do not need it (i.e. when importing data).
The last option is to create a separate service object that will create livestock and all other required objects. That's probably the best approach, but it will require more customized code.
Update
Please also make sure to move if/else block outside the respond_to block:
if #livestock.save
# create history object here
respond_to do |format|
format.html { redirect_to #livestock }
flash[:success] = "Livestock was successfully created"
format.json { render :show, status: :created, location: #livestock}
end
else
respond_to do |format|
format.html { render :new }
format.json { render json: #livestock.errors, status: :unprocessable_entity }
end
end

Rails web shop with Devise login and current_user

I am making a web shop application in Rails with Devise as a login gem. The structure is as follows:
A user table which has a basket_id column and in its model I have set has_one :basket
The basket table belongs_to :user
In my basket_controller.rb I want to use the create method to get the user
# POST /baskets
# POST /baskets.json
def create
#basket = Basket.new(basket_params)
#basket.user_id = current_user.id
respond_to do |format|
if #basket.save
format.html { redirect_to #basket, notice: 'Basket was successfully created.' }
format.json { render :show, status: :created, location: #basket }
else
format.html { render :new }
format.json { render json: #basket.errors, status: :unprocessable_entity }
end
end
The problem is that the current_user method that should be automatically generated by Devise is not found and therefore the user_id in the Baskets table is not set. Also I have no idea where would I set the basket_id in the Users table or this should be done automatically because of the relationship defined in the models?
Any help would be appreciated. Thanks!
For current_user to work you need to add before_filter :authenticate_user! to your controller. If that does not work maybe is a csrf problem that you can solve placing
= csrf_meta_tags
In your layout.

Create more than one object at once using the standard create method in Ruby on Rails

I am trying to use the standard create method created for Ruby/Rails projects and simply pass in an additional form field that tells the method how many objects to create (vs just creating one object). The standard create method looks like so:
def create
#micropost = Micropost.new(micropost_params)
respond_to do |format|
if #micropost.save
format.html { redirect_to #micropost, notice: 'Micropost was successfully created.' }
format.json { render :show, status: :created, location: #micropost }
else
format.html { render :new }
format.json { render json: #micropost.errors, status: :unprocessable_entity }
end
end
end
I want to pass in an additional data (form field called number_to_create) which tells the method how many of the microposts to create. I just added a new form field like this, in addition to the other micropost form field params:
<%= text_field_tag :number_to_create %>
My question is how do I modify the create method code such that it creates N number of micropost objects vs. just one. So if I pass in 3 from the form along with the other micropost attributes, the method creates 3 identical micropost objects, not just one as it currently does.
Thanks in advance for your help on this.
You could use the param as times
#microposts = Micropost.transaction do
[].tap do |microposts|
param[:number_to_create].times do
microposts << Micropost.create(micropost_params)
end
end
end
respond_to do |format|
if #microposts.all? &:persisted?
format.html { redirect_to #micropost, notice: 'Micropost was successfully created.' }
format.json { render :show, status: :created, location: #micropost }
else
format.html { render :new }
format.json { render json: #micropost.errors, status: :unprocessable_entity }
end
end
The transaction block is to make sure that either all of them gets saved, or none of them gets saved, this way you can fix your errors and recreate them without worrying of getting any stray saved objects

Can't associate post to Devise user

I'm having extreme difficulty associating post to a user registered in devise.
I generated a post scaffold and got everything set up correctly in Devise.
I added a migration to the post that included a user_id field
The user model has_many :posts
The Post model belongs_to :user
For some reason I cannot connect the user with the post. Am I missing something?
thanks all!
My controller for posts
def create
#user = User.find(params[:id])
#post = #user.posts.create(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render action: 'show', status: :created, location: #post }
else
format.html { render action: 'new' }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
First of all you need a user to associate a post:
#user = User.find(params[:id]) # or just use current_user as you are using Devise
As long as you have has_many association you can do the following:
#post = #user.posts.build(params[:post]) # to return newly created object without saving it to the database
#post = #user.posts.create(params[:post]) # to create and save record to the database
That's it.

Rails: Pass Parameters to New Controller during 'redirect_to'

I'm using Ryan Bates' Rails Cast on Wicked Wizard Forms to create a multi-step form. I don't have a current_user method defined (not using an authentication gem) - so, I'm trying to pass the user.id parameter during the redirect_to - unfortunately, I can't seem to get it to work. Any help is appreciated!
My user controller create method
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to controller: 'user_steps', id: 'user.id' }
#format.html { redirect_to #user, notice: 'User was successfully created.' }#
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
The user_steps controller that to which I am redirecting:
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :gender, :items, :brands, :final
def show
render_wizard
end
end
You should pass it through as a param, ideally, which the redirect_to method will do for you if you use a proper route path.
Example:
redirect_to(user_steps_path(#user))
In your case, if you don't have a named route, you might do this:
redirect_to(controller: 'user_steps', id: #user.to_param)
In URLs it's advisable to use the to_param method. id is used for database queries.
What you're passing in is literally 'user.id' as a parameter. It will not be evaluated.

Resources