I created a polymorphic relation in a book reviewing app that I am writing. My app has the nested models: Piece >> Section >> Subsection >> Subsubsection, and I have created a model which belongs to all of these called KeyConcept, my intention being that each of these can have a key concept. When trying to display the "Show" view of the "Piece" model i get the following error:
NameError in Pieces#show
uninitialized constant Piece::Keyconcept
<h5>Summary: </h5><p><%= simple_format(#piece.summary) %></p>
<br/>
<% #piece.keyconcepts.each do |concept| %>
<li>
<%= link_to concept.definition, r, class: 'section_name' %>
</li>
So the routes.rb file looks like this:
resources :pieces do
resources :sections do
resources :subsections do
resources :subsubsections
end
end
resources :links
end
resources :pieces, :sections, :subsections, :subsubsections do
resources :connections, only: [:index, :new, :edit, :update, :destroy, :create]
resources :keyconcepts, only: [:index, :new, :edit, :update, :destroy, :create, :show]
end
the model.rb files look like this:
in models/concerns/conceptable.rb
module Conceptable
extend ActiveSupport::Concern
included do
has_many :keyconcepts, as: :conceptable
end
end
key_concept.rb
class KeyConcept < ActiveRecord::Base
belongs_to :conceptable, polymorphic: true
end
piece.rb
class Piece < ActiveRecord::Base
include Connectable
include Conceptable
has_many :sections
has_many :subsections, through: :sections
has_many :links
end
I don't know if this is a problem in the controller?
class KeyconceptsController < ApplicationController
include KeyconceptsHelper
def whereami
if params[:piece_id]
#piece = Piece.find(params[:piece_id])
here = #piece
parameter = :piece_id
type = "Piece"
elsif params[:section_id]
#section = ::Section.find(params[:section_id])
#piece = #section.piece_id
here = #section
parameter = :section_id
type = "Section"
elsif params[:subsection_id]
#subsection = Subsection.find(params[:subsection_id])
#section = #subsection.section_id
#piece = Section.find(id=#section).piece_id
here = #subsection
parameter = :subsection_id
type = "Subsection"
elsif params[:subsubsection_id]
#subsubsection = Subsubsection.find(params[:subsubsection_id])
#subsection = #subsubsection.subsection_id
#section = Subsection.find(id=#subsection).section_id
#piece = Section.find(id=#section).piece_id
here = #subsubsection
parameter = :subsubsection_id
type = "Subsubsection"
end
end
def redirect
if type == "Piece"
redirect_to piece_path(#piece)
elsif type == "Section"
redirect_to piece_section_path(#piece, #section)
elsif type == "Subsection"
redirect_to piece_section_subsection_path(#piece, #section, #subsection)
elsif type == "Subsubsection"
redirect_to piece_section_subsection_subsubsection_path(#piece, #section, #subsection, #subsubsection)
end
end
def index
whereami.call
end
def show
whereami.call
r = redirect.call
end
def new
#keyconcept = KeyConcept.new
#keyconcept.conceptable_id = here.id
end
def create
whereami.call
#keyconcept = KeyConcept.new(keyconcept_params)
#keyconcept.conceptable_id = params[parameter]
#keyconcept.conceptable_type = type
#keyconcept.save
redirect.call
end
def destroy
here.destroy
redirect.call
flash.notice = "#{type} '#{here.name}' from '#{#piece.name}' deleted!"
end
def edit
whereami.call
end
def update
whereami.call
here.update(keyconcept_params)
flash.notice = "#{type} '#{here.name}' Updated!"
redirect.call
end
end
I have reloaded the console and I get the same error. I have also tried to do a few things in the console and this: Piece.first.keyconcepts, does not work (i get the same NameError: uninitialized constant Piece::Keyconcept) however this: KeyConcept.first DOES work, i even though i get nil because i havent created any instances yet.
I notice that, in the error message it says Keyconcept and not camelcase KeyConcept. I think this is where the problem lies but I do not have enough experience to understand it.
I would appreciate help in solving this!
The problem here is you are not following proper conventions
Change your model name to Keyconcept and file_name to keyconcept.rb
keyconcept.rb
class Keyconcept < ActiveRecord::Base
belongs_to :conceptable, polymorphic: true
end
OR
You will need to change keyconcepts to key_concepts in the following places:
routes
resources :keyconcepts
controller name
class KeyConceptsController < ApplicationController
...
end
controller file name
key_concepts_controller.rb
strong params
def key_concept_params
params.require(:key_concept).permit(:your, :params)
end
associations
has_many :key_concepts
Related
I have 2 models that are linked through a joint table:
class Dailyreport < ApplicationRecord
max_paginates_per 9
belongs_to :owner
has_many :dailyreport_issues
has_many :issues, through: :dailyreport_issues
accepts_nested_attributes_for :issues, allow_destroy: true
end
class Issue < ApplicationRecord
belongs_to :project
belongs_to :owner
has_many :dailyreport_issues
has_many :dailyreports, through: :dailyreport_issues
max_paginates_per 10
before_create { |issue| issue.jiraid = issue.jiraid.upcase }
validates :jiraid, uniqueness: true
validates :jiraid, :project, :owner, :time_forecast, :time_real, presence: true
validates :jiraid, format: { with: /\b[a-zA-Z]{2,6}-[1-9]\d{0,3}\b/, message: 'must follow this format ABCXYZ-9999' }
validates :time_real, numericality: { only_float: true }
validates :time_forecast, numericality: { only_float: true }
end
class DailyreportIssue < ApplicationRecord
belongs_to :dailyreport
belongs_to :issue
end
I use nested forms 'cocoon gem' to generate issues inside the create form of the dailyreport.
I successfully implemented that with these 2 controllers:
class DailyreportsController < ApplicationController
helper DailyreportsHelper
before_action :define_dailyreport, only: [:edit, :show, :update, :destroy]
def index
#dailyreports = Dailyreport.all.order(created_at: :desc).page params[:page]
end
def new
#dailyreport = Dailyreport.new
#dailyreport.issues.build
#issues = Issue.all.order(created_at: :desc)
end
def edit
end
def show
end
def owner_dailyreport
#owner_dailyreport = current_user.owner.dailyreports
end
def create
#dailyreport = Dailyreport.new(dailyreport_params)
#dailyreport.issues.each do |cr_issue|
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{cr_issue.jiraid}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{cr_issue.jiraid} exists and is available on JIRA"
no_api_reponse
else
issue_details_from_jira(cr_issue)
issue_time_real_from_jira(cr_issue)
end
if #dailyreport.save!
redirect_to #dailyreport, notice: 'Dailyreport was successfully created.'
else
render :new
end
end
end
def update
if #dailyreport.update(dailyreport_params)
redirect_to #dailyreport, notice: 'Dailyreport was successfully updated.'
else
render :edit
end
end
def destroy
if current_user.admin? || current_user.email == #dailyreport.owner.email
#dailyreport.destroy
else
admin_only_access
end
previous_page
end
private
def dailyreport_params
params.require(:dailyreport).permit(
:comment,
:owner_id,
issues_attributes: [
:jiraid,
:project_id,
:owner_id,
:time_forecast,
:time_real,
:departement,
:retour_test,
:status,
:_destroy
]
)
end
def define_dailyreport
#dailyreport = Dailyreport.find(params[:id])
end
end
class IssuesController < ApplicationController
require 'net/http'
require 'uri'
before_action :define_issue, only: [:show, :edit, :update, :destroy]
before_action :admin_only_access, only: [:destroy, :edit, :update]
def index
#issues = Issue.all.order(created_at: :desc).page params[:page]
end
def search
if params[:search].blank?
redirect_to issues_path and return
else
#parameter = params[:search].downcase
#results = Issue.all.where('lower(jiraid) LIKE :search', search: "%#{#parameter}%").page params[:page]
end
end
def new
#issue = Issue.new
end
def show
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{#issue.jiraid}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{#issue.jiraid} exists and is available on JIRA"
no_api_reponse
else
issue_details_from_jira(#issue)
yes_api_response
end
end
def create
#issue = Issue.new(issue_params)
# Check if issue exists on JIRA
unless call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{#issue.jiraid}")
flash.alert = "Please check if #{#issue.jiraid} exists and is available on JIRA"
end
# Get issue details from JIRA
issue_details_from_jira(#issue)
issue_time_real_from_jira(#issue)
# Save the issue
if #issue.save
flash.notice = "Issue #{#issue.jiraid} created"
redirect_to issues_path and return
else
flash.alert = "There was a problem saving #{#issue.jiraid}, check if all the fields are filled on the JIRA issue"
end
end
def edit
end
def update
if #issue.update(issue_params)
redirect_to issues_path
else
render :edit, status: :unprocessable_entity
end
end
def destroy
if current_user.admin?
#issue.destroy
else
admin_only_access
end
previous_page
end
private
def issue_params
params.require(:issue).permit(
:jiraid,
:project_id,
:owner_id,
:time_forecast,
:time_real,
:departement,
:retour_test,
:status
)
end
def define_issue
#issue = Issue.find(params[:id])
#issue_owner = Owner.find_by(params[:current_user])
end
end
My routesRails.application.routes.draw do
get '/search', to: 'issues#search'
get '/home/jira', to: 'home#jira'
get '/dailyreports/owner_dailyreport/:id', to: 'dailyreports#owner_dailyreport', :as => 'my_crs'
resources :projects
resources :issues
resources :departements
resources :owners
resources :dailyreports
# Devise routes
devise_scope :user do
get 'users', to: 'devise/sessions#new'
end
devise_for :users
authenticated :user do
root to: 'home#index', as: :authenticated_root
end
root to: redirect('/users/sign_in')
end
I am trying to implement an update or create process:
Check if the JIRAID exists in my DB
If it doesn't just get the data and save the dailyreport.
If it does, I call the API and get its updated details then update it and save the dailyreport.
And here I found some issues with the code I tried.
First when I update the issue then try to save the dailyreport, it throws the validation error (Jiraid exists) because the dailyreport.save is trying to update the issue again.
I also tried this:
def create
#dailyreport = Dailyreport.new(dailyreport_params)
issues_attributes = params[:dailyreport][:issues_attributes]
p("///////////////////////////////////ISSUES_ATTRIBUTES#{issues_attributes}")
issues_attributes.each do |_, issue_attributes|
p("~~~~~~~~~~~~~~~~~~~~~~ISSUE_ATTRIBUTE#{issue_attributes}")
# Call the JIRA API and check for errors
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{issue_attributes["jiraid"]}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{issue_attributes["jiraid"]} exists and is available on JIRA"
return
end
# Update the issue attributes with details from the JIRA API
issue_details_from_jira(issue_attributes)
issue_time_real_from_jira(issue_attributes)
p("~~~~~~~~~~~~~~~~~~~~~~JIRA ID IN THE DB: #{issue.jiraid}")
# Check if the issue already exists in the database
issue = Issue.find_by(jiraid: issue_attributes["jiraid"])
if issue
issue_details_from_jira(issue)
issue_time_real_from_jira(issue)
# Update the existing issue
issue.update(
time_forecast: issue.time_forecast,
time_real: issue.time_real,
status: issue.status
)
else
# Build and save a new issue if it doesn't exist
#dailyreport.issues.build(issue_attributes)
end
end
I know I have an issue here:
issue_details_from_jira(issue_attributes)
issue_time_real_from_jira(issue_attributes)
I am going to have to create an object to pass to my methods. But i don't know how.
I couldn't update the issue from the dailyreport controller too, so I tried passing the update method (+ the id) inside the strong params of the dailyreport. That resulted in a ForbiddenAttributes error.
I actually need a lead of how to approach this, not a specific solution. I think that my approach is wrong.
thank you in advance
Hello I have an exercise app where a user should be able to Like some products.
I could find a way to display the product he liked, but I really can't figure how to create and make work the like button.
I am not using any gem, I wan't to understand how to do it from Scratch.
Here are my models:
class User < ApplicationRecord
has_many :likes
has_many :liked_products, through: :likes, source: :product
end
class Product < ApplicationRecord
has_many :likes
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :product
end
In my view product show where I want the like button:
<h1><%= #product.name %></h1>
<%= link_to "Like", product_likes_path(#product), method: :put, remote: true %>
my routes:
Rails.application.routes.draw do
root to: 'visitors#index'
devise_for :users
resources :users
resources :products do
resource :likes
end
end
That's my products controller, I think things must come in here but I don't know HOW!
class ProductsController < ApplicationController
before_action :find_product, only: :show
def index
#products = Product.all
end
def show
##product.like => gives an error 404
end
private
def find_product
#product = Product.find(params[:id])
end
end
I had created a likes controller but it seems it is not useful.... So... I gave up there...
class LikesController < ApplicationController
def new
#like = Like.new(like_params)
end
def create
#like = Like.new(like_params)
end
private
def like_params
params.require(:likes).permit(:user_id, :product_id)
end
end
I would really enjoy some light on this please :)
Finally found out how to set the controller
class LikesController < ApplicationController
def create
#user = current_user.id
#product = params[:product_id]
likes = {user_id: #user, product_id: #product}
#like = Like.new(likes)
#like.save!
if #like.save
redirect_to user_path(#user)
else
redirect_to product_path
end
end
end
the buttton
<%= link_to "Like", product_likes_path(#product), method: :post %>
routes
Rails.application.routes.draw do
root to: 'products#index'
devise_for :users
resources :users
resources :users do
resources :products do
resources :likes
end
end
end
You could try something along these lines:
Routes:
Rails.application.routes.draw do
root to: 'visitors#index'
devise_for :users
resources :users do
resources :products do
resources :likes
end
end
resources :products do
resource :likes
end
end
Which will give you something like:
... other routes ...
user_product_likes GET /users/:user_id/products/:product_id/likes(.:format) likes#index
POST /users/:user_id/products/:product_id/likes(.:format) likes#create
new_user_product_like GET /users/:user_id/products/:product_id/likes/new(.:format) likes#new
edit_user_product_like GET /users/:user_id/products/:product_id/likes/:id/edit(.:format) likes#edit
user_product_like GET /users/:user_id/products/:product_id/likes/:id(.:format) likes#show
PATCH /users/:user_id/products/:product_id/likes/:id(.:format) likes#update
PUT /users/:user_id/products/:product_id/likes/:id(.:format) likes#update
DELETE /users/:user_id/products/:product_id/likes/:id(.:format) likes#destroy
... other routes ...
Then:
<%= link_to "Like", user_product_likes_path(#user, #product), method: :post, remote: true %>
And in your LikesController:
class LikesController < ApplicationController
def new
#like = Like.new(like_params)
end
def create
#like = Like.new(like_params)
if #like.save
... do something happy
else
... do something sad
end
end
private
def like_params
params.require(:likes).permit(:user_id, :product_id)
end
end
Untested, so buyer beware. You might need to fiddle with your like_params and other stuff.
in my search.html.haml, I want the user have the ability to click on the teacher name and it will lead them to the teacher show page. However, it also requires me to have the school_id because a teacher belongs to a school, and a school has many teachers. Now, I am wondering if there is a way for me to a the school_id to the path without breaking the application. The error that rails is throwing at me now is:
No route matches {:action=>"show", :controller=>"teachers", :id=>"1",
:school_id=>nil} missing required keys: [:school_id]
Here are my files:
search.html.haml:
.text-center
/ No search results announcement/notification
- if #teachers.blank?
%h2 Xin lỗi, hệ thống chúng tôi không có thông tin về giảng viên mà bạn muốn tìm.
- else
- #teachers.each do |teacher|
%h2= link_to teacher.fullName, school_teacher_path(#school, teacher)
#note
%em
Khoa #{teacher.department}, trường #{teacher.school.name}
teachers_controller.rb:
class TeachersController < ApplicationController
before_action :find_school, except: [:welcome, :search]
before_action :find_teacher, only: [:show, :edit, :update, :destroy]
def welcome
end
def show
end
def search
if params[:search].present?
#teachers = Teacher.search(params[:search], fields: [:fullName])
else
#teachers = nil
end
end
def new
#teacher = #school.teachers.build
end
def create
#teacher = #school.teachers.create(teacher_params)
#teacher.save
redirect_to(#school)
end
def edit
end
def update
#teacher.update(teacher_params)
redirect_to(#school)
end
private
def find_school
#school = School.find(params[:school_id])
end
def find_teacher
#teacher = Teacher.find(params[:id])
end
def teacher_params
params.require(:teacher).permit(:firstName, :lastName, :middleName, :department, :school_id, :fullName)
end
end
teacher.rb:
class Teacher < ActiveRecord::Base
belongs_to :school
has_many :ratings
searchkick
def name
"#{lastName} #{middleName} #{firstName}"
end
def to_s
name
end
end
school.rb:
class School < ActiveRecord::Base
has_many :teachers, dependent: :destroy
# searchkick
end
routes.rb:
Rails.application.routes.draw do
devise_for :users
resources :schools do
# collection do
# get 'search'
# end
resources :teachers do
collection do
get 'search'
end
end
end
resources :teachers do
collection do
get 'search'
end
resources :ratings
end
root 'teachers#welcome'
end
school_teacher_path is called with a #school that is nil since find_school is not used for search. You probably want to replace #school with teacher.school: school_teacher_path(teacher.school, teacher).
I'm trying to build a software for small banks, which involves deals, pools (of banks) and credit facilities. For information, a facility belongs to a pool of banks, which belongs to a deal.
Below is my issue when i try to create a "facility" :
Couldn't find Pool with 'id'=
I have 3 models : Deal, Pool, Facility
class Deal < ActiveRecord::Base
has_many :banks
has_many :pools, :dependent => :destroy
end
class Pool < ActiveRecord::Base
belongs_to :deal
end
class Facility < ActiveRecord::Base
belongs_to :pool
end
Below is my Facilitys controller :
class FacilitysController < ApplicationController
before_action :authenticate_user!
before_action :set_pool
before_action :set_facility, only: [:show, :edit, :update, :destroy]
def new
#pool = Pool.find(params[:id])
#facility = Facility.new
end
def edit
#facility = Facility.find(params[:id])
end
def create
#facility = Facility.new(facilitys_params)
if #facility.save
redirect_to root_path, notice: "Facility successfully created!"
else
render "New"
end
end
def show
#facility = Facility.find(params[:id])
#facility.pool_id = #pool.id
end
def update
#facility.update(facilitys_params)
if #facility.update(facilitys_params)
redirect_to deal_facility_url(#pool, #facility), notice: "Facility successfully updated!"
else
render :edit
end
end
def destroy
#facility.destroy
end
private
def set_pool
#pool = Pool.find(params[:id])
end
def set_facility
#facility = Facility.find(params[:id])
end
def facilitys_params
params.require(:facility).permit(:name)
end
end
My routes are
resources :deals do
resources :pools, except: [:index] do
resources :facilitys, except: [:index]
end
end
Looks like you're trying to use nested resources, but you've not provided enough information.
If your routes are not configured like this:
resources :pools do
resources :facilities
end
...then please add the relevant routes to your question.
If your routes are configured like that then good, but now your set_pool is incorrect. Take a look at the output of rake routes and you should see something like this for your Facility routes:
pool_facility_index GET /pools/:pool_id/facility(.:format) facility#index
POST /pools/:pool_id/facility(.:format) facility#create
new_pool_facility GET /pools/:pool_id/facility/new(.:format) facility#new
edit_pool_facility GET /pools/:pool_id/facility/:id/edit(.:format) facility#edit
pool_facility GET /pools/:pool_id/facility/:id(.:format) facility#show
PATCH /pools/:pool_id/facility/:id(.:format) facility#update
PUT /pools/:pool_id/facility/:id(.:format) facility#update
DELETE /pools/:pool_id/facility/:id(.:format) facility#destroy
Notice how there are two params mentioned in each route, :id and :pool_id. Now take a look at your set_pool method and see which param you're using to find the Pool. You need to change that to use params[:pool_id] too.
Given the routes:
Example::Application.routes.draw do
concern :commentable do
resources :comments
end
resources :articles, concerns: :commentable
resources :forums do
resources :forum_topics, concerns: :commentable
end
end
And the model:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
When I edit or add a comment, I need to go back to the "commentable" object. I have the following issues, though:
1) The redirect_to in the comments_controller.rb would be different depending on the parent object
2) The references on the views would differ as well
= simple_form_for comment do |form|
Is there a practical way to share views and controllers for this comment resource?
In Rails 4 you can pass options to concerns. So if you do this:
# routes.rb
concern :commentable do |options|
resources :comments, options
end
resources :articles do
concerns :commentable, commentable_type: 'Article'
end
Then when you rake routes, you will see you get a route like
POST /articles/:id/comments, {commentable_type: 'Article'}
That will override anything the request tries to set to keep it secure. Then in your CommentsController:
# comments_controller.rb
class CommentsController < ApplicationController
before_filter :set_commentable, only: [:index, :create]
def create
#comment = Comment.create!(commentable: #commentable)
respond_with #comment
end
private
def set_commentable
commentable_id = params["#{params[:commentable_type].underscore}_id"]
#commentable = params[:commentable_type].constantize.find(commentable_id)
end
end
One way to test such a controller with rspec is:
require 'rails_helper'
describe CommentsController do
let(:article) { create(:article) }
[:article].each do |commentable|
it "creates comments for #{commentable.to_s.pluralize} " do
obj = send(commentable)
options = {}
options["#{commentable.to_s}_id"] = obj.id
options["commentable_type".to_sym] = commentable.to_s.camelize
options[:comment] = attributes_for(:comment)
post :create, options
expect(obj.comments).to eq [Comment.all.last]
end
end
end
You can find the parent in a before filter like this:
comments_controller.rb
before_filter: find_parent
def find_parent
params.each do |name, value|
if name =~ /(.+)_id$/
#parent = $1.classify.constantize.find(value)
end
end
end
Now you can redirect or do whatever you please depending on the parent type.
For example in a view:
= simple_form_for [#parent, comment] do |form|
Or in a controller
comments_controller.rb
redirect_to #parent # redirect to the show page of the commentable.