Newbie Rails HABTM association not working - ruby-on-rails

I'm new to Rails and trying to create a has_and_belongs_to_many relationship between orders and items.
class Order < ActiveRecord::Base
has_and_belongs_to_many :items
end
class Item < ActiveRecord::Base
has_and_belongs_to_many :orders
end
Migration for Orders (not shown. very basic)
Migration for OrderItems:
class CreateItems < ActiveRecord::Migration
def self.up
create_table :items do |t|
t.string :name
t.decimal :price
t.timestamps
end
create_table :items_orders, :id => false do |t|
t.references :item, :order
end
end
def self.down
drop_table :items
drop_table :items_orders
end
end
In script/console I'm trying to "prove" that relationship works but either my understanding of Ruby is bad (likely) or my model is.
$ script/console
Loading development environment (Rails 2.3.5)
>> o = Order.new
=> #<Order id: nil, name: nil, created_at: nil, updated_at: nil>
>> o.name = 'first order'
=> "first order"
>> o.save
=> true
>> o.items
=> []
>> i1 = o.items.new
=> #<Item id: nil, name: nil, price: nil, created_at: nil, updated_at: nil>
>> i1.name = 'some widget'
=> "some widget"
>> i1.price = 12.50
=> 12.5
>> i1.save
=> true
>> o.items
=> []
>> o.items.first
=> nil
looking in the development.sqlite3 database:
$ sqlite3 development.sqlite3
SQLite version 3.6.12
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
items items_orders orders schema_migrations
sqlite> select * from items_orders;
sqlite> .schema items_orders
CREATE TABLE "items_orders" ("item_id" integer, "order_id" integer);
sqlite>
Nothing!
I know it's obvious...but not to me...at this stage anyway...
what have I missed/screwed up?

First of all, why don't you just use belongs_to and has_many? For example:
class Order < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :order
end
As for why you don't get expected results you can try:
order = Order.new
order.save
item = Item.new
item.order = order
item.save
or better
order = Order.create(myordercolumn => "whatever")
order.items.create(:name => "some widget")

Related

Ruby on Rails - Polymorphic Association with Categories

I'm currently trying to implement a Category model to my application. I'm trying to design it in a way that Users can have many Categories, and so can Groups.
The problem I'm running into is that I also want to be able to just have a normal list of Categories without them being assigned to any User or Group.
I was referencing rubyonrails.org/association_basics.
class CreateCategories < ActiveRecord::Migration[5.0]
def change
create_table :categories do |t|
t.string :name
t.text :description
t.references :categorizable, polymorphic: true, index: true
t.timestamps
end
end
end
class Category < ApplicationRecord
belongs_to :categorizable, :polymorphic => true
end
class User < ApplicationRecord
has_many :categories, :as => :categorizable
end
class Group< ApplicationRecord
has_many :categories, :as => :categorizable
end
I'm trying to create a new Category through rails c, but whenever I try to save, it rolls back my transaction probably because I'm missing some condition.
Category(id: integer, name: string, description: text, created_at: datetime, updated_at: datetime)
Category.create( :id => 1, :name => 'Category_1', :description => '' )
begin transaction
rollback transaction
I also feel like there is a better way to create a new Category, as I shouldn't be setting the id manually.
Thanks for your help.
In rails 5, whenever you define a belongs_to association, it is required to have the associated record present by default. You would see this when you look at the errors after trying to create the category object
category = Category.create(:name => 'Category_1', :description => '' )
category.errors.full_messages.to_sentence
If you want to be able to save a record without the belongs_to association, you would have to specify it explicitly
class Category < ApplicationRecord
belongs_to :categorizable, polymorphic: true, required: false
end
If you try to create a new category and see the error is that there needs to exists a categorizable record in order for the category to be created, an easy way to do it is to put the new object itself as the categorizable one and it should do the trick.
$ category = Category.new
=> #<Category id: nil, name: nil, description: nil, categorizable_type: nil, categorizable_id: nil, created_at: nil, updated_at: nil>
$ category.save
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> false
$ category.errors.full_messages
=> ["Categorizable must exist"]
$ category = Category.new(categorizable: category)
=> #<Category id: nil, name: nil, description: nil, categorizable_type: "Category", categorizable_id: nil, created_at: nil, updated_at: nil>
$ category.save
(0.1ms) begin transaction
SQL (1.3ms) INSERT INTO "categories" ("categorizable_type", "created_at", "updated_at") VALUES (?, ?, ?) [["categorizable_type", "Category"], ["created_at", 2017-01-15 00:08:55 UTC], ["updated_at", 2017-01-15 00:08:55 UTC]]
(0.7ms) commit transaction
=> true
This should help, Rails Cast on Polymorphic
https://www.youtube.com/watch?v=6l9EAuev16k
You can create polymorphic records with this...
`#category = #categorizable.User.new`
`#category = #categorizable.Group.new`
So you do not need the id.

How to create has_many :through associations with presence validations using Rails 4, seeds.rb, and the console?

I am using a has_many :through association between a Musician and an Instrument through a MusiciansInstrument join table:
class Musician < ActiveRecord::Base
has_many :musicians_instruments
has_many :instruments, through: :musicians_instruments
end
class Instrument < ActiveRecord::Base
has_many :musicians_instruments
has_many :musicians, through: :musicians_instruments
validates :instrument_name, presence: true
end
class MusiciansInstrument < ActiveRecord::Base
belongs_to :musician
belongs_to :instrument
validate :began_playing, presence: true
end
I want to make sure that I know how many years each Musician has been playing each Instrument, so I have a required began_playing datetime field in my MusiciansInstrument table.
I would like to do this without accepts_nested_attributes_for if possible.
My schema.rb(minus timestamps):
create_table "musicians_instruments", force: true do |t|
t.integer "musician_id"
t.integer "instrument_id"
t.datetime "began_playing"
end
create_table "instruments", force: true do |t|
t.string "instrument_name"
end
create_table "musicians", force: true do |t|
end
My question is what is how to best create objects using this association when making seeds and querying objects in the console. I also need to get FactoryGirl making these, but first things first.
1) In the console (using the approach proposed in this Stack Overflow)
musician = Musician.new
#=> #<Musician id: nil, user_id: nil, created_at: nil, updated_at: nil>
instrument = Instrument.new(:instrument_name => 'Bass Guitar')
#=> #<Instrument id: nil, created_at: nil, updated_at: nil, instrument_name: "Bass Guitar">
instrument.save!
musician.musicians_instruments << MusiciansInstrument.create(:instrument => instrument, :began_playing => Time.now - 10.years)
#=> #<ActiveRecord::Associations::CollectionProxy [#<MusiciansInstrument id: 19, musician_id: nil, instrument_id: 15, began_playing: "2004-02-25 23:52:12">]>
musician.save!
#=> ActiveRecord::RecordInvalid: Validation failed: Instruments can't be blank
2) In seeds.rb:
m7 = Musician.create
i7 = Instrument.create(:instrument_name => 'Voice')
m7.musicians_instruments << MusiciansInstrument.create(:instrument => i7, :began_playing => Time.now - 10.years)
These console queries return:
Musician.includes(:instruments).where(instruments.instrument_name => "Voice") #=>NameError: undefined local variable or method `instruments' for main:Object
Musician.joins(:instruments).where(instruments.instrument_name => "Voice") #=>NameError: undefined local variable or method `instruments' for main:Object
The seeds.rb statements work, and do create the associated objects and populate the through table with a began_playing date, but the queries are wrong. Two ways to query the began_playing field on the has_many :through join table are:
MusiciansInstrument.joins(:musician).where.not(began_playing: nil)
#=> #<ActiveRecord::Relation [#<MusiciansInstrument id: 13, musician_id: 7, instrument_id: 7, began_playing: "1984-02-26 03:59:01">]>
MusiciansInstrument.joins(:instrument).where.not(began_playing: nil)
#=> #<ActiveRecord::Relation [#<MusiciansInstrument id: 13, musician_id: 7, instrument_id: 7, created_at: "2014-02-26 03:59:01", updated_at: "2014-02-26 03:59:01", began_playing: "1984-02-26 03:59:01">]>
Does anyone know how or if this could be queried from the Musician or Instrument objects? Something like this, which doesn't work:
Musician.joins(:musicians_instruments).where.not(:musicians_instruments.began_playing => nil)

Rails 3. Saving an association which attributes are represented by a value object

Why can't I save changes in an associated object saving a current object if attributes of the first one are represented by a value object?
For example, I have a simple e-commerce application. It use a Client model for manipulating clients
Client model
# db/migrate/<...>_create_clients.rb
class CreateClients < ActiveRecord::Migration
def self.up
create_table :clients do |t|
t.string :name
end
end
def self.down
drop_table :clients
end
end
# app/models/client.rb
Client < ActiveRecord::Base
has_one :balance
end
and an associated Balance model for holding thier balances.
Balance model
# db/migrate/<...>_create_balances.rb
class CreateBalances < ActiveRecord::Migration
def self.up
create_table :balances do |t|
t.integer :amount
t.string :currency
t.references :client
end
end
def self.down
drop_table :balances
end
end
# app/models/balance.rb
class Balance < ActiveRecord::Base
belongs_to :client
composed_of :money,
:mapping =>
[%w{amount cents}, %w{currency currency_as_string}],
:constructor =>
->(amount, currency) { Money.new(amount || 0, currency || 'RUB') }
end
The Balance model uses a Money object from an external library called Money. The object represents model's attributes amount and currency adding to the model useful methods for manipulating those attributes.
Gemfile
# Gemfile
gem 'money'
I have some seed data in seeds.rb.
Seeds
# db/seeds.rb
elena = Client.create(:name => 'Elena')
elena.build_balance.money = Money.new(0, 'RUB')
elena.save
When I try to change a balance of the client it isn't changed in spite of the save method of the current object returns true.
>> elena = Client.find_by_name('Elena')
=> #<Client id: 1, name: "Elena">
>> elena.balance
=> #<Balance id: 1, amount: 0, currency: "RUB", client_id: 1>
>> elena.balance.money
=> 0.00
>> elena.balance.money += Money.new(50000, 'RUB')
=> 500.00
>> elena.save
=> true
# log/development.log
# no changes
However, I can save the changes using the following two ways.
1.
>> elena = Client.find_by_name('Elena')
=> #<Client id: 1, name: "Elena">
>> balance = Balance.find(elena.id)
=> #<Balance id:1 , amount: 0, currency: "RUB", client_id: 1>
>> balance.money += Money.new(50000, 'RUB')
=> 500.00
>> balance.save
=> true
# log/development.log
# UPDATE "balances" SET "amount" = 50000 WHERE ("balances"."id" = 1)
2.
>> elena = Client.find_by_name('Elena')
=> #<Client id:1, name:"Elena">
>> elena.balance.money += Money.new(50000, 'RUB')
=> 500.00
>> elena.balance.save
=> true
# log/development.log
# UPDATE "balances" SET "amount" = 50000 WHERE ("balances"."id" = 1)
Despite I can save changes of a balance using the aforementioned ways I would like to know why I can't do it using the “traditional” <current_model>.save method.
Thanks.
Debian GNU/Linux 5.0.6;
Ruby 1.9.2;
Ruby on Rails 3.0.1;
Money 3.1.5.
It is accomplished by doing this:
# app/models/client.rb
Client < ActiveRecord::Base
has_one :balance, :autosave => true
end
The problem with the association before is that when you were invoking elena.save, it was only looking to see if the Client object had changed. If you performed a check like this on the first example:
>> elena.changed?
=> false
>> elena.balance.changed?
=> true
If you don't inform ActiveRecord to check the associated models it will be lazy and ignore changes when saving the parent model.

Rails 2.3.8 Association Problem has_many belongs_to

I'm new to Rails. I have two models, Person and Day.
class Person < ActiveRecord::Base
has_many :days
end
class Day < ActiveRecord::Base
belongs_to :person
has_many :runs
end
When I try to access #person.days I get an SQL error:
$ script/consoleLoading development environment (Rails 2.3.8)
ree-1.8.7-2010.02 > #person = Person.first
=> #<Person id: 1, first_name: "John", last_name: "Smith", created_at: "2010-08-29 14:05:50", updated_at: "2010-08-29 14:05:50"> ree-1.8.7-2010.02
> #person.days
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: days.person_id: SELECT * FROM "days" WHERE ("days".person_id = 1)
I setup the association between the two before running any migrations, so I don't see why this has not been setup correctly.
Any suggestions?
Telling your model about the association doesn't set up the foreign key in the database - you need to create an explicit migration to add a foreign key to whichever table is appropriate.
For this I'd suggest:
script/generate migration add_person_id_to_days person_id:integer
then take a look at the migration file it creates for you to check it's ok, it should be something like this:
class AddPersonIdToDays < ActiveRecord::Migration
def self.up
add_column :days, :person_id, :integer
end
def self.down
remove_column :days, :person_id
end
end
Run that and try the association again?

ActiveRecord has_one relationship does not return in certain cases

Given three models that are each nested in each other. If I create the top-level object and build_* the other child objects, I can retrieve all child objects through the relationships before and after save() on the original instance. However, if I try to retrieve the 2nd level nested object after find(:id) the original parent it fails. I can retrieve the 1st level nested object, however. This usually happens in a controller, but I'll illustrate it in console output below.
What am I overlooking?
Thanks in advance!
~jpv
>> l = Lead.new :buyer_name => 'Kim Possible', :email => 'kim#possible.com', :phone => '7131231234'
>> l.build_buyer
>> l.buyer.name = 'kim buyer'
>> l.buyer
>> l.buyer.build_contact_detail
>> l.buyer.contact_detail.email = "kim-contact-detail#possible.com"
>> l.save #returns true
>> l.buyer #THIS WORKS
=> #<Buyer id: 1, name: "kim buyer", lead_id: 1>
>> l.buyer.contact_detail #THIS WORKS
=> #<ContactDetail id: 1, company_id: nil, buyer_id: 1, email: nil, address_line_1: nil, address_line_2: nil, city: nil, state: nil, postal_code: nil>
>> l2 = Lead.find(1)
=> #<Lead id: 1, company_id: nil, buyer_id: nil, public_lead_id: nil, buyer_name: "Kim Possible", company_name: nil, email: "kim#possible.com", phone: "7131231234">
>> l2.buyer #THIS WORKS AS EXPECTED
=> #<Buyer id: 1, name: "kim buyer", lead_id: 1>
>> l2.buyer.contact_detail #THIS BREAKS
=> nil
All the boilerplate stuff below:
class Lead
has_one :buyer
#...
end
class Buyer
has_one :contact_detail
belongs_to :lead
#...
end
class ContactDetail
belongs_to :buyer
#...
end
The appropriate foreign keys are in each of the "belongs_to" classes.
class CreateBuyers < ActiveRecord::Migration
def self.up
create_table :buyers do |t|
t.string :name
t.integer :lead_id
...
class CreateContactDetails < ActiveRecord::Migration
def self.up
create_table :contact_details do |t|
t.integer :buyer_id
I think you might be getting tripped up because you're expecting the object to eager load nested child objects. In general, you have to explicitly specify it for that to happen in the find. Try adding :include => [:borrower, {:borrower => :contact_detail}] to the options for the find and see if that works.
Does this work if you do...
l.buyer.name = 'kim buyer'
l.save
l.buyer.build_contact_detail
...
If so, this could be a rails bug with the ContactDetail object not really knowing who its daddy is at creation time.

Resources