I would like an Order object to be comprised of many Product objects, so I set up a HABTM relationship on object.
I'm wondering if it's "correct" (or the Ruby/Rails) way to also include additional data within the HABTM table. For instance, if I need to compute the subtotal and there's a chance the line-item totals might need to be overridden, do I store that as part of the association table, or do I need a LineItem object or something better?
Thanks
ActiveRecord::Schema.define(version: 3) do
create_table "orders", force: true do |t|
t.string "order_id", null: false
t.string "order_status", default: "new"
# <snip>
t.decimal "pay_total", precision: 8, scale: 2, null: false
end
add_index "orders", ["order_id"], name: "index_orders_on_order_id", unique: true, using: :btree
add_index "orders", ["order_status"], name: "index_orders_on_order_status", using: :btree
create_table "orders_products", id: false, force: true do |t|
t.integer "order_id" # migrated with belongs_to
t.integer "product_id" # migrated with belongs_to
t.decimal "pay_cost", precision: 8, scale: 2, null: false
t.decimal "pay_discount", precision: 8, scale: 2, default: 0.0
t.decimal "pay_linetotal", precision: 8, scale: 2, null: false
end
add_index "orders_products", ["order_id", "product_id"], name: "index_orders_products_on_order_id_and_product_id", unique: true, using: :btree
create_table "products", force: true do |t|
t.string "name", null: false
t.decimal "price", precision: 8, scale: 2,null: false
t.boolean "active", default: true
end
Join tables (aka HABTM) are purely for joining relationships and Rails (Active Record) ignores any additional fields. However, you can get around this by using a has_many through relationship, which would make sense to call "LineItem" instead of "OrdersProducts".
class Order
has_many :line_items
has_many :products, through: :line_items
end
class LineItem
belongs_to :order
belongs_to :product
end
class Product
has_many :line_items
has_many :orders, through: :line_items
end
Related
I'm trying to populate the flight table through the seed.rb file on sqlit3 db in rails ActiveRecord with the following code:
departure = Date.new(2021, 9, 1)
arrival = Date.new(2021, 10, 1)
(departure).upto(arrival).each do |flight_schedule|
airports.each do |origin|
airports.each do |destination|
if origin == destination
next
else
3.times { Flight.create(origin: origin,
destination: destination,
flight_schedule: flights_time,
duration: flights_duration(origin.code, destination.code))}
end
end
end
end
I have a many to many relationships set up between the airport and flight model with references (foreign_key) as follows;
class Airport < ApplicationRecord
has_many :departures,
class_name: :Flight,
foreign_key: :origin_id,
dependent: :destroy
has_many :arrivals,
class_name: :Flight,
foreign_key: :destination_id,
dependent: :destroy
end
class Flight < ApplicationRecord
belongs_to :origin, class_name: :Airport
belongs_to :destination, class_name: :Airport
end
Here is the the db schema;
ActiveRecord::Schema.define(version: 2021_07_21_232515) do
create_table "airports", force: :cascade do |t|
t.string "code"
t.string "location"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "flights", force: :cascade do |t|
t.integer "origin_id", null: false
t.integer "destination_id", null: false
t.integer "duration"
t.datetime "flight_schedule"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["destination_id"], name: "index_flights_on_destination_id"
t.index ["origin_id"], name: "index_flights_on_origin_id"
end
add_foreign_key "flights", "destinations"
add_foreign_key "flights", "origins"
end
When I run rails db:seed
I have these errors:
Rails aborted!
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such table: main.destinations
Rails version 6.1.4
Ruby version 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-linux]
the schema looks incorrect, add_foreign_key should be like this
add_foreign_key "flights", "airports", column: "destination_id"
add_foreign_key "flights", "airports", column: "origin_id"
so i guess your migrations are incorrect, you should check those migrations relative to add references and foreign_key to flights table, it should look like this:
create_table :flights do |t|
# ...
t.belongs_to :origin, null: false
t.belongs_to :destination, null: false
# ...
t.foreign_key :airports, column: :origin_id
t.foreign_key :airports, column: :destination_id
end
I'm building a Rails app that has modals Outage, Service, Note and User.
Service has a boolean attribute is_down. By default, is_down is false. When the attribute is updated to true meaning the service goes down, an Outage should be created and a Note should also be created with User, automated.
This all happens in one update of the is_down attribute. If Service goes back up, the outage remains intact but now has an end_time.
Here is the 'story line`:
Service model:
class Service < ApplicationRecord
has_many :outages
has_many :notes
# This is where I'm confused
is_down
if self.is_down
Outage.create(start_time: Time.now, reason: nil)
Note.create(user_id: 1, entry: "Outage began at #{Time.now}", service_id: self.id)
end
end
end
Outage model:
class Outage < ApplicationRecord
belongs_to :service
has_many :notes
has_many :users, through: :notes
end
Note modal (a join table between Outage and User)
class Note < ApplicationRecord
belongs_to :outage
belongs_to :user
end
and User model:
class User < ApplicationRecord
has_many :notes
has_many :outages, through: :notes
end
Outage is more like a story line where during the outage, users can enter notes about what they've learned.
Here is the schema:
enable_extension "plpgsql"
create_table "notes", force: :cascade do |t|
t.text "entry"
t.boolean "is_public", default: true
t.bigint "outage_id"
t.bigint "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["outage_id"], name: "index_notes_on_outage_id"
t.index ["user_id"], name: "index_notes_on_user_id"
end
create_table "outages", force: :cascade do |t|
t.datetime "start_time"
t.datetime "end_time"
t.text "reason"
t.boolean "is_recurring", default: false
t.string "frequency", default: "None"
t.bigint "service_id"
t.index ["service_id"], name: "index_outages_on_service_id"
end
create_table "services", force: :cascade do |t|
t.string "name"
t.boolean "is_down", default: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "username"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
add_foreign_key "notes", "outages"
add_foreign_key "notes", "users"
add_foreign_key "outages", "services"
end
Besides the initial question of automated creation on update of Service attribute, is_down, is this also a good way to go about implementing this?
I would suggest looking into the lifecycle callbacks for ActiveRecord. You can add an after_save callback to your Service class that checks to see if is_down has changed and then create or close an Outage
class Service < ApplicationRecord
has_many :outages
...
after_save :create_or_update_outage, if: is_down_changed?
...
private
def create_or_update_outage
if is_down
outages.create
else
outages.where(end_time: nil).last.update(end_time: Time.now)
end
end
I am trying to make an association that is not working.
I have the following scope:
ActiveRecord::Schema.define(version: 2020_04_05_125812) do
create_table "accounts", force: :cascade do |t|
t.string "social_network"
t.string "name_account"
t.integer "person_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["person_id"], name: "index_accounts_on_person_id"
end
create_table "lists", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "lists_people", id: false, force: :cascade do |t|
t.integer "list_id", null: false
t.integer "person_id", null: false
end
create_table "people", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "posts", force: :cascade do |t|
t.string "post_text"
t.date "date"
t.string "link"
t.integer "account_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_posts_on_account_id"
end
add_foreign_key "accounts", "people"
add_foreign_key "posts", "accounts"
end
I wish I could consult, for example:
I want to consult the person named "Test_name"
That person belongs to lists, which is a collection of people. In addition, that person has accounts and those accounts have Posts.
class List < ApplicationRecord
has_and_belongs_to_many :people
end
class Person < ApplicationRecord
has_and_belongs_to_many :lists
end
class Account < ApplicationRecord
has_many :posts
belongs_to :person
end
class Post < ApplicationRecord
belongs_to :account
end
How could I have a return like the one below:
List | Name | social_network
1 | Test_name | facebook
2 | Test_name | twitter
All the queries I make, either return the wrong type, or return only the list.
Problems such as:
List | Name | social_network
1 | Test_name | facebook
2 | Test_name | twitter
1 | Second_name | twitter
And I don't want to see the data "second_name"
I try this:
#lists = List.from(
Person.left_outer_joins(:list).where('people.name LIKE ?', "Renata Rempel"),
:list
)
but, doesn't works =/
To start off with you want to setup a many to many association between Person and List. This can be done with has_and_belongs_to_many but there are many reasons why has_many through: may be a better choice. The primary one is that it will let you add features like keeping track of banned users or when a user joined a list.
# rails g model list_membership member:belongs_to user:belongs_to
class ListMembership < ApplicationRecord
belongs_to :member, class_name: 'Person'
belongs_to :list
end
We then have to fix the foreign key in the association:
class CreateListMemberships < ActiveRecord::Migration[6.0]
def change
create_table :list_memberships do |t|
t.belongs_to :list, null: false, foreign_key: true
t.belongs_to :member, null: false, foreign_key: { to_table: :people }
t.timestamps
end
# can be a good idea to add a compound index
# add_index [:list_id, :member_id], unique: true
end
end
class Person < ApplicationRecord
has_many :list_memberships, foreign_key: :member_id
has_many :lists, through: :list_memberships
has_many :accounts
has_many :posts, through: :accounts
end
class List
has_many :list_memberships
has_many :members,
through: :list_memberships
end
Your from query will not work as your actually selecting rows from people but you just alias the table lists. That won't magically select the right data! If you really wanted to use from you would do:
List.from(
List.joins(:members).where("people.name LIKE ?", "Renata Rempel"),
:lists
).eager_load(members: :posts)
If you want to create a bunch of lists with a random number of members in your seed file you can just do:
ids = 10.times.map do
Person.create!(name: Faker::Name.name).id
end
lists = 10.times.do
List.create!(member_ids: ids.sample(2))
end
In my online shop I have tables Product and Size, also I think I need to add a table Restocking
Instead of updating a product, I guess It's better to have a Restocking table then I could track the dates where I added any new sizes, quantity, and why not the new prices (buying and selling)... and create stats...
Do you this it is correct?
Once a Restocking is created, the corresponding Product is updated with new quantity and price?
Well,
So it started this way:
#Product
has_many :sizes
accepts_nested_attributes_for :sizes, reject_if: :all_blank, allow_destroy: true
#Size
belongs_to :product
The Restocking table needs to have sizes attributes (like product)
I believe that I have to use polymorphic associations, but how I am supposed to update my schema , what should I add, remove?
So since I added the Restocking model, my models look like this:
#Product
has_many :sizes, inverse_of: :product, dependent: :destroy, as: :sizeable
has_many :restockings
accepts_nested_attributes_for :sizes, reject_if: :all_blank, allow_destroy: true
#Restocking
has_many :sizes, as: :sizeable
belongs_to :product
accepts_nested_attributes_for :sizes, reject_if: :all_blank, allow_destroy: true
#Size
belongs_to :product
belongs_to :restocking
belongs_to :sizeable, polymorphic: true, class_name: "Size"
schema.rb
create_table "sizes", force: :cascade do |t|
t.string "size_name"
t.integer "quantity"
t.bigint "product_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "quantity_stock"
t.index ["product_id"], name: "index_sizes_on_product_id"
end
create_table "restockings", force: :cascade do |t|
t.bigint "product_id"
t.bigint "sizeable_id"
t.decimal "price", precision: 10, scale: 2
t.decimal "buying_price", precision: 10, scale: 2
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["product_id"], name: "index_restockings_on_product_id"
t.index ["sizeable_id"], name: "index_restockings_on_sizeable_id"
end
create_table "products", force: :cascade do |t|
t.string "title", limit: 150, null: false
t.text "description"
t.bigint "category_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "color"
t.integer "user_id"
t.json "attachments"
t.string "brand"
t.string "ref"
t.decimal "price"
t.decimal "buying_price", precision: 10, scale: 2
t.index ["category_id"], name: "index_products_on_category_id"
end
At this point I have several errors, like
in ProductsController
def new
#product = Product.new
#product.sizes.build
end
error:
ActiveModel::UnknownAttributeError at /admin/products/new
unknown attribute 'sizeable_id' for Size.
Can you light me on the migrations I have to change?
Suggestions are welcome
You're almost there, to use polymorphic inside your Size model, you have to change the size resource, and add two attributes to the resource: sizeable_id and sizeable_type.
The sizeable_type is a string, indicates the class of the parent element, in your case, can be Product or Restocking, and sizeable_id indicates the element_id to find the parent element, your relations are correct, but you must add this elements to your Size, see the following:
One exemple of a migration to your case:
class AddSizeableToSize < ActiveRecord::Migration
def change
add_reference :sizes, :sizeable, polymorphic: true, index: true
end
end
On your Size model:
# app/models/size.rb
class Size < ActiveRecord::Base
belongs_to :sizeable, polymorphic: true
end
In your Product or Restocking model:
has_many :sizes, as: :sizeable
This is just a simple way to make your case works! If you want to know more about rails associations and polymorphism, can take a look in this link.
Ruby 2.3.0, Rails 4.2.4, PostgreSQL 9.5
UPDATE: added activerecord-import code below.
Does anyone know how to make these associations hold, so that a model's table attributes can be referenced in another view? Similar to another Q&A (Rails has_many through aliasing with source and source_type for multiple types), where I have investors, companies, and transactions.
I've tried associations like the below (has_many ... through ...), but I'm failing to get ActiveRecord to recognize the connection among the 3 models & tables. Seeding the db:
The way data gets into these tables is via a csv file having 3 columns. I use roo-xls to extract each into an array of arrays.
My activerecord-import gem-based code (each *_val is an array of 1000s of arrays):
icol = [:name]
ccol = [:name]
tcol = [:investor_name, :company_name, :percent_owned]
investor_val = [["i1"],["i2"]] # just showing 2 arrays for brevity
company_val = [["c1"],["c2"]] # ""
transaction_val = [["i1","c1","pct1"],["i2","c2","pct2"]] # ""
Investor.import icol, investor_val, :validate => false
Company.import ccol, company_val, :validate => false
Transaction.import tcol, transaction_val, :validate => false
Import works, but when I check the transactions table, both company_id and investor_id are nil after executing the activerecord-import .import. I of course would like them to contain the foreign keys for the company and investor model records.
My models are below.
Class Company < ActiveRecord::Base
has_many :investors,
:through => :transactions
has_many :transactions
end
Class Investor < ActiveRecord::Base
has_many :companies,
:through => :transactions
has_many :transactions
end
Class Transaction < ActiveRecord::Base
belongs_to :company
belongs_to :investor
end
Transactions migration (others left out for brevity)
class CreatePositions < ActiveRecord::Migration
def change
create_table :positions do |t|
t.string :investor_name
t.string :company_name
t.string :percent_owned
t.belongs_to :company, index: true
t.belongs_to :manager, index: true
t.timestamps null: false
end
end
end
My schema, where I've added references to the belongs_to (transactions) table.
ActiveRecord::Schema.define(version: 20160128224843) do
create_table "companies", force: :cascade do |t|
t.string "name"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "investors", force: :cascade do |t|
t.string "name"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "transactions", force: :cascade do |t|
t.string "investor_name"
t.string "company_name"
t.float "percent_owned"
t.integer "investor_id"
t.integer "company_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "transactions", ["investor_id"], name: "index_transactions_on_investor_id", using: :btree
add_index "transactions", ["company_id"], name: "index_transactions_on_company_id", using: :btree