Ruby on Rails - No method error - ruby-on-rails

I'm completing this airbnb clone course (https://code4startup.com/projects/build-airbnb-with-ruby-on-rails-level-1) but have diverted a bit in order to complete my own project; a marketplace for education camps. Therefore I've added an additional model. It now has User>Listing>Course.
Since adding this course I keep receiving the following error upon running the server and going to localhost:3000/courses/new I've tried searching for the problem on stackoverflow but I'm not experienced enough to describe and therefore find the issue, I'd appreciate any ideas.
Error Message
undefined method `curriculum_type' for #<Listing:0x007fb776ac0f50>
Extracted source (around line #15):
<div class="form-group">
<label>Course Type</label>
<%= f.select :curriculum_type, [["English Language", "English Language"], ["Culture", "Culture"], ["Sports", "Sports"], ["Tech/Science", "Tech/Science"], ["Adventure", "Adventure"], ["Mixture", "Mixture"]],
id: "type", prompt: "Select...", class: "form-control" %>
</div>
</div>
Models
class User < ApplicationRecord
has_many :listings
has_many :courses, :through => :listings
end
class Listing < ApplicationRecord
belongs_to :user
has_many :courses
end
class Course < ApplicationRecord
belongs_to :listing
end
Courses Controller
class CoursesController < ApplicationController
before_action :set_course, except: [:index, :new, :create]
before_action :authenticate_user!, except: [:show]
def index
#courses = current_user.listings.courses
end
def new
#course = current_user.listings.build
end
def create
#course = listings.build(course_params)
if #course.save
redirect_to listing_course_path(#course), notice: "Saved..."
else
render :new, notice: "Something went wrong..."
end
end
def show
def listing
end
def pricing
end
def description
end
def photo_upload
end
def amenities
end
def location
end
def update
if #course.update(course_params)
flash[:notice] = "Saved..."
else
flash[:notice] = "Something went wrong..."
end
redirect_back(fallback_location: request.referer)
end
private
def set_course
#course = Course.find(params[:id])
end
def room_params
params.require(:course).permit(:name, :course_type, :summary, :address, :course_places, :start_date, :finish_date, :price)
end
end
end
Courses new.html.erb
<div class="panel panel-default">
<div class="panel-heading">
Create your course listing
</div>
<div class="panel-body">
<div class="devise-container">
<%= form_for #course do |f| %>
<div class="row">
</div>
<div class="col_md_4 select">
<div class="form-group">
<label>Course Type</label>
<%= f.select :curriculum_type, [["English Language", "English Language"], ["Culture", "Culture"], ["Sports", "Sports"], ["Tech/Science", "Tech/Science"], ["Adventure", "Adventure"], ["Mixture", "Mixture"]],
id: "type", prompt: "Select...", class: "form-control" %>
</div>
</div>
<div class="col_md_4 select">
<div class="form-group">
<label>Places</label>
<%= f.select :course_places, [["1", 1], ["2", 2], ["3", 3], ["4", 4], ["5", 5], ["6", 6], ["7", 7]],
id: "places", prompt: "Select...", class: "form-control" %> -->
</div>
</div>
</div>
<div><%= f.submit "Create My Course", class: "btn btn-primary-green" %></div>
<% end %>
</div>
</div>
</div>
In Rails Console I can create all models, but it seems that it is not recognising the Courses Model when I start the server

Your controller is building a Listing object, but your curriculum_type attribute is an attribute for a Course. You'll want to modify your controller to be building a Course object if that's the case, or add the attribute to Listing.

Related

Ruby on Rails form isn't capturing values in select multiple field

I followed the example here to create a select multiple element for my form: Ruby on Rails -- multiple selection in f.select
However, when the submit button is clicked the values from the select field are not captured. I had the same issue when using checkboxes. It works when I change it to a text field, though. Am I missing something obvious? How do I capture the values that the user selects from the select field? My code is below.
The form: new.html.erb
<%= form_for #service do |f| %>
<div class="row">
<h3>Operating hours</h3>
<p>Please select the days you are open. Hold CTRL (Windows) or Command (Mac) to select multiple.</p>
<div class="form-group">
<div class="col-md-6">
<%= f.select(:work_days, [['Monday', 'Monday'],
['Tuesday', 'Tuesday'],
['Wednesday', 'Wednesday'],
['Thursday', 'Thursday'],
['Friday', 'Friday'],
['Saturday', 'Saturday'],
['Sunday', 'Sunday']
],{},
{ :multiple => true, :size => 7 }
) %>
</div> <!-- .col-md-6 -->
</div> <!-- .form-group -->
</div> <!-- .row -->
<!-- the code in the div below works -->
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label for="work_days">work days</label>
<%= f.text_field :work_days, placeholder: "Type the days you are available", class: "form-control", required: true, id: "work_days" %>
</div>
</div>
</div> <!-- .row -->
<div class="text-center">
<%= f.submit "Save", class: "btn btn-success" %>
</div>
<% end %>
The relevant controller code: services_controller.rb
class ServicesController < ApplicationController
# require the user to be logged in to edit and create, but not to view
before_action :set_service, except: [:index, :new, :create]
before_action :authenticate_user!, except: [:show]
before_action :is_authorised, only: [:listing, :update]
def new
#service = current_user.services.build
end
def create
#service = current_user.services.build(service_params)
if #service.save
redirect_to listing_service_path(#service), notice: "Saved..."
else
flash[:alert] = "Something went wrong..."
render :new
end
end
private
def set_service
#service = Service.find(params[:id])
end
def service_params
params.require(:service).permit(:title, :description, :work_days, :open_time, :close_time)
end
end
In service_params, work_days should accept an array so it will accept multiple values:
def service_params
params.require(:service).permit(
:title,
:description,
:open_time,
:close_time,
work_days: []
)
end

NoMethodError - Ruby on Rails

I'm working through Code4Startup airbnb course (https://code4startup.com/projects/build-airbnb-with-ruby-on-rails-level-1) but I'm trying to adapt it for my own idea - an airbnb for education courses for my local colleges. I'm new to ruby and stackoverflow, I've tried searching for the solution for ages but my issue is that I don't know how to correctly describe it, so please explain it like I'm 5!
The error I'm receiving:
The error seems to be suggesting that my course model isn't being built correctly or that it is reverting back to the Listing model somehow (perhaps because my course_params aren't correct?)
NoMethodError in Courses#new
Showing /Users/Jack_R/code/rails/planet_study/app/views/courses/new.html.erb where line #22 raised:
undefined method `type' for #<Listing:0x007fc25da72ea8>
Extracted source (around line #22):
20
21
22
<div class="form-group">
<label>Course Type</label>
<%= f.select :type, [["English Language", "English Language"], ["Culture", "Culture"], ["Sports", "Sports"], ["Tech/Science", "Tech/Science"], ["Adventure", "Adventure"], ["Mixture", "Mixture"]],
id: "type", prompt: "Select...", class: "form-control" %>
</div>
</div>
My Models
I have a User model, a Listing model and a Course model (User>Listing>Course):
class User < ApplicationRecord
has_many :listings
has_many :courses, :through => :listings
end
Listing model:
class Listing < ApplicationRecord
belongs_to :user
has_many :courses
end
Course model:
class Course < ApplicationRecord
belongs_to :listing
end
Courses Controller
class CoursesController < ApplicationController
before_action :set_course, except: [:index, :new, :create]
before_action :authenticate_user!, except: [:show]
def index
#courses = current_user.listings.courses
end
def new
#course = current_user.listings.build
end
def create
#course = listings.build(course_params)
if #course.save
redirect_to listing_course_path(#course), notice: "Saved..."
else
render :new, notice: "Something went wrong..."
end
end
def show
def listing
end
def pricing
end
def description
end
def photo_upload
end
def amenities
end
def location
end
def update
if #course.update(course_params)
flash[:notice] = "Saved..."
else
flash[:notice] = "Something went wrong..."
end
redirect_back(fallback_location: request.referer)
end
private
def set_course
#course = Course.find(params[:id])
end
def room_params
params.require(:course).permit(:name, :type, :summary, :address, :places, :start_date, :finish_date, :price)
end
end
end
Course new.html.erb
<div class="panel panel-default">
<div class="panel-heading">
Create your course listing
</div>
<div class="panel-body">
<div class="devise-container">
<%= form_for #course do |f| %>
<div class="row">
</div>
<div class="col_md_4 select">
<div class="form-group">
<label>Course Type</label>
<%= f.select :type, [["English Language", "English Language"], ["Culture", "Culture"], ["Sports", "Sports"], ["Tech/Science", "Tech/Science"], ["Adventure", "Adventure"], ["Mixture", "Mixture"]],
id: "type", prompt: "Select...", class: "form-control" %>
</div>
</div>
<div class="col_md_4 select">
<div class="form-group">
<label>Places</label>
<%= f.select :places, [["1", 1], ["2", 2], ["3", 3], ["4", 4], ["5", 5], ["6", 6], ["7", 7]],
id: "places", prompt: "Select...", class: "form-control" %>
</div>
</div>
</div>
<div><%= f.submit "Create My Course", class: "btn btn-primary-green" %></div>
<% end %>
</div>
</div>
</div>
type is a reserved word in Rails. You can create a model with a type attribute, but you can't perform actions on this model until you rename the type attribute.
If you try to create a new record, through the rails console, you'll see a message like:
$ rails console
[1] pry(main)> Course.new(name: 'first', type: 'some type')
ActiveRecord::SubclassNotFound: The single-table inheritance mechanism
failed to locate the subclass: 'some type'. This error is raised because
the column 'type' is reserved for storing the class in case of
inheritance. Please rename this column if you didn't intend it to be
used for storing the inheritance class or overwrite
Course.inheritance_column to use another column for that information.
from
/Users/***/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/activerecord-5.1.4/lib/active_record/inheritance.rb:196:in
`rescue in find_sti_class'
As the message says, you must rename the type attribute to be able to work with it, to do it just run a migration to change this name and edit the file created, like:
$ rails generate migration rename_type_to_type_of
In the generated file use the rename_colum method, and specify first the model, then the old attribute name, and then the new one, like:
class RenameTypeToTypeOf < ActiveRecord::Migration[5.1]
def change
rename_column :courses, :type, :type_of
end
end
After that you can run rails db:migrate. Note type_of is just a poor suggestion of mine, you can adapt it as you want.

Nested fields_for form wont create child objects rails 5

sale.rb "Parent"
class Sale < ActiveRecord::Base
has_many :branch_history_solds
accepts_nested_attributes_for :branch_history_solds, :reject_if => lambda { |a| a[:content].blank? },
:allow_destroy => true
end
class SalesController < ApplicationController
before_action :set_sale, only: [:show, :edit, :update, :destroy]
# GET /sales
# GET /sales.json
def index
#sales = Sale.all
end
# GET /sales/1
# GET /sales/1.json
def show
end
# GET /sales/new
def new
#sale = Sale.new
#sale.branch_history_solds.build
end
# GET /sales/1/edit
def edit
end
# POST /sales
# POST /sales.json
def create
#sale = Sale.create(sale_params)
# #sale.branch_history_solds.build
respond_to do |format|
if #sale.save
format.html { redirect_to #sale, notice: 'Sale was successfully created.' }
format.json { render :show, status: :created, location: #sale }
else
format.html { render :new }
format.json { render json: #sale.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /sales/1
# PATCH/PUT /sales/1.json
def update
#sale = Sale.find(params[:id])
respond_to do |format|
if #sale.update_attributes(sale_params)
format.html { redirect_to #sale, notice: 'Sale was successfully updated.' }
format.json { render :show, status: :ok, location: #sale }
else
format.html { render :edit }
format.json { render json: #sale.errors, status: :unprocessable_entity }
end
end
end
# DELETE /sales/1
# DELETE /sales/1.json
def destroy
#sale.destroy
respond_to do |format|
format.html { redirect_to sales_url, notice: 'Sale was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_sale
#sale = Sale.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def sale_params
params.require(:sale).permit(:receipt_no, :customer_name, :phone_number, :email, :branch_id, :paid, branch_history_sold_attributes: [:id, :sold, :branch_product_id])
end
end
branch_history_sold.rb "Child"
class BranchHistorySold < ActiveRecord::Base
belongs_to :sale
end
class BranchHistorySoldsController < ApplicationController
def index
#search = BranchHistorySold.ransack(params[:q])
#branch_sold_histories = #search.result(distinct: true).group(:name).sum(:sold)
end
def create
#sale = Sale.find(params[:sale_id]) # Find specific branch_product we will be working with
#branch_history_sold = #sale.branch_history_solds.create(branch_history_sold_params) # Enable whitelisted attributes to get created
flash[:notice] = "New products have been Sold from branch" # flash notice will show immediately after branch_history_sold gets created
redirect_to branch_product_path(#branch_product) # redirect to branch_product show page
end
def destroy
#sale = Sale.find(params[:sale_id]) # Find specific branch_product we will be working with
#branch_history_sold = #sale.branch_history_solds.find(params[:id]) # Find specific branch_history_sold that will be destroyed
#branch_history_sold.destroy # destroy branch_history_sold
flash[:notice] = "Newly sold products have been added back to branch" # flash notice will show immediately after branch_history_sold is destroyed
redirect_to branch_product_path(#branch_product) # redirect to branch_product show page
end
private
def branch_history_sold_params
params.require(:branch_history_sold).permit(:sold, :customer_name) # whitelisted attributes
end
end
And finally my form with the fields_for attribute
<div class="container">
<div class="row">
<%= form_for #sale, html: { class: "form-horizontal" } do |f| %>
<!-- Text input-->
<div class="form-group">
<label class="col-md-1 control-label">R/NO</label>
<div class="col-md-6">
<%= f.collection_select(:branch_id, Branch.all, :id, :name) %>
</div>
</div>
<!-- Text input-->
<div class="form-group">
<label class="col-md-1 control-label">R/NO</label>
<div class="col-md-6">
<%= f.text_field :receipt_no, placeholder: "Receipt number", class: "form-control input-md" %>
</div>
</div>
<!-- Text input-->
<div class="form-group">
<label class="col-md-1 control-label" >Name</label>
<div class="col-md-6">
<%= f.text_field :customer_name, placeholder: "Prince Abalogu", class: "form-control input-md" %>
</div>
</div>
<!-- Appended Input-->
<div class="form-group">
<label class="col-md-1 control-label">Number</label>
<div class="col-md-6">
<%= f.text_field :phone_number, placeholder: "08185438075", class: "form-control input-md" %>
</div>
</div>
<!-- Appended Input-->
<div class="form-group">
<label class="col-md-1 control-label">E-mail</label>
<div class="col-md-6">
<%= f.text_field :email, placeholder: "example#yahoo.com", class: "form-control input-md" %>
</div>
</div>
<!-- Appended Input-->
<%= f.fields_for :branch_history_solds, #sale.branch_history_solds.build do |b| %>
<div class="form-group">
<label class="col-md-1 control-label">Product</label>
<div class="col-md-6">
<%= b.number_field :sold, placeholder: "Quantity" %>
</div>
</div>
<div class="form-group">
<label class="col-md-1 control-label"></label>
<div class="col-md-6">
<% #branch = BranchProduct.where :branch_id, 19 %>
<%= b.collection_select(:branch_product_id, BranchProduct.where(branch_id: params[:branch_id] ), :id, :name ) %> Select Product
</div>
</div>
<% end %>
<!-- Appended Input-->
<div class="form-group">
<label class="col-md-1 control-label"></label>
<div class="col-md-6">
<%= f.check_box :paid %> Paid
</div>
</div>
<!-- Button (Double) -->
<div class="form-group">
<label class="col-md-1 control-label"></label>
<div class="col-md-8">
<%= f.button :submit %>
</div>
</div>
<% end %>
</div>
</div>
The form displays but the only problem im having now is that it doesnt create any branch_history_solds after I submit
The problem is that the lambda you are using to evaluate if the nested records should be rejected will always return true since your form/model does not have a content attribute:
class Sale < ActiveRecord::Base
has_many :branch_history_solds
accepts_nested_attributes_for :branch_history_solds, :reject_if => lambda { |a| a[:content].blank? },
:allow_destroy => true
end
You're also whitelisting the wrong attributes branch_history_sold not branch_history_solds.
You need to change this to be an attribute that is actually passed:
class Sale < ActiveRecord::Base
has_many :branch_history_solds
accepts_nested_attributes_for :branch_history_solds, :reject_if => lambda { |a| a[:branch_product_id].blank? },
:allow_destroy => true
end
However your general setup is just plain strange and I don't know if its just the naming but it does not make much sense.
If you want to create a point of sales system or order management system you would do it like so:
class Order
belongs_to :customer
has_many :line_items
has_many :products, through: :line_items
accepts_nested_attributes_for :line_items,
allow_destroy: true,
reject_if: -> { |li| li[:product_id].blank? || li[:quantity].blank? }
end
# columns:
# - order_id [integer, index, foreign key]
# - product_id [integer, index, foreign key]
# - quantity [decimal or integer]
# - price [decimal]
# - subtotal [decimal]
class LineItem
belongs_to :order
belongs_to :product
end
class Product
has_many :line_items
has_many :orders, through: :line_items
end
class OrdersController
def new
end
def create
#order = Order.new(order_params) do
order.customer = current_user
end
if (#order.save)
else
end
end
private
def order_params
params.require(:order)
.permit(:foo, :bar, line_item_attributes: [:product_id, :quantity])
end
end
Note that you should only let users pass an extremely limited number of params - never take things like prices from the user. The actual pricing logic should be done on the model layer. You also wan't to detach the customer details from the order model - otherwise every repeat order will duplicate data. I would spend some time getting the actual domain model down before adding additional features like search.

Invalid single-table inheritance type: [duplicate]

This question already has answers here:
Rails -- use type column without STI?
(5 answers)
Closed 8 years ago.
I'm getting this error and I don't know what to do. It says
Invalid single-table inheritance type: 1 is not a subclass of Game
It says that this error is occuring at the 9 in games_controller.rb.
i.e.
#game = current_user.created_games.build(game_params)
I don't understand what is happening and what I should write here to specify the cause of this error. I copy&paste game.rb, user.rb, application_controller.rb and games_controller.rb here.
game.rb is
class Game < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
end
user.rb is
class User < ActiveRecord::Base
has_many :created_games, class_name: 'Game', foreign_key: :owner_id
def self.find_or_create_from_auth_hash(auth_hash)
provider = auth_hash[:provider]
uid = auth_hash[:uid]
nickname = auth_hash[:info][:nickname]
image_url = auth_hash[:info][:image]
User.find_or_create_by(provider: provider, uid: uid) do |user|
user.nickname = nickname
user.image_url = image_url
end
end
end
application_controller.rb is
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :logged_in?
private
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end
def logged_in?
!!session[:user_id]
end
def authenticate
return if logged_in?
redirect_to root_path, alert:'Please log in.'
end
def created_by?(user)
return false unless user
owner_id == user.id
end
end
games_controller.rb
class GamesController < ApplicationController
before_action :authenticate
def new
#game = current_user.created_games.build
end
def create
#game = current_user.created_games.build(game_params)
if #game.save
redirect_to #game, notice: 'You created game.'
else
render :new
end
end
private
def game_params
params.require(:game).permit(
:name, :content, :type
)
end
end
Added
new.html.erb
<% now = Time.zone.now %>
<div class="page-header">
<h1>Create Game</h1>
</div>
<%= form_for(#game, class: 'form-horizontal', role:'form') do |f| %>
<% if #game.errors.any? %>
<div class="alert alert-danger">
<ul>
<% #game.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :type %>
<div>
<%= f.select :type, options_for_select: [['basic', 1]], class: 'form-control' %>
</div>
</div>
<div class="form-group">
<%= f.label :content %>
<%= f.text_area :content, class: 'form-control', row: 10 %>
</div>
<%= f.submit 'Create', class: 'btn btn-default', data: { disable_with: 'Creating...' } %>
<% end %>
When I try to create new game, console says as follows.
Started POST "/games" for 127.0.0.1 at 2015-01-29 01:50:29 +0900
Processing by GamesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"2W57cDKT2552Xgnh2MLi17uQpcrqDkmxhQJQa1qNuJNsZb0R10l/AU37y+KO9DysCp56aVTFBE/MqRoimMnjYQ==", "game"=>{"name"=>"this is name", "type"=>"1", "content"=>"this is content"}, "commit"=>"Create"}
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
Completed 500 Internal Server Error in 32ms
ActiveRecord::SubclassNotFound - Invalid single-table inheritance type: 1 is not a subclass of Game:
So to answer the question, you cannot have a column name called type in your migrations. I believe there are ways around this, but it's good practice to just rename your column something else.

Adding a upvote/score to an existing tasks controller in Ruby on Rails?

I'm very new to Rails and I'm attempting to build a Todo app with the ability to upvote Todos. Currently my Todo model is as follows;
class Todo < ActiveRecord::Base
belongs_to :user
belongs_to :house
default_scope { order("score, created_at DESC") }
end
My Todos controller is as follows;
class TodosController < ApplicationController
before_filter :authenticate_user!
before_filter :assign_house_from_invite!
before_filter :ensure_house!
def index
#todo = Todo.new user: current_user
#todos = current_user.house.todos
end
def create
#todo = Todo.new(todo_params.merge(user: current_user, house: current_user.house))
#todo.save
end
protected
def todo_params
params.require(:todo).permit(:task, :vote)
end
def assign_house_from_invite!
end
def ensure_house!
redirect_to new_house_path if current_user.house.nil?
end
end
and my Todos erb partial is;
<div class="row input-append">
<%= form_for(#todo, remote: true, id: "new_todo_form") do |t| %>
<%= t.text_field :task, :placeholder => "Add new task", :class => "span2", :id => "appendedInputButton", :type => "text" %>
<%= t.submit '+', :id =>'plus', :class => 'btn', :type => 'button' %>
<% end %>
</div>
<div id="todos" class="row">
<% #todos.each do |todo| %>
<%= render partial: 'todo', locals: {todo: todo} %>
<% end %>
</div>
I'm wondering what do I have to add to each of these to enable each task to have an initial score assigned of 0 and then for it to be able to be incremented when it is upvoted? Any help would be much appreciated.

Resources