Updating a referenced entity through the create form of another - ruby-on-rails

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

Related

NoMethodError - Rails - Can't track down the problem

I am currently working on a blog app. The blog has users, stories, and favourites. 1 user can have many stories and many favourites, and one story can have many favourites (1 from each user) too.
When I try to save a new favourite on the front end (the create route), I receive the following error:
undefined method `favourites' for #<Class:0x00007f850e24e290>
Extracted source (around line #11):
9 def create
10 #story = Story.find(params[:story_id])
11 #favourite = Story.favourites.new
12 #favourite.user = #current_user
13 #favourite.save
14 redirect_to story_path(#story)
I feel like this should be defined, and is almost the same way that I am using create with other controllers. Based on what I've found so far, I suspect that it is because the favourites in Story.favourites.new is nested within the Story, but I haven't found a way to sort it out yet. I'm quite new to Rails, so apologies if the answer is obvious. Any guidance is appreciated! Thanks for your help!
Below are some of the pertinent files.
favourites_controller.rb
class FavouritesController < ApplicationController
before_action :logged_in, except: [:index, :show]
def show
end
def create
#story = Story.find(params[:story_id])
#favourite = Story.favourites.new
#favourite.user = #current_user
#favourite.save
redirect_to story_path(#story)
end
end
stories_controller.rb
class StoriesController < ApplicationController
before_action :logged_in, except: [:index, :show]
# Stories list page (also homepage)
def index
# Topic filtering
#topic = params[:topic]
#stories = if #topic.present?
Story.where(topic: #topic)
else
Story.all
end
end
def new
#story = Story.new
end
def create
#story = Story.new(form_params)
#story.user = #current_user
if #story.save
redirect_to root_path
else
render 'new'
end
end
def show
#story = Story.find(params[:id])
end
def destroy
#story = Story.find(params[:id])
#story.destroy if #story.user == #current_user
redirect_to root_path
end
def edit
#story = Story.find(params[:id])
redirect_to root_path if #story.user != #current_user
end
def update
#story = Story.find(params[:id])
if #story.user == #current_user
if #story.update(form_params)
redirect_to story_path(#story)
else
render 'edit'
end
else
redirect_to root_path
end
end
def form_params
params.require(:story).permit(:title, :topic, :body)
end
end
models/favourite.rb
class Favourite < ApplicationRecord
belongs_to :story
belongs_to :user
validates :story, uniqueness: { scope: :user }
end
models/story.rb
class Story < ApplicationRecord
has_many :comments
has_many :favourites
belongs_to :user
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
def to_param
id.to_s + '-' + title.parameterize
end
end
config/routes.rb
Rails.application.routes.draw do
resources :stories do
resources :comments
resource :favourite
end
resources :users
resource :session
root 'stories#index'
end
the issue is in this line:
11 #favourite = Story.favourites.new
it should be:
11 #favourite = #story.favourites.new
because the class Story itself doesn't have the favourites method but its instances do.

form_for nested route url generation error

I have two models tea and a review. I have nested a route to create reviews for a specific tea but when I submit the form I am getting a UrlGeneration Eror based on missing a required key. Below is the controller action and route and picture of the error. I am trying to have a user create a new review from /teas/1/reviews/new it is a nested form the issue being it does not save the create.
Review Model
class Review < ApplicationRecord
belongs_to :user
belongs_to :tea
validates :title, presence: true
validates :rating, numericality: {only_integer: true, greater_than_or_equal_to: 0, less_than: 11}
validates :tea, uniqueness: {scope: :user, message: "has already been reviewed by you" }
scope :order_by_rating, ->{left_joins(:reviews).group(:id).order('avg(rating) desc')}
end
class ReviewsController < ApplicationController
before_action :set_review, only:[:create, :show, :edit, :update, :destroy]
def new
if #tea = Tea.find_by_id(params[:tea_id])
#review = #tea.reviews.build
else
#review = Review.new
end
end
def create
#review = current_user.reviews.build(review_params)
if #review.valid?
#review.save
redirect_to new_review_path(#review)
else
render :new
end
end
def show
#review = Review.find_by_id(params[:id])
end
def index
if #tea = Tea.find_by_id(params[:tea_id])
#reviews = #tea.reviews
else
#reviews = Review.all
end
end
def edit
end
def update
#review.update(review_params)
redirect_to tea_reviews_path(current_user.id)
end
def destroy
#review.destroy
flash[:delete_review] = "Review Deleted!"
redirect_to reviews_path(#review)
end
private
def review_params
params.require(:review).permit(:tea_id, :content, :rating,:title)
end
def set_review
#review = Review.find_by_id(params[:id])
redirect_to reviews_path if !#review
end
end
Route
resources :reviews
resources :teas do
resources :reviews, only: [:new, :index]
end
In line 62 of Reviews_controller
redirect_to review_path if !#review
In this line you are trying to redirect to a review show page if review doesn't exist,
hence when review doesn't exist it redirects to show path without an id, that's why you are getting an error.
Think carefully where do you want to redirect, if a review does not exist.
Per your updated question,
Remove :create from the before_action in the first line of the controller,
also, in your create action change the redirect to
redirect_to reviews_path
after the #review.save.

How do I create a page without a model?

I'm working on an app which has many 'Activities'. Each 'Activity' has many 'Ranks'. I'd like each 'Activity' to have a page called grading, where the user can see a list of all of that activity's ranks and conveniently update them. I imagine the URL would be something like http://localhost:3000/activities/21/grading
I'm already using http://localhost:3000/activities/21/edit for its intended purpose.
I don't need a model for gradings, as I don't need to save any grading records.
I know exactly what to put in the view, I'm just unsure what to add to the controller and routes files. Other people have worked on this app but I'm unable to contact them.
Routes
resources :activities do
collection do
get 'scheduled_classes'
end
end
resources :ranks
end
activities_controller
class ActivitiesController < ApplicationController
def new
#activity = Activity.new
#activity.timeslots.build
#activity.ranks.build
end
def create
#activity = current_club.activities.new(activity_params)
if #activity.save
flash[:success] = "New class created!"
redirect_to activity_path(#activity)
else
render 'new'
end
end
def edit
#activity = current_club.activities.find_by(id: params[:id])
#active_ranks = #activity.ranks.where(active: true)
if !#activity.active?
redirect_to activities_path
else
#activity.timeslots.build
end
end
def update
#activity = current_club.activities.find_by(id: params[:id])
if #activity.update_attributes(activity_params)
flash[:success] = "Class updated!"
redirect_to edit_activity_path(#activity)
else
render 'edit'
end
end
def show
#activity = current_club.activities.find_by(id: params[:id])
#active_ranks = #activity.ranks.where(active: true)
if #activity.nil?
redirect_to root_url
elsif !#activity.active?
redirect_to activities_path
end
end
def index
#activities = current_club.activities.all
end
def destroy
#activity = current_club.activities.find_by(id: params[:id])
if #activity.nil?
redirect_to root_url
else
#activity.destroy
flash[:success] = "Class deleted"
redirect_to activities_path
end
end
end
private
def activity_params
params.require(:activity).permit(:name, :active,
:timeslots_attributes => [:id,
:time_start,
:time_end,
:day,
:active,
:schedule],
:ranks_attributes => [:id,
:name,
:position,
:active])
end
end
activity
class Activity < ApplicationRecord
belongs_to :club
has_many :timeslots, dependent: :destroy
accepts_nested_attributes_for :timeslots,:allow_destroy => true
has_many :ranks, dependent: :destroy
has_many :attendances, dependent: :destroy
accepts_nested_attributes_for :ranks
validates :club_id, presence: true
validates :name, presence: true, length: { maximum: 50 }
end
Your routes don't need to have an associated model or resource.
resources :activities do
collection do
get 'scheduled_classes'
end
member do
get :grading
end
end
will match to activities#grading
See https://guides.rubyonrails.org/routing.html#adding-member-routes for more info.
As you want to add a route on a particular activity, you should add member route on the activity like below,
resources :activities do
collection do
get 'scheduled_classes'
end
get :grading, on: :member
end
Apart from this, you have to add method in ActivitiesController for this route like below,
def grading
#activity = Activity.find_by(id: params[:id])
# do more here
end
In view files, you can create grading.html.erb under activities resources and put your view code there.

Rails - Model not saving to db

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 'Courses'. It now has User>Listing>Course. This Courses model is working in rails console but not saving to my database when I'm running the server. Any suggestions would be appreciated...
Error Message
ActiveRecord::RecordInvalid in CoursesController#create
Validation failed: Listing must exist
Models
class User < ApplicationRecord
has_many :listings
has_many :courses, :through => :listings
end
class Listing < ApplicationRecord
belongs_to :user
has_many :courses
validates :listing_type, presence: true
validates :course_type, presence: true
validates :accommodate, presence: true
end
class Course < ApplicationRecord
belongs_to :listing
validates :curriculum_type, presence: true
validates :course_places, presence: true
end
Course Controller
class CoursesController < ApplicationController
before_action :set_course, except: [:index, :new, :create]
before_action :authenticate_user!, except: [:show]
def index
#courses = current_user.courses
end
def new
#course = current_user.courses.build
end
def create
#course = current_user.courses.build(course_params)
if #course.save!
redirect_to course_listing_path(#course), notice: "Saved..."
else
render :new, notice: "Something went wrong..."
end
end
def show
end
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 course_params
params.require(:course).permit(:name, :curriculum_type, :summary, :address, :course_places, :start_date, :finish_date, :price)
end
end
Routes
Rails.application.routes.draw do
root 'pages#home'
devise_for :users,
path: '',
path_names: {sign_in: 'login', sign_out: 'logout', edit: 'profile', sign_up: 'registration'},
controllers: { omniauth_callbacks: 'omniauth_callbacks', registrations: 'registrations' }
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :users, only: [:show]
resources :listings, except: [:edit] do
member do
get 'listing'
get 'pricing'
get 'description'
get 'photo_upload'
get 'amenities'
get 'location'
end
end
resources :courses, except: [:edit] do
member do
get 'listing'
get 'pricing'
get 'description'
get 'photo_upload'
get 'amenities'
get 'location'
end
end
end
below you can read my comments with the # sign
You are trying to save an object Course that has belongs_to listings so it is expected that it has as course.listing_id the id of an existing Listing
def create
#course = current_user.courses.build(course_params)
# You need to set #course.listing_id to an existing Listing
# You need to find that listing and save it in a variable.
# I am not getting into your logic, because your code is confused and need many adjustments
listing = Listing.find() # include hear your logic to find an existing listing from the db
#course.listing_id = listing.id
if #course.save!
redirect_to course_listing_path(#course), notice: "Saved..."
else
render :new, notice: "Something went wrong..."
end
end

Has many through or Has and belongs to many missing ID

Just trying to create a simple workout log here I'm getting this error when I try and create weights
"Workout ID must exist"
Models
class Workout < ApplicationRecord
belongs_to :user
has_many :exercises
has_many :weights, through: :exercises
accepts_nested_attributes_for :exercises, :allow_destroy => true
accepts_nested_attributes_for :weights, :allow_destroy => true
validates :bodypart, presence: true, length: { maximum: 255 }
end
class Weight < ApplicationRecord
belongs_to :exercise
belongs_to :workout
validates :amount, presence: true, length: { maximum: 255 }
validates :reps, presence: true, length: { maximum: 255 }
end
class Exercise < ApplicationRecord
belongs_to :workout
has_many :weights
accepts_nested_attributes_for :weights
validates :name, presence: true
validates_associated :weights
end
After reading a few things I thought that a has many through would be the option for these associations but now I'm not so sure. When I try and create a weight for exercise I get the exercise ID but can't seem to get the workout ID despite a number of attempts. I'm not sure if its simply a controller issue or if Im missing something bigger here.
My Current Weights Controller
class WeightsController < ApplicationController
before_action :authenticate_user!
def new
#weight = Weight.new
end
def create
#exercise = Exercise.find(params[:exercise_id])
#weight = #exercise.weights.build(weight_params)
if #weight.save
flash[:notice] = "Set saved"
redirect_to exercise_path(#exercise)
else
redirect_to exercise_path(#exercise)
flash[:notice] = "#{#weight.errors.full_messages.to_sentence}"
end
end
def edit
end
def update
end
def destroy
weight = Weight.find(params[:id])
#weight.destroy
redirect_to root_path
end
private
def weight_params
params.require(:weight).permit(:amount, :reps)
end
end
Routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: "callbacks" }
root 'articles#index'
get '/demo', to: 'static_pages#demo'
get '/about', to: 'static_pages#about'
get '/test', to: 'static_pages#index'
resources :articles
resources :workouts do
resources :exercises
end
resources :exercises do
resources :weights
end
resources :workouts do
member do
put :is_finished
put :unfinish
end
end
resources :exercises do
member do
put :is_finished
put :unfinish
end
end
resources :books, :only => :index
end
Exercises controller
class ExercisesController < ApplicationController
before_action :authenticate_user!
# before_action :set_exercise, except: [:index, :new, :create]
def new
#exercise = Exercise.new
#exercise.weights.new
end
def index
#workout = Workout.find(params[:workout_id])
#exercise = #workout.exercises
end
def create
#workout = Workout.find(params[:workout_id])
#exercise = #workout.exercises.build(exercise_params)
if #exercise.save
redirect_to workout_exercise_path(#workout, #exercise)
flash[:notice] = "Exercise created, enter weight and reps for your first set"
else
redirect_to #workout
flash[:notice] = "#{#exercise.errors.full_messages.to_sentence}"
end
end
def edit
end
def update
#exercise = Exercise.find(params[:id])
if #exercise.update_attributes(exercise_params)
redirect_to exercise_path(#exercise)
else
flash[:notice] = "Something went wrong"
end
end
def show
#exercise = Exercise.find(params[:id])
#weights = #exercise.weights.order('created_at DESC').paginate(page: params[:page], per_page: 5)
end
I do have an ID column for workout_id set on my Weights model but it always populates NULL whenever I can manage to create an exercise. Also, in the rails console I can find a workout and call #workout.weights and it returns the weights associated with the workout just fine. I just can't get the workout ID to populate in Weight model. Wondering if has and belongs to many would be better OR if my has many through is just set up wrong. I did try "inverse_of" without any luck. Any help would be appreciated.
The answer is quite simple. To add reverse functionality, you have to explicitly state it, through the exercise.
in weight.rb remove belongs_to :workout and add the following
def workout
self.exercise.workout
end
Perhaps you will need to add logic to avoid nilClass errors.
Now you do have #weight.workout through #weight.exercise

Resources