Ruby on Rails - HABTM - Inserting data into 2 models in one controller - ruby-on-rails

I'm a beginner in RoR and am having issues on working with some of my models.
Basically I have a habtm relation between a product-ticket-reservation.
A product habtm reservations through tickets and vice-versa.
I also have a Supplier, which has_many :products and has_many :reservations.
What I want to do is after the user selects a supplier and sees it's products, he may then select the products he wants from that supplier.
In that reservations.new I got a form but since after the "submit" action I have to insert data in 2 models, I'm having issues with it.
When I create a reservation, it is supposed to create a reservation entry and a ticket entry at the same time, the ticket entry will have the reservation_id and the product_id as foreign keys.
My Reservations' view:
<%= form_for(#reservation) do |f| %>
Reservation Info
<div id="reservation_top"></div>
<div id="reservation">
<%= f.label :name %><br />
<%= f.text_field :name %>
<%= f.label :surname %><br />
<%= f.text_field :surname %>
(...)
<%= f.hidden_field :supplier_id, :value => #reservation.supplier_id %> #to get the supplier ID
Products:
<%= f.fields_for :tickets do |t| %>
<%= t.select("product_id",options_from_collection_for_select(#products, :id, :name))%>
#I also have another t.select and although this isn't my primary concern, I wanted this t.select option's to change according to what is selected on the previous t.select("product_id"). Something like a postback. How is it done in RoR? I've searched and only found observe_field, but I didn't understand it very much, can you point me in the right direction? thanks
<%end%>
<%= f.label :comments %>
<%= f.text_area :comments %>
<%= f.submit%>
<%end%>
Now i think the problem is in my controller, but I can't understand what to put there, I currently have:
def new
#supplier=Supplier.find(params[:supplier_id])
#reservation = Reservation.new(:supplier_id => params[:supplier_id])
#ticket = Ticket.new(:reservation_id => params[#reservation.id])
#products = Supplier.find(params[:supplier_id]).products
#ticket = #reservation.tickets.build
respond_to do |format|
format.html
format.json { render :json => #reservation }
end
end
def create
#reservation = Reservation.new(params[:reservation])
respond_to do |format|
if #reservation.save
#reservation.tickets << #ticket
format.html { redirect_to #reservation, :notice => 'Reservation Successful' }
else
format.html { render :action => "new" }
format.json { render :json => #reservation.errors, :status => :unprocessable_entity }
end
end
I'm now getting a
Called id for nil, which would mistakenly be 4
Is it because it is trying to create a ticket and it doesn't have the reservation_id?
I've never handled habtm associations before. Any tips?
Thanks in advance,
Regards

Take a look at the POST params for your create action in your log. That will show you exactly what data you have to work with from params when it comes time to save your data.
In
def create
#reservation = Reservation.new(params[:reservation])
respond_to do |format|
if #reservation.save
#reservation.tickets << #ticket
what is #ticket at that point? (There's your nil I believe)
I think it might also be interesting to see what your #reservation and #ticket look like in your new method right before generating the response... log a .inspect of each of those objects to make sure you have what you think you have.
And in a more complicated save like you have, I'd wrap it all in a transaction.

Related

Call controller from the view of another controller

i have a restaurant controller, and in the show method i render a form of another controller (dishes)
#new.html.erb (from dishes controller)
<%= render 'dishes/dish_form' %>
#show.html.erb (from restaurant controller)
<%= render template: 'dishes/new' %>
and this is the form:
<%= form_for #dish, :url => { :controller => "dishes", action_name => "create" } do |f| %>
<div class="field">
<%= f.label :dish_name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div class="field">
<%= f.label :image %>
<%= f.file_field :avatar %>
</div>
<div class="actions">
<%= f.submit 'Add new dish' %>
</div>
<% end %>
but when i try so add a dish, i have this error
this is my dishes controller:
def new
#dish = Dish.new
end
def create
#dish = Dish.new(dish_params)
respond_to do |format|
if #dish.save
format.html { redirect_to #restaurant, notice: 'dish was successfully created.' }
format.json { render action: 'show', status: :created, location: :restaurant }
else
format.html { render action: 'show', location: :restaurant }
format.json { render json: #restaurant.errors, status: :unprocessable_entity }
end
end
end
private
def dish_params
params.require(:dish).permit(:avatar, :name, :description)
end
and this are my models:
class Restaurant < ApplicationRecord
has_many :dish, inverse_of: :restaurant
accepts_nested_attributes_for :dish
end
class Dish < ApplicationRecord
belongs_to :restaurant
end
im learning rails so maybe is a dumb error but im stuck
you should try to this to the controller action where the form is rendered
#dish = Dish.new
as you directly call the 'create' action rails is missing the instance variable Dish.new. Therefore the error message. Normally rails works like this:
def new
#dish = Dish.new
end
and than you call the create action on 'submit'.
However, as it looks like you call the form from the #show action just add this code there and you will be fine. May be not the best solution but it will work like that. Add to #show
#dish = Dish.new
Sorry mate. Yes #dish is correct and not :dishes.
OK.
My latest guess is that you want the restaurants to be able to create many dishes.
So you should set up your models accordingly:
#restaurants_model
has_many :dishes
#dishes_model
belongs_to :restaurant
next is that you add a column called restaurant_id to your dishes table
t.string :restaurant_id
than in your restaurant controller #show
def show
end
if you use the routes as normal, e.g.
resources :restaurants
resources :dishes
the show action of the restaurant controller should give you a url that looks like this: localhost:3000/restaurants/1
where the 1 is the id of the restaurant.
This id you want to save to the dish by adding a hidden field
=f.hidden_field :restaurant_id, value: #restaurant.id
you need to permit the restaurant_id in the dishes controller params, e.g. dish_params. Just add
:restaurant_id
and it should be fine. This way the id will be saved to the dishes table and you can later call it from there.
This gives you the chance to call #restaurant.dishes which will show all dishes of that restaurant.
If you just want to redirect back you can use redirect :back
Otherwise, you can try to integrate a helper to get the right restaurant using the hidden restaurant_id field

Differences between Rails' has_one and has_many when it comes to automatic data building

Greetings StackOverflow community!
I have been learning Rails for the past few days and extending the basic blog example in the official Rails guide. I wanted to learned more about one-to-one connections since I'm planning on extending the Devise login system with a custom user data table and the rules of normalization dictate that I should make a different table for the actual auth data (the devise user table) and another for the app-specific user info (my own user table).
But now back to simple rails example. The example basically describes an app that can create blog posts, tag them and accept comments (comment's don't really matter right now). A post has_many tags and a tag belongs_to a post. Pretty straightforward. (same thing can be said about the comments, too, but I'll just stick with the tags for this example)
When a user wishes to create a NEW post, the controller has a call to #post = Post.new which prepares an empty post to be edited and created. It also needs to call post.tags.build to make sure that at least one 'baby' tag which would belong to the post is ready for editing 5.times do #post.tags.build end, for instance would prepare not one but five tags for editing.
In the controller's create method, it's enough to create a new post from params[:post] and #post.save it. It saves the tags automatically without any extra function calls needing to be made.
Here's where I started extending. I just wanted to add another table, called post_data, that would get linked one-to-one to the original posts table. post_data contains a foreign key to the post it belongs to, as well as a belongs_to :post instruction in its model. The post has_one :post_data. It also accepts_nested_attibutes_for :post_data.
Again, in the new method of the post controller, post_data needs to be initialized.#post.build_post_data does just this. The form displays just fine the two different models - one as post[title]...etc and the other as post_data[data_field_name_here].
However, in the create name, the post_data entry needs to be manually saved: #post.create_post_data(params[:post_data]) in order for it to be entered into the db. Otherwise it just doesn't get saved.
Now my question is this - how come the has_many objects of the post, the tags, just need to be prepared in the controller's new method, and then get saved in create automatically, while has_one links also need to be manually saved?
I'm just wandering as to why Rails would work like this.
Thanks in advance for your time and patience! :)
Edit: The code files!
posts_controller
class PostsController < ApplicationController
# GET /posts
# GET /posts.json
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #posts }
end
end
# GET /posts/1
# GET /posts/1.json
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render :json => #post }
end
end
# GET /posts/new
# GET /posts/new.json
def new
#post = Post.new
#post.build_post_data
5.times do #post.tags.build end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
# This works now - it creates all the needed resources automatically
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to #post, :notice => 'Post was successfully created.' }
else
format.html { render :action => "new" }
end
end
end
# PUT /posts/1
# PUT /posts/1.json
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, :notice => 'Post was successfully updated.' }
else
format.html { render :action => "edit" }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
end
end
end
posts/_form.html.erb
<%= form_for(#post) do |post_form| %>
<% if #post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% #post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= post_form.label :name %><br />
<%= post_form.text_field :name %>
</div>
<%= post_form.fields_for(:post_data) do |pdf| %>
<div class="field">
<%= pdf.label :herp, "DERP POST DATA" %><br />
<%= pdf.text_field :herp %>
</div>
<% end %>
<div class="field">
<%= post_form.label :title %><br />
<%= post_form.text_field :title %>
</div>
<div class="field">
<%= post_form.label :content %><br />
<%= post_form.text_area :content %>
</div>
<h2>Tags</h2>
<%= render :partial => 'tags/form',
# send some custom variables to the
# rendered partial's context
:locals => { :form => post_form } %>
<div class="actions">
<%= post_form.submit %>
</div>
<% end %>
and the posts.rb model
class Post < ActiveRecord::Base
validates :name, :presence => true
validates :title, :presence => true, :length => { :minimum => 5 }
has_many :comments, :dependent => :destroy
attr_accessible :post_data_attributes, :name, :title, :content
#let's just assume this makes sense, k?
has_one :post_data
accepts_nested_attributes_for :post_data
# TAGS
has_many :tags
accepts_nested_attributes_for :tags, :allow_destroy => :true,
# reject if all attributes are blank
:reject_if => lambda { |attrs| attrs.all? { |k, v| v.blank? } }
end
Final edit:
Fixed up all the code, syncing it with my working code! :D
If anybody from the distant future still has this issue and my code doesn't work, send me a personal message and we'll sort it out! :)
I believe this:
<%= fields_for(:post_data) do |pdf| %>
Should be this:
<%= post.fields_for(:post_data) do |pdf| %>
Can you try that?

Rails model/form layout question

I'm making a recipe-manager (who isn't as their first app?) in Rails, and here is my layout:
Ingredient belongs to Recipe
Recipe has many Ingredients
What is the best way to make a form that reflects this relationship? I was thinking an input that, when one is filled, creates another, so there is always 'one more' at the end of the form for ingredients.
Once I have the UI made, what would the structure of the model and controller look like? Right now I have the scaffolded controller create method:
def create
#recipe = Recipe.new(params[:recipe])
respond_to do |format|
if #recipe.save
format.html { redirect_to(recipes_path, :notice => 'You made a new recipe!') }
format.xml { render :xml => #recipe, :status => :created, :location => #recipe }
else
format.html { render :action => "new" }
format.xml { render :xml => #recipe.errors, :status => :unprocessable_entity }
end
end
end
Should the params[:recipe] just be a more deeply nested object/hash/dictionary, that contains an array of ingredients or something?
Thanks for any guidance here.
You should use accepts_nested_attributes here.
Some links:
API:
http://apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for
Screencasts:
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
So your model will look like this
class Recipie < ActiveRecord::Base
has_many :ingredients
accepts_nested_attributes_for :ingridients, :allow_destroy => true
end
Views:
<%= form_for #recipe do |f| %>
... # reciepe fields
<%= f.fields_for :ingridients do |i| %>
... # your ingridients forms
<% end %>
...
<% end %>
And controller
def create
#recipe = Recipe.new(params[:recipe])
#recipe.save # some save processing
end
Just add ingredients by comma delimited.
This can be a text_field_tag because you will need to parse it and save each word spaced by a comma with a before save.
class Recipie < ActiveRecord::Base
has_many :ingredients
before_save :add_ingredients
attr_accessor :ingredients_to_parse #this will be the text_field_tag
def add_ingredients
#create an array of ingredients from #ingredients_to_parse
#then loop through that array i.e. you have your ingredients_array
ingredients_array.each do
Ingredient.create(:recipe => self, :other_params => 'stuff')
end
#there are a lot of ways, I just used create to show you how to add it
end
end
So then in your form just have that text_field_tag
<%= form_for(#recipe) do |f| %>
<% f.text_field :name %>
<% text_field_tag :ingredients_to_parse %>
<%= f.submit %>
<% end %>
Then you can add Javascript so that each time a comma is added in that text_field_tag you can just use some js to to so fancy stuff.
This way it will work when servers are slow, js is not working well, etc. It's always a good idea to get the HTML version going first too.
Good luck, let me know if you have questions/problems.

Rails: Form for selecting an existing parent when creating new child records?

I have a has_many and belongs_to association set up between two models: Project and Task.
I'd like to be able to create a form which enables me to create a new Task and assign an existing Project as a parent. For example, this form might have a pulldown for selecting from a list of existing projects.
There are only a finite set of projects available in this application, so I've created Project records via a seeds.rb file. I do not need to make a form for creating new Projects.
I believe I've achieved a solution by using a collection_select form helper tag in the new Task form. I'm pretty happy with how this works now, but just curious if there are other approaches to this problem.
#models/project.rb
class Project < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
end
#models/task.rb
class Task < ActiveRecord::Base
belongs_to :project
end
#controllers/tasks_controller.rb
class TasksController < ApplicationController
def new
#task = Task.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #task }
end
end
def create
#task = Task.new(params[:task])
respond_to do |format|
if #task.save
format.html { redirect_to(#task, :notice => 'Task was successfully created.') }
format.xml { render :xml => #task, :status => :created, :location => #task }
else
format.html { render :action => "new" }
format.xml { render :xml => #task.errors, :status => :unprocessable_entity }
end
end
end
end
#views/new.html.erb
<h1>New task</h1>
<%= form_for(#task) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="select">
<%= collection_select(:task, :project_id, Project.all, :id, :name) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<%= link_to 'Back', tasks_path %>
I just reviewed your code and this looks fantastic to me. One small tweak:
<%= f.collection_select(:project_id, Project.all, :id, :name) %>
This is just slightly cleaner in that you're still using the |f| block variable
Since you mentioned other approaches, I would definitely mention and actually recommend, you use formtastic. The associations are handled automatically and keeps your code clean and also gives you some great customization options.

validates_presence_of not working properly...how to debug?

In my Review model, I have the following:
class Review < ActiveRecord::Base
belongs_to :vendor
belongs_to :user
has_many :votes
validates_presence_of :summary
end
I submit a new entry as follows in the URL:
vendors/9/reviews/new
The new.html.erb contains a form as follows:
<%= error_messages_for 'review' %>
<h1>New review for <%= link_to #vendor.name, #vendor%></h1>
<% form_for(#review, :url =>vendor_reviews_path(#vendor.id)) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :summary %><br />
<%= f.text_area :summary, :rows=>'3', :class=>'input_summary' %>
<%= f.hidden_field :vendor_id, :value => #vendor.id %>
</p>
<p>
<%= f.submit 'Submit Review' %>
</p>
<% end %>
When I leave the field for :summary blank, I get an error, not a validation message:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.name
Extracted source (around line #3):
1: <%= error_messages_for 'review' %>
2:
3: <h1>New review for <%= link_to #vendor.name, #vendor%></h1>
I don't understand what is happening, it works if :summary is populated
def new
#review = Review.new
#vendor = Vendor.find(params[:vendor_id])
#review = #vendor.reviews.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #review }
end
end
def create
#review = Review.new(params[:review])
##vendor = Vendor.find(params[:vendor_id]) #instantiate the vendor from the URL id -- NOT WOKRING
##review = #vendor.reviews.build #build a review with vendor_id -- NOT working
#review = #current_user.reviews.build params[:review]#build a review with the current_user id
respond_to do |format|
if #review.save
flash[:notice] = 'Review was successfully created.'
format.html { redirect_to review_path(#review) }
format.xml { render :xml => #review, :status => :created, :location => #review }
else
format.html { redirect_to new_review_path(#review) }
format.xml { render :xml => #review.errors, :status => :unprocessable_entity }
end
end
end
My guess is that when it fails it is going to redirect_to new_review_path(#review) and so doesn't know the vendor it. How can I redirect to vendor/:vendor_id/reviews/new instead?
You probably don't have #vendor member variable set - but to fix this, it would be more correct to use not #vendor directly, but through your #review variable instance.
If you are creating new review, you already have #review member variable created, and you simply are populating fields in it - so, you need to set the vendor for #review (unless it's optional)... it would be more correct to use #review.vendor.name instead.
(If vendor is optional, then you obviously must catch all vendor.nil? cases.)
What code do you have in the new and create actions in your ReviewsController?
I suspect that your new Review is failing validation because the summary field is blank and then when the form is redisplayed on validation failure, the #vendor instance variable is nil.
You need to make sure that #vendor is assigned a value for both code paths.
I think you need to render :action => 'new' instead of your redirect_to new_review_path(#review). This will keep your error_messages on the #review object. By redirecting you are losing the old object and creating a new one.
As others has said, you also need to make sure you re-populate the #vender variable in your create method before rendering the view.
PS. I like to use the ardes resources_controller plugin for bog standard controller actions like these, makes life a lot easier for me and it handles nested resources really well.

Resources