I am new to Rails.I am creating a Rails project in which Product and Character are the models.I have the following Questions.
Q1. Is the given association between the two models is right?
class Product < ActiveRecord::Base
has_many :characters
end
class Character < ActiveRecord::Base
belongs_to :product
end
Q2. I created a link to add new character in the 'show' page of the products such if we click add new character it should display a 'new' page of characters in which i should have a dropdown list of character names(i.e., height,width,weight and color) but it is not.
It is showing NoMethodError in Characters#new error.
Error raised in the below line of my characters/new file.
collection_select(:product, :c_id, #character, :id, :name)
Note: I had created the values for name attribute as height,weight,width,color in the characters before migrating it.
Q3. If that works(i.e., Q2), i want to show the character name and value in the 'show page of products.For this how can i redirect to 'show' page of products..?
My characterscontroller for new,show and create:
def show
#product = Product.find(params[:id])
#character = Character.find(params[:id])
end
def new
#character = Character.new(params[:character])
#product = Product.find(params[:id])
end
def create
#character = Character.new(params[:character])
if #character.save
redirect_to :action => 'show', :id => #product.id
else
render :action => 'new'
end
end
Well now, after entering the values for the character in the characters/new and clicking create button it is trowing the following error
ActiveRecord::RecordNotFound in ProductsController#show
Couldn't find Product without an ID
I want to show the character name and value in the products/show. the above error is stopping me to do that..
My show method in productscontroller:
def show
#product = Product.find(params[:id])
#character = Character.find(:all)
end
The classes are correct to reflect the One-to-Many Relationship.
Problem with NoMethodError appears to be related to how you setup model, routes, controller & view.
Lets say in your model you have
attr_accessible :id, :name (I don't see this in your model and this is why the error!)
In character controller you need something like this:
def new
#character = Character.new
#product = Product.find(params[:character_id])
end
In the view(app/views/products/new.html.erb), you can add the link for new characters as:
<% link_to "Character", new_product_character_path(product) %>
the above assumes, you set your routes correct by having following in your config/routes.rb:
"resources :products"
"resources :characters"
Related
I am trying to create a simple blog application. In which each post can be associated with the tags. This is my view
_form.hmtl.erb
<%= form.collection_select(:tag_ids, #tags, :id, :name, {}, :multiple => true) %>
This is my controller
posts_controller.rb
def new
#post = #topic.posts.new
#tag = #post.tags.new
#tags = Tag.all
end
def create
#post = #topic.posts.new(post_params)
if #post.save
redirect_to topic_posts_path
else
render 'new'
end
end
def post_params
params.require(:post).permit(:title,:content,:post_image, tag_ids: [])
end
end
Model Post.rb
has_and_belongs_to_many :tags
I am getting an error while creating new post "NoMethodError in Posts#create", "undefined method `map' for nil:NilClass". I am not able to find where the error is.
You should have a join table with name posts_tags.rb
class Post_Tag < ApplicationRecord
belongs_to :tag
belongs_to :post
end
so you have already some tags which you want to be selected with post and join table should be updated with post_id and tag_id
change this in your form_partial: -
<%= select_tag "tag_ids", options_for_select(#tags,:id,:name),{}, multiple: true%> <br/><br/>
Note: - you should not use form.collection_select(:tag_ids...) Reason being form_for is used here for #post object and tag_ids is not attribute of #post object.
so on submitting form you should get array of tag's ids in params[:tag_ids]
def create
#post = #topic.posts.new(post_params)
if #post.save
#create join table for tags that are associated with post
#post.tags << Tag.find(params[:tag_ids])
redirect_to topic_posts_path
else
render 'new'
end
end
so here you can get
#post.tags => which will return you collection of tags associated with the post
if you are using this scenario you have to make MANY TO MANY relation between these two models.
has_and_belongs_to_many :tags
then in form you should use like
form.collection_select(:post, :tag_id, Tag.all, :id, :name_with_initial, prompt: true)
The #tags variable is missing from your create action in controller - you have declared it in new, but not in create. It may cause error in view if save was unsucessful and action tries to re-render the form.
Not sure if that's all, because I'm not sure where the error is thrown exactly. But as you said in one of the comments - it works when you use Tag.all instead of #tags.
I'm currently sitting with a problem where I am trying to add dynamic fields, and it works, however I need to created a second model for the nested attributes that are dynamically generated on the field.
I have a very easy and simple form which asks for a user's name, I want to be able to allow the user to click a button ("Add additional name") and have that field added dynamically then add another name. Can I do this using only the one model and controller, and without using nested forms?
This is what I currently have, but I do not want it working this way:
Controller
class GuestsController < ApplicationController
skip_before_filter :authenticate_user!, only: [:new, :create]
def index
#guest = Guest.all
end
def new
guest = Guest.new
#guest_form = GuestForm.new(guest)
end
def show
#guest = Guest.find(params[:id])
end
def create
guest = Guest.new
#guest_form = GuestForm.new(guest)
#guest_form.submit(guest_params)
if #guest_form.save
respond_to do |format|
format.html { redirect_to :back, notice: 'Thank you for replying' }
format.js
end
else
respond_to do |format|
format.html { render :new }
format.js
end
end
end
def destroy
#guest = Guest.find(params[:id])
#guest.destroy
redirect_to guests_path
end
private
def guest_params
params.require(:guest).permit(:status, :name, :message, plusones_attributes: [:id, :name, :oldness, :_destroy])
end
end
Models:
class Guest < ActiveRecord::Base
has_many :plusones, dependent: :destroy
belongs_to :user
end
class Plusone < ActiveRecord::Base
belongs_to :guest
end
So ideally I would like to use one Model, and allow additional fields to be entered of the exact same attribute, meaning if I have a Name: field, I should be able to click "Add another name" and add that name, and that would be saved as an individual guest in the table, meaning the first guest would have guest_id of 1, and the dynamically added field for another guest would add a guest with guest_id of 2.
I think you are making a mistake in thinking that there must be a one to one relationship between controllers and models. Have a look at ActiveRecord's Nested Attributes. That shows that you can have a single form submit data to an object and it's sub-objects in one action.
Have a look at fields_for, for information about how to configure a form to allow you to enter data for sub-objects.
When I've worked with nested set, I've found using a JavaScript tool like dynatree is the best way to display the nested data to users. I've also created a gem TreeDecorator, to make it easy to render nested sets as nested HTML lists, that can then be passed to dynatree.
I managed to find a solution that allowed me to keep my nested attributes and still be able to display the related child elements in the index page.
This is what I did to get the plusones related to the guest_id to display within index.html.erb
<% #guest.each do |guest| %>
<% guest.plusones.each do |plus| %>
<%= plus.name %>
<%= plus.oldness %>
<% end %>
<% end %>
I have really been scratching my head on this and would greatly appreciate help. I have a store setup where people can take courses. I have a course model, order model, and coupon model. Here are the associations in the models
class Course < ActiveRecord::Base
belongs_to :category
has_many :orders
has_many :coupons
end
class Order < ActiveRecord::Base
belongs_to :course
belongs_to :user
belongs_to :coupon
end
class Coupon < ActiveRecord::Base
belongs_to :course
has_many :orders
end
I have a very simple coupon model setup that has code and newprice columns. I want the ability for someone to be able to fill out the coupon form on the new order page and it to update the price.
In my my view for new order I have two forms one for the new order and one for the coupon. How do check in my controller if a user has entered the correct coupon code? How do I update the coupon price to be shown instead of the course price?
here is my order controller
class OrdersController < ApplicationController
before_action :set_order, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#orders = Order.all
end
def show
end
def new
course = Course.find(params[:course_id])
#course = Course.find(params[:course_id])
#order = course.orders.build
#coupon = Coupon.new
#user = current_user.id
#useremail = current_user.email
end
def discount
course = Course.find(params[:course_id])
#order = course.orders.build
#user = current_user.id
#useremail = current_user.email
end
def edit
end
def create
#order = current_user.orders.build(order_params)
if current_user.stripe_customer_id.present?
if #order.pay_with_current_card
redirect_to #order.course, notice: 'You have successfully purchased the course'
else
render action: 'new'
end
else
if #order.save_with_payment
redirect_to #order.course, notice: 'You have successfully purchased the course'
else
render action: 'new'
end
end
end
def update
if #order.update(order_params)
redirect_to #order, notice: 'Order was successfully updated.'
else
render action: 'edit'
end
end
def destroy
#order.destroy
redirect_to orders_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_order
#order = Order.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def order_params
params.require(:order).permit(:course_id, :user_id, :stripe_card_token, :email)
end
end
You can accomplish this with an AJAX request using the form_for helper with the :remote option.
Summary
Set :remote option to true for your coupons form to submit the AJAX request.
Create controller action to handle the AJAX request from the form.
Use JavaScript to respond to the controller action to update your orders form (the other form in your view) with the new price information, etc.
AJAX request using `:remote`
Here's some example code representing your coupon form :
<%= form_for #coupon, method: :post, url: check_coupon_code_path, remote: true do |f| %>
<%= f.text_field :coupon_code, :placeholder => "Enter your coupon" %>
<%= f.submit "Submit Coupon Code" %>
<% end %>
Notice the following:
The :remote option for the form_for tag is set to true.
The :url option is the path to your controller action in your CouponsController. Because the :remote option is set to true, the request will be posted to this :url option as an AJAX request.
In this code example, it's assuming it has a route defined like this in the routes.rb file to handle the AJAX request for checking the coupon code:
post 'check_coupon_code' => 'coupons#check_coupon_code'
Note: In the forms_for helper, the :url option appends _path to the prefix defined in the routes.rb file.
Bonus note: Use the command rake routes to see the available routes and their respective controller action targets.
Handle AJAX request in the Controller
In your CouponsController, define the action check_coupon_code to handle your AJAX request from the above form_for:
def check_coupon_code
# logic to check for coupon code here
respond_to do |format|
if # coupon code is valid
format.js {}
else
# some error here
end
end
end
Notice the format.js in the respond_to block of the action. This allows the controller to respond to the AJAX request with JavaScript to update your orders form in your view. You'll have to define a corresponding app/views/coupons/check_coupon_code.js.erb view file that generates the actual JavaScript code that will be sent and executed on the client side (or name the JavaScript file check_coupon_code.js.coffee if you're using CoffeeScript).
Updating with JavaScript
The JavaScript in your check_coupon_code.js.erb file will then update the price in your order form.
WARNING: Even if you use JavaScript to change the order price on the client-side (i.e. the browser), it is critical to validate the actual price again in the back-end (i.e. in your controller) in case some malicious user tries to manipulate the browser's request, etc.
You can see the official RailsGuide for another example.
At the time I have two models: Patient (has many), and Treatment (belongs to).
Until now I displayed the form for a new treatment on the patient show page and all worked fine. But now i want to outsource the treatments form to an new page. To visualize it better:
<%= render "treatments/form" %>
change to:
<% link_to "new", "treatments/form" %>
SO my problem is that I always become an route error:
No route matches [GET] "/patients/treatments/form"
But the routes look so, and i thought they would work:
resources :patients do
resources :treatments
resources :paintings
end
And the controller of the treatments:
class TreatmentsController < ApplicationController
def create
#patient = Patient.find(params[:patient_id])
#treatment = #patient.treatments.create(params[:treatment])
redirect_to patient_path(#patient)
end
def destroy
#patient = Patient.find(params[:patient_id])
#treatment = #patient.treatments.find(params[:id])
#treatment.destroy
redirect_to patient_path(#patient)
end
end
Since your proposed form is really just a way of creating a new patient treatment, you should consider following RESTful conventions and create a new TreatmentsController action called new:
# app/controllers/treatments_controller.rb
class TreatmentsController < ApplicationController
def new
#patient = Patient.find(params[:patient_id])
end
Since your treatments routes are a nested resource of the patients nested resource routes, you'll need to pass the patient_id to your link helper:
<%= link_to "New Patient Treatment", new_patient_treatment_path(#patient) %>
This will enable you to properly access your nested route for treatment#new.
My application has two associated models: Magazine and Article:
class Magazine < ActiveRecord::Base
has_one :article
end
class Article < ActiveRecord::Base
belongs_to :magazine
validation_presence_of :title
end
From the Magazine show page I can create a new Article, so my routes.rb is configured like:
resources :magazines, :shallow => true do
resources :articles
end
and in the Magazine show page I have the link "New article", like:
<%= link_to 'New article', new_magazine_article_path(#article)
and an article helper to pass correct parameters to the form_for:
module ArticlesHelper
def form_for_params
if action_name == 'edit'
[#article]
elsif action_name == 'new'
[#magazine, #article]
end
end
end
so I can use Article form_for like:
<%= simple_form_for(form_for_params) do |f| %> ...
The ArticlesController methods for new and create are:
respond_to :html, :xml, :js
def new
#magazine = Magazine.find(params[:magazine_id])
#article = Article.new
end
def create
#magazine = Magazine.find(params[:magazine_id])
#article = #magazine.build_article(params[:article])
if #article.save
respond_with #magazine # redirect to Magazine show page
else
flash[:notice] = "Warning! Correct the title field."
render :action => :new
end
end
The problem occurs when there is a validation error with the title attribute, and the action new is rendered. In this moment I get the message: undefined method `model_name' for NilClass:Class in the first line of form_for. I think it is because the #magazine parameter passed in the helper.
How could I solve this problem withou use redirect_to ? (I want to mantain the other attributes that were filled in the form .)
Your form_for_params method is returning nil, because the action_name is set to 'create', not 'new' or 'edit'.
Try this:
elseif action_name == 'new' or action_name == 'create'
[#magazine, #article]