ActiveModel::MissingAttributeError (can't write unknown attribute `flights_count`): - ruby-on-rails

I am doing some refactoring and I have seen this project for a while and it worked from what I last recall. But the issue is, I am trying to create a flight and I keep getting "ActiveModel::MissingAttributeError (can't write unknown attribute flights_count):" when trying create a new flight.
As far my models in place:
My Flight, Pilot models
class Flight < ActiveRecord::Base
has_many :passengers
belongs_to :destination
belongs_to :pilot, counter_cache: true
accepts_nested_attributes_for :passengers
belongs_to :user, class_name: "Flight" ,optional: true
validates_presence_of :flight_number
validates :flight_number, uniqueness: true
scope :order_by_flight_international, -> { order(flight_number: :asc).where("LENGTH(flight_number) > 3") }
scope :order_by_flight_domestic, -> { order(flight_number: :asc).where("LENGTH(flight_number) <= 2 ") }
def dest_name=(name)
self.destination = Destination.find_or_create_by(name: name)
end
def dest_name
self.destination ? self.destination.name : nil
end
def pilot_name=(name)
self.pilot = Pilot.find_or_create_by(name: name)
end
def pilot_name
self.pilot ? self.pilot.name : nil
end
end
class Pilot < ActiveRecord::Base
belongs_to :user, optional: true
has_many :flights
has_many :destinations, through: :flights
validates_presence_of :name, :rank
validates :name, uniqueness: true
scope :top_pilot, -> { order(flight_count: :desc).limit(1)}
end
Edit
Flight Controller
class FlightsController < ApplicationController
before_action :verified_user
layout 'flightlayout'
def index
#flights = Flight.order_by_flight_international
#dom_flights = Flight.order_by_flight_domestic
end
def new
#flight = Flight.new
10.times {#flight.passengers.build}
end
def create
#flight = Flight.new(flight_params)
# byebug
if #flight.save!
redirect_to flight_path(current_user,#flight)
else
flash.now[:danger] = 'Flight Number, Destination, and Pilot have to be selected at least'
render :new
end
end
private
def flight_params
params.require(:flight).permit(:flight_number,:date_of_flight, :flight_time, :flight_id, :destination_id, :pilot_id, :pilot_id =>[], :destination_id =>[], passengers_attributes:[:id, :name])
end
end
Edit
Flights, Pilot Schemas
create_table "flights", force: :cascade do |t|
t.integer "pilot_id"
t.integer "destination_id"
t.string "flight_number"
t.string "date_of_flight"
t.string "flight_time"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "pilots", force: :cascade do |t|
t.string "name"
t.string "rank"
t.integer "user_id"
t.integer "flight_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "flight_count", default: 0
end
As I said before when I last worked on this project everything was working fine, but I am faced with this issue. What am I doing wrong this time.

You have defined a counter_cache in your Flight model for pilots. When you just use counter_cache: true to define it, ActiveRecord will look for a column named flights_count in your pilots table but I see that you have named it as flight_count instead. You can either rename the column to flights_count or pass the custom column name to it by using counter_cache: :flight_count
Source https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache

Related

How can I create a resource in Rails/SQL that belongs_to many different resources but does not require a foreign id for all of them?

Here is my recurring payment model. I want it to optionally belong to each of the included resources, but to allow creation with only one parent resource. I tried setting the default for each foreign key to 0, but I still get an error when I try to create instances saying "bank account must exist", "investment must exist", etc. I am sure there must be a way to accomplish this, but I cannot see how? *Edit: I solved this by defaulting the foreign id's to 1 instead of 0, since this will just be seed data anyway, but I would still love to know if anyone has a better solution!
class RecurringPayment < ApplicationRecord
belongs_to :bank_account
belongs_to :credit_card
belongs_to :investment
belongs_to :loan
def bank_account_name
self.try(:bank_account).try(:name)
end
def bank_account_name=(name)
bank_account = BankAccount.find_by(name: name)
if bank_account
self.bank_account = bank_account
end
end
def credit_card_provider
self.try(:credit_card).try(:provider)
end
def credit_card_provider=(provider)
credit_card = CreditCard.find_by(provider: provider)
if credit_card
self.credit_card = credit_card
end
end
def investment_name
self.try(:investment).try(:name)
end
def investment_name=(name)
investment = Investment.find_by(name: name)
if investment
self.investment = investment
end
end
def loan_name
self.try(:loan).try(:name)
end
def loan_name=(name)
loan = Loan.find_by(name: name)
if loan
self.loan = loan
end
end
end
Here is the schema:
create_table "recurring_payments", force: :cascade do |t|
t.string "source"
t.boolean "status"
t.date "pay_date"
t.integer "pay_amount"
t.integer "duration"
t.integer "bank_account_id", default: 0
t.integer "credit_card_id", default: 0
t.integer "loan_id", default: 0
t.integer "investment_id", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "payment_frequency"
end
In Rails 5, belongs_to associations are required by default.
You can change this behavior in a config file, by adding:
Rails.application.config.active_record.belongs_to_required_by_default = false
Or in a specific class:
class RecurringPayment < ApplicationRecord
belongs_to :bank_account, optional: true
belongs_to :credit_card, optional: true
belongs_to :investment, optional: true
belongs_to :loan, optional: true
end
In your code you should always check before using the association model, as it may not exist.
Check section 4.20 in this documentation: http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html

How can I save current user to the database when a purchase is edited?

Question UPDATED: I am trying to save the id of the current user whenever a purchase is edited. Specifically when a purchase is marked as received.
I have a before_update callback to a method that saves the datetime when the purchase is marked as received, that works well, I just need to figure out how to pass the purchase editor's id to the database.
In user.rb
class User < ActiveRecord::Base
has_many :purchase_edits, :foreign_key => :purchase_editor_id
has_many :edited_purchases, :through => :purchase_edits
has_many :created_purchases, :foreign_key => :creator_id, :class_name => "Purchase"
end
In purchase.rb
class Purchase < ActiveRecord::Base
belongs_to :vendor
belongs_to :creator, :class_name => "User"
has_many :purchase_edits, :foreign_key => :edited_purchase_id
has_many :editors, :through => :purchase_edits, :source => :purchase_editor
before_update :update_marked_received_date
## Saves date when marked received. ##
def update_marked_received_date
return unless received == true
self.marked_received = Time.now
end
end
In purchase_edit.rb (Join Table)
class PurchaseEdit < ActiveRecord::Base
belongs_to :purchase_editor, :class_name => "User"
belongs_to :edited_purchase, :class_name => "Purchase"
end
In purchases_controller.rb, (create & update), I have this:
def create
#vendor_options = Vendor.order("name ASC").all.map{ |u| [ u.name, u.id ] }
#purchase = Purchase.new(purchase_params)
#purchase.creator = current_user
if #purchase.save
redirect_to #purchase
else
render 'new'
end
end
def update
#vendor = Vendor.all
#vendor_options = Vendor.order("name ASC").all.map{ |u| [ u.name, u.id ] }
#purchase = Purchase.find(params[:id])
if #purchase.update(purchase_params)
flash[:notice] = 'Update successful.'
redirect_to #purchase
else
render 'edit'
end
end
In Schema.rb
create_table "purchases", force: :cascade do |t|
t.integer "vendor_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "order_number"
t.string "tradegecko_url"
t.integer "creator_id"
t.boolean "received", default: false, null: false
t.date "estimated_ship_date"
t.boolean "closed", default: false, null: false
t.datetime "marked_received"
end
create_table "purchase_edits", id: false, force: :cascade do |t|
t.integer "purchase_editor_id", null: false
t.integer "edited_purchase_id", null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "admin", default: false
end
For anyone else who comes across this question, (and my future self), this is how I got it to work:
First, in application_controller.rb:
class ApplicationController < ActionController::Base
before_action :set_current_user
def set_current_user
User.current = current_user
end
end
Then in user.rb:
class User < ActiveRecord::Base
has_many :created_purchases, class_name: 'Purchase', foreign_key: 'creator_id'
has_many :received_purchases, class_name: 'Purchase', foreign_key: 'receiver_id'
def self.current
Thread.current[:user]
end
def self.current=(user)
Thread.current[:user] = user
end
def purchases
Purchase.where("creator_id = ? OR receiver_id = ?", self.id, self.id)
end
end
Finally, in purchase.rb:
class Purchase < ActiveRecord::Base
belongs_to :creator, class_name: "User", foreign_key: "creator_id"
belongs_to :receiver, class_name: "User", foreign_key: "receiver_id"
before_update :update_marked_received
def update_marked_received
return unless received == true
if self.marked_received.blank?
self.marked_received = Time.now
self.receiver_id = User.current.id
end
end
end
Now, when a purchase is marked as received, the timestamp and user are recorded.
Class Purchase
belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_id'
has_one :marked
end
Class Marked
belongs_to :purchase
belongs_to :user
end
This could work in your favour. Moreover, you could keep the relation has_one or has_many marked, according to your requirement.
I would recommend you to create a different model for this because it will be easier to maintain and the approach you are trying to achieve is violation of single responsibility principle. The model Marked will have a foreign key of two models because it has a relationship belongs_to for both the model. Please have a look of belongs to association.

User_id is nil on rails association

I have a user model and a shout model. I am trying to have a user be associated with a shout. I did not make this association upon creation of the shouts table so I had to run a new migration. Below is my table, the models of each, and the output when from my console I run a command to try and find the user_id of a shout. Can you see what I am doing wrong?
schema:
create_table "shouts", force: :cascade do |t|
t.string "title"
t.text "description"
t.integer "user_id"
end
add_index "shouts", ["user_id"], name: "index_shouts_on_user_id"
create_table "users", force: :cascade do |t|
t.string "email", null: false
t.string "password_digest", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
end
User Model:
class User < ActiveRecord::Base
validates :email, presence: true, uniqueness: true
validates :password_digest, presence: true
has_many :shouts
end
Shout Model:
class Shout < ActiveRecord::Base
belongs_to :user
end
Shout Controller:
class ShoutsController < ApplicationController
def new
#new_shout = Shout.new
end
def create
#new_shout = Shout.new(shouts_params)
if #new_shout.user_id == nil
render new_shout_path
elsif #new_shout.save
redirect_to dashboard_path
else
render new_shout_path
end
end
private
def shouts_params
params.require(:shout).permit(:title, :description, :user_id)
end
end
Some test code:
> Shout.find(4)
> #<Shout id: 4, title: "four", description: "four", user_id: nil>
Creating an instance of user from the console, working:
> User.first.shouts.create(title: 'four', description: 'four')
>[["title", "four"], ["description", "four"], ["user_id", 1]
Migration file:
class AddUserRefToShouts < ActiveRecord::Migration
def change
add_reference :shouts, :user, index: true, foreign_key: true
end
end
Here are a couple admittedly hacky options (but they'll work) if you don't want to follow the approach suggested in the comments. You could pass the user_id as a hidden field so it'll be included in the params or you can expressly set it in the create action.
If you want to pass as a hidden field, on your shout form add:
<%= f.hidden_field :user_id, value: current_user.id %>
Alternatively, if you want to handle in your create action:
def create
#shout = Shout.new(shout_params)
#shout.user_id = current_user.id
#shout.save
end

Inheritance with ActiveRecord at the same time

I have a class 'Report' that has columns 'description', 'pending', etc.
/app/models/report.rb
class Report < ActiveRecord::Base
end
class CreateReports < ActiveRecord::Migration
def change
create_table :reports do |t|
t.boolean :pending, :default => true
t.boolean :accepted, :default => false
t.text :description
t.timestamps null: false
end
end
end
But I also have two other classes: ReportPost (when a User report a Post), and ReportTopic (when a User report a Topic). I used this approach because I can user 'belongs_to :topic' for ReportTopic and 'belongs_to :post' for ReportPost. So, here comes the problem:
Since ReportPost and ReportTopic have the same columns of 'Report', I need to use the inheritance from 'Report'. But I also need to use ActiveRecord inheritance to capture new attributes from :report_topic migrates.
But, how?
Here are the other classes:
class ReportTopic < Report
belongs_to :topic
end
class ReportPost < Report
belongs_to :post
end
`And, the migrates:
class CreateReportPosts < ActiveRecord::Migration
def change
create_table :report_posts do |t|
t.belongs_to :post
t.timestamps null: false
end
end
end
class CreateReportTopics < ActiveRecord::Migration
def change
create_table :report_topics do |t|
t.belongs_to :topic
t.timestamps null: false
end
end
end
You could use Single Table Inheritance (STI) in this case. Just add a column named 'type' to your report table.
def change
create_table :reports do |t|
t.boolean :pending, :default => true
t.boolean :accepted, :default => false
t.text :description
t.string :type
t.timestamps null: false
end
end
Rails will understand this as STI. Any subclass that you may create will have its type equal to the name of the class (e.g. type = 'ReportTopic')

undefined method `addresses' for #<User:0x007fc955029380>

I am new to Ruby and Rails, trying to fix an error I constantly get. Not sure how to fix it. Please help..
Route.rb
namespace :my do
namespace :account do
resource :details, :only => [:show, :update]
resources :addresses
end
end
AddressesController
class My::Account::AddressesController < MyController
def index
#addresses = current_user.addresses
end
def new
#address = current_user.addresses.new
end
....
end
Error - undefined method `addresses'
NoMethodError in My::Account::AddressesController#index
undefined method `addresses' for #<User:0x007fc955029380>
Schema.rb for customer addresses and users
create_table "customer_addresses", force: true do |t|
t.integer "customer_id"
t.string "name"
t.string "line_1"
t.string "line_2"
t.string "line_3"
t.string "line_4"
t.string "line_5"
t.string "postcode"
t.string "phone"
t.datetime "deleted_at"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "customer_addresses", ["customer_id"], name: "index_customer_addresses_on_customer_id"
create_table "users", force: true do |t|
t.string "first_name"
t.string "last_name"
t.string "email"
t.string "password_digest"
t.datetime "created_at"
t.datetime "updated_at"
t.string "password_reset_token"
t.datetime "password_reset_token_at"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
User.rb
class User < ActiveRecord::Base
validates :email, :presence => true, :uniqueness => true
has_secure_password
...
end
Customer.rb
class Customer < User
has_many :addresses
def self.register(attributes)
customer = create!(attributes)
return customer
end
def full_name
"#{first_name} #{last_name}"
end
end
Address.rb
class Customer::Address < ActiveRecord::Base
belongs_to :customer
self.table_name = 'customer_addresses'
default_scope { where(:deleted_at => nil) }
validates :line_1, :postcode, :phone, :presence => true
end
You need to define the relationship on both models.
class User < ActiveRedord::Base
has_many :addresses, class_name: 'CustomerAddress', foreign_key: 'customer_id'
end
class Address < ActiveRecord::Base
belongs_to :user, foreign_key: 'customer_id'
end
Add a #current_customer method to your ApplicationController that return a Customer-instance instead of a User-instance:
class ApplicationController
#…
private
def current_customer
current_user && Customer.find_by_id(current_user.id)
end
end
then change your code like this:
class My::Account::AddressesController < MyController
def index
#addresses = current_customer.addresses
end
def new
#address = current_customer.addresses.new
end
#…
end

Resources