I've got orders which can be made from different suppliers. I want to group the total amount of money paid by the said supplier.
My SupplierOrder.rb:
class SupplierOrder < ApplicationRecord
include Order
belongs_to :product
has_many :payments, as: :payable, dependent: :destroy
accepts_nested_attributes_for :payments, allow_destroy: true
end
Now, a simple SupplierOrder.group(:supplier) in the console delivers me:
SupplierOrder.group(:supplier)
SupplierOrder Load (0.5ms) SELECT "supplier_orders".* FROM "supplier_orders" GROUP BY "supplier_orders"."supplier"
(Object doesn't support #inspect)
=>
If it's helpful, here my schema.rb:
create_table "supplier_orders", force: :cascade do |t|
t.float "paid"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.float "price"
t.string "supplier"
t.string "order_number"
t.integer "amount"
t.bigint "product_id"
t.index ["product_id"], name: "index_supplier_orders_on_product_id"
end
Here is my order.rb Concern (`models/concerns/order.rb'):
module Order
extend ActiveSupport::Concern
def full_title
self.product.full_title
end
end
Right now I'm getting supplier-Value from a global variable, defined in helpers/application_helper.rb:
module ApplicationHelper
SIZES = %w(1:1 1:2 1:3 1:4 1:5 1:6 1:7 1:8)
VERSIONS = %w(regular deluxe exclusive)
COLORS = %w(black blue white)
SUPPLIERS = %w(A B C D)
end
I don't override any initializers like in linked topics or do anything extraordinary. All (at least for now) other methods do work in the console, but just this one is not.
Add gem 'pry-rails' in your development group in Gemfile and run bundle install and execute the command again in rails console.
Related
I am working on the flight-booker project in The Odin Project. I am trying to seed a database with 60 days worth of flights to and from 10 cities. When I seed the database, I get this error:
Created database 'flight_booker_development' Created database
'flight_booker_test'
#Airport:0x00007f003f0c6f20 rails aborted! ActiveModel::MissingAttributeError: can't write unknown attribute
flight_id
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
^^^^^ /home/stuart/repos/flight-booker/db/seeds.rb:23:in `block (4 levels) in <main>'
/home/stuart/repos/flight-booker/db/seeds.rb:22:in times' /home/stuart/repos/flight-booker/db/seeds.rb:22:in block (3 levels)
in ' /home/stuart/repos/flight-booker/db/seeds.rb:21:in times' /home/stuart/repos/flight-booker/db/seeds.rb:21:in block (2 levels)
in ' /home/stuart/repos/flight-booker/db/seeds.rb:15:in block in <main>' /home/stuart/repos/flight-booker/db/seeds.rb:14:in '
Tasks: TOP => db:reset => db:setup => db:seed (See full trace by
running task with --trace)
The problem seems to be that it is trying to write an attribute that I am not using. Here is my seeds.rb file:
require "faker"
airports = %w[LAX DFW NYC DEN BOS MIA HOU PIT POR MIN]
airports.each { |city_code| Airport.create!(city_code: city_code) }
Airport.all.each do |departure|
Airport.all.each do |arrival|
next if departure == arrival
puts departure
duration = rand(100..300)
flight_number = rand(1000..1999)
frequency = rand(3..5)
60.times do
frequency.times do
Flight.create!(
origin_id: departure,
destination_id: arrival,
duration: duration,
flight_number: flight_number,
departure_time: Faker::Time.forward(days: 60, period: :all)
)
end
end
end
end
I was trying to use flight_id to hold the flight number, but have changed that because I realized I had a flight_id column in my bookings table. I am not doing anything with the bookings table at this time though. I was getting this same error before and then I did a migration to remove flight_id and add_flight number to the flights table. Here is the current schema.rb
ActiveRecord::Schema[7.0].define(version: 2022_12_09_210422) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "airports", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "city_code"
end
create_table "bookings", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id", null: false
t.bigint "flight_id", null: false
t.index ["flight_id"], name: "index_bookings_on_flight_id"
t.index ["user_id"], name: "index_bookings_on_user_id"
end
create_table "flights", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "destination"
t.string "origin"
t.datetime "departure_time"
t.integer "duration"
t.bigint "destination_id", null: false
t.bigint "origin_id", null: false
t.integer "flight_number"
t.index ["destination_id"], name: "index_flights_on_destination_id"
t.index ["origin_id"], name: "index_flights_on_origin_id"
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.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "bookings", "flights"
add_foreign_key "bookings", "users"
add_foreign_key "flights", "airports", column: "destination_id"
add_foreign_key "flights", "airports", column: "origin_id"
end
Here the models/flight.rb file:
class Flight < ApplicationRecord
belongs_to :booking, foreign_key: "flight_id"
has_one :origin_id, class_name: "Airport"
has_one :destination_id, class_name: "Airport"
validates :departure_time, presence: true
validates :duration, presence: true
validates :flight_number, presence: true
end
That is the only place that flight_id appears anywhere, but I still get the same error if I remove that line of code.
In the error it is referencing the seeds file line 23, which is the start of the Flight.create action. It did previously try to create flight_id, but that has been changed to flight_number, and I have saved the file, and restarted the computer to be thorough.
Here is the flights_controller.rb file:
class FlightsController < ApplicationController
before_action :set_flight
def index
#flight = Flight.all
end
def new
#flight = Flight.new
end
def create
#flight = Flight.new(flight_params)
end
private
def set_flight
#flight = Flight.find(params[:id])
end
def flight_params
params.require(:flight).permit(
:airport,
:flight_number,
:origin,
:origin_id,
:destination_id,
:destination,
:duration,
:departure_time,
:arrival_time
)
end
end
I previously had flight_id as a permitted param, but that has been changed to flight_number.
So, I'm at a bit of a loss as to what to try next. Any help would be greatly appreciated. I would be happy to provide any additional information you might think is relevant. Thank you.
Edit to add that I tried creating a table entry from the rails console and got the same error in irb.
irb(main):001:0> Flight.create!(departure_time: "2022-12-25 11:11:11
-0700", duration: 200, flight_number: 1599, origin_id: Airport.first, destination_id: Airport.last) Airport Load (0.2ms) SELECT
"airports".* FROM "airports" ORDER BY "airports"."id" ASC LIMIT $1
[["LIMIT", 1]] Airport Load (0.1ms) SELECT "airports".* FROM
"airports" ORDER BY "airports"."id" DESC LIMIT $1 [["LIMIT", 1]]
/home/stuart/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/activemodel-7.0.4/lib/active_model/attribute.rb:211:in
with_value_from_database': can't write unknown attribute flight_id`
(ActiveModel::MissingAttributeError)
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
^^^^^ irb(main):002:0>
The problem is that you have defined your assocation with foreign_key: "flight_id"
class Flight < ApplicationRecord
belongs_to :booking, foreign_key: "flight_id"
end
This option is documented as:
Specify the column used to store the associated object's type
Needless to say having a flights.flight_id column that references bookings is just wrong.
Lets scrub this broken attempt and try again. Stash you work in GIT and roll back and lets setup those models correctly.
To setup the assocation just between flights, airports and airlines you should use three tables and associate them like so:
# rails g model airline name
class Airline < ApplicationRecord
has_many :flights
end
# rails g model airport name iata_code
class Airport < ApplicationRecord
has_many :flights_as_origin,
class_name: 'Flight',
foreign_key: :origin_id
has_many :flights_as_destination,
class_name: 'Flight',
foreign_key: :destination_id
end
# rails g model flight flight_number:string airline:references origin:references destination:references
class Flight < ApplicationRecord
belongs_to :airline
# These should be belongs_to assocations
belongs_to :origin, class_name: 'Airport'
belongs_to :destination, class_name: 'Airport'
end
This is the data thats common to all passengers. You don't alter anything here when a passenger books a ticket. When creating the flights table you need to explicitly tell Rails that destination_id and origin_id should point to the airports table:
class CreateFlights < ActiveRecord::Migration[7.0]
def change
create_table :flights do |t|
# ...
t.references :origin, null: false, foreign_key: { to_table: :airports }
t.references :destination, null: false, foreign_key: { to_table: :airports }
# ...
end
end
end
To model the assocation between passengers, flights and bookings you actually want a many to many assocation with a join table:
# rails g model ticket passenger_name:string flight:references seat:string
class Ticket < ApplicationRecord
belongs_to :flight
end
class Booking < ApplicationRecord
belongs_to :user
has_many :tickets
has_many :flights, through: :tickets
end
class Flight < ApplicationRecord
# ...
has_many :tickets
has_many :bookings, through: :tickets
end
Here you use the tickets table to store each item in an itenary so that you can actually model stuff like multi-leg trips and multiple passengers per booking.
Issue is I can't find why reference column id can't be inserted when create new record.
I have 3 table shop_plan, shop and app
Below is tables schema:
create_table "shop_plans", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "shops", force: :cascade do |t|
t.string "url"
t.bigint "plan_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["plan_id"], name: "index_shops_on_plan_id"
end
create_table "apps", force: :cascade do |t|
t.bigint "shop_id"
t.binint "amount"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["app_id"], name: "index_apps_on_shop_id"
end
add_foreign_key "shops", "shop_plans", column: "plan_id"
add_foreign_key "apps", "shops"
And below is Model
class ShopPlan < ApplicationRecord
has_many :shop
end
class Shop < ApplicationRecord
belongs_to :shop_plan, class_name: 'ShopPlan', foreign_key: :plan_id
has_many :app
end
class App < ApplicationRecord
belongs_to :shop, class_name: 'Shop', foreign_key: :shop_id
end
There will be 1 default record added in seed.db for table shop_plan
ShopPlan.create(name: 'Basic')
ShopPlan and Shop are linked by plan_id column in Shop
Shop and App are linked by shop_id column in App
I pre-insert some value when user access index:
#basic_plan
#basicPlan = ShopPlan.where(name: "Basic").first
# if new shop registered, add to database
unless Shop.where(url: #shop_session.url).any?
shop = Shop.new
shop.url = #shop_session.url
shop.plan_id = #basicPlan.id
shop.save
end
This insert works well, however, when i run 2nd insert:
#shop= Shop.where(url: #shop_session.url).first
unless App.where(shop_id: #shop.id).any?
app = App.new
app.shop_id = #shop.id,
app.amount = 10
app.save
end
error occurs as somehow app.shop_id will not add in my #shop.id and it will return will error: {"shop":["must exist"]}
I even try hard-code app.shop_id =1 but it does not help and when I add in optional: true to app.db model, it will insert null
Appreciate if anyone can help point out why I get this error
EDIT: #arieljuod to be clear
1) I have to specific exact column class due to between Shop And Shop_Plan, i'm using a manual plan_id instead of using default shopplans_id columns.
2) I have update 1 column inside App and all that unless is just to do checking when debugging.
First of all, like #David pointed out, your associations names are not right. You have to set has_many :shops and has_many :apps so activerecord knows how to find the correct classes.
Second, you don't have to specify the class_name option if the class can be infered from the association name, so it can be belongs_to :shop and belongs_to :shop_plan, foreign_key: :plan_id. It works just fine with your setup, it's just a suggestion to remove unnecesary code.
Now, for your relationships, I think you shouldn't do those first any? new block manually, rails can handle those for you.
you could do something like
#basicPlan = ShopPlan.find_by(name: "Basic")
#this gives you the first record or creates a new one
#shop = #basicPlan.shops.where(url: #shop_session.url).first_or_create
#this will return the "app" of the shop if it already exists, and, if nil, it will create a new one
#app = #shop.app or #shop.create_app
I have found out the silly reason why my code does not work.
It's not because as_many :shops and has_many :app and also not because my code when creating the record.
It just due to silly comma ',' when creating new record in App at app.shop_id = #shop.id,, as I was keep switching between Ruby and JavaScript. Thank you #arieljuod and #David for your effort
schema.rb:
ActiveRecord::Schema.define(version: 20150324012404) do
create_table "groups", force: :cascade do |t|
t.string "title"
t.integer "teacher_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "groups_students", id: false, force: :cascade do |t|
t.integer "group_id"
t.integer "student_id"
end
add_index "groups_students", ["group_id"], name: "index_groups_students_on_group_id"
add_index "groups_students", ["student_id"], name: "index_groups_students_on_student_id"
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.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "admin", default: false
t.string "type"
t.integer "group_id"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
group.rb:
class Group < ActiveRecord::Base
belongs_to :teacher
has_and_belongs_to_many :students
end
student.rb:
class Student < User
has_and_belongs_to_many :groups
end
I could have set a simple belongs_to and a has_many relationship between the student and group models, but I want students to be able to belong to more than one group, so I set up a HABTM association and corresponding join table.
I think I that right?
The question is, how do I, in the console, set a Student to belong to more than one group?
I have setup a User with 'type: Student' and I have two Groups. So...
In the console I do:
student = Student.first
Then, I want to set 'student' to belong to both Groups, but I don't know how to do this.
To set it to belong to one group I can do:
student.update_attributes(group_id: 1)
But how do make it belong to both groups? It would have two group_id's wouldn't it? I don't know how to set this.
If you need to see any of the other files, it's the 'handcode' branch here:
https://github.com/Yorkshireman/sebcoles/tree/handcode
The answers others have already provided are correct. But if you're working with id's you can also do something like this
student = Student.first
student.group_ids = 1,2,3,4
You don't need to set group_id for the User, the association is handled by the join table and the HABTM statement. You should remove group_id from the users table in the schema.
From memory you should be able to do something like this:
student = Student.first
groups = Group.all
student.groups << groups
student.save
See the active record guide on HABTM associations - specfically 4.4.1.3
Instead of habtm, just use the normal through and your life becomes easy. Make sure an id is generated for the association table (remove id:false)
create_table "group_students", force: :cascade do |t|
t.integer :group_id, nil:false
t.integer :student_id, nil:false
end
class Group < ActiveRecord::Base
has_many :group_students, dependent: :destroy, inverse_of :group
has_many :students, through :group_students
end
class Student < ActiveRecord::Base
has_many :group_students, dependent: :destroy, inverse_of :student
has_many :groups, through: :group_students
end
class GroupStudent < ActiveRecord::Base
belongs_to :group,
belongs_to :student
validates_presence_of :group, :student
end
Group.last.students << Student.last
or..
Student.last.groups << Group.last
Student.last.groups = [Group.find(1), Group.find(2)]
etc....
Ok, so it took me 3 days of all kinds of pain to work this out.
There was nothing wrong with my original code, except that I needed to remove the group_id from the user table.
roo's answer was correct, except that using 'group' as a variable name in the console confused Rails. This had led me to believe there was something wrong with my code, but there wasn't. You learn the hard way.
So, Students can be pushed into Groups like this:
To push a student into one group:
student = Student.first
OR
student = Student.find(1)
(or whatever number the id is)
group1 = Group.first
OR
group1 = Group.find(1)
student.groups << group1
To push into multiple groups (which was the original goal of this whole debacle:
student = Student.first
OR
student = Student.find(1)
allclasses = Group.all
student.groups << allclasses
To view your handywork:
student.groups
Works beautifully. The only problem I can see with my code is that it's possible to push the same student into a group twice, resulting in two duplicates of that student in one group. If anyone knows how to prevent this happening, I'm all ears.
I have a scenario where I need to have multiple has_one relationships and be able to access them all at once through a relationship.
Currently I have an STI table, we'll call it Animals.
There are subclasses: Lion, Meerkat, Boar
And they all need to relate to multiple Group objects.
So a Lion can be in multiple groups, but a Group may only have one Lion related.
I want to limit and refer to specifically one Lion, Meerkat and Boar.
I've tried a simple bridge table, has_many :animals, through: :associated_animals. But there doesn't seem to be an easy way to pull out group.lion. I've also tried doing multiple has_one :lion/:meerkat/:boar relationships, but there was no obvious way to do group.animals.
Is there an STI way to get this functionality through default rails associations or do I have to do a combination of both the has_one :lion and has_many :animals?
Have you tried it like this:
app/models/animal.rb
class Animal < ActiveRecord::Base
belongs_to :group
end
class Lion < Animal; end
class Meerkat < Animal; end
Of course you can split out the Lion and Meerkat classes to separate ruby files
app/models/group.rb
class Group < ActiveRecord::Base
has_many :animals
has_one :lion
has_one :meerkat
end
after you run your migrations your schema should look something like this:
db/schema.rb
ActiveRecord::Schema.define(version: 20150309234835) do
create_table "animals", force: :cascade do |t|
t.string "name"
t.string "type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "group_id"
end
create_table "groups", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Now you should be able to write queries using group:
g = Group.find(1)
g.animals
g.lion
Lion.first.group
Animal.last.group
I have an article model that should belong to a section. I'm having trouble making this connection work and I receive "Undefined Method" errors when attempting Section.article or Article.section in rails console.
How can I tie these models together to print all articles of a particular section and verify their connection?
I've implemented many solutions from answers and posts and may have mixed things up.
Thank you for your help!
Models (I've also had versions with a forgeign_key or reference entries):
class Article < ActiveRecord::Base
belongs_to :section
end
class Section < ActiveRecord::Base
has_many :articles
end
Migration to update tables:
class AddSectionRefToArticles < ActiveRecord::Migration
def change
add_reference :articles, :section, index: true
end
end
Schema.rb
ActiveRecord::Schema.define(version: 20141107123935) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "articles", force: true do |t|
t.string "title"
t.string "body"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "section_id"
end
add_index "articles", ["section_id"], name: "index_articles_on_section_id", using: :btree
create_table "sections", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
end
What are you actually running on the command line? Section.article or Article.section will not work.
You need to run the relation methods on an instance, not the class itself.
section = Section.create(name: 'Section 1')
section.articles << Article.new(title: 'My article')
section.articles # returns an array of articles belonging to the Section 1 object
Article.last.section # returns the Section 1 object
You attempt to use class methods (Section.article or Article.section), whereas associations are defined as instance methods. So that, to call an association you have to call it on an object, e.g: Section.first.articles or Article.last.section