Can't figure out has_many/belongs_to RAILS 4 - ruby-on-rails

My models are:
class CarBrand < ActiveRecord::Base
has_many :car_models
end
class CarModel < ActiveRecord::Base
belongs_to :car_brand
end
and my migrations are
class CreateCarBrands < ActiveRecord::Migration
def up
create_table :car_brands do |t|
t.string "brand", :limit => 20
t.timestamps null: false
end
end
def down
drop_table :car_brands
end
end
class CreateCarModels < ActiveRecord::Migration
def up
create_table :car_models do |t|
t.references :car_brand
t.string "model", :limit => 20
t.timestamps null: false
end
add_index :car_models, :car_brand_id
end
def down
drop_table :car_models
end
end
and i want to get car models according to specific car brand, in database i have both records, but when i type in console it gives error
somecar = CarBrand.where(:brand => 'Toyota')
somecar.car_models
so it doesn't returns me models of toyota, but i have them in database!!!

somecar = CarBrand.where(:brand => 'Toyota') returns an active record relation #<ActiveRecord::Relation [#<..... the main point is that it is a collection. You have to iterate over each item in the collection.
some_cars = CarBrand.where(:brand => 'Toyota')
some_cars.each do |car| puts car.car_model end
or on the first item some_cars.first.car_model
Notice I changed some_car to some_cars, the name of the variable does matter but it is easier to see that it is a collection. Notice I called .car_model (and NOT car_models) on each item, that really is important.

Try like that:-
somecar = CarBrand.where(:brand => 'Toyota')
somecar.first.car_models
As CarBrand.where(:brand => 'Toyota') returns an array.
OR
Try like that:-
somecar = CarBrand.find_by brand: 'Toyota'
somecar.car_models
CarBrand.find_by brand: 'Toyota' will fetch first matching record.

Related

Scope a Post with his association table

I would like to know how we can scope a Post with his association table ?
I explain what I want to do it will be more clear. I actually have a scaffold :ranch, and a scaffold :staff. And when I create a new Staff, I can choose to associated it with many ranches. So I have an other table to reference this association :ranch_staff.
So if I want to scope the staff to display only which are associated with the actual #ranch, how can I do that ?
Something like this should do the trick:
migration:
class Init < ActiveRecord::Migration
def change
create_table :ranches do |t|
t.string "name"
end
create_table :staffs do |t|
t.string "name"
end
create_table :ranches_staffs, id: false do |t|
t.belongs_to :ranch
t.belongs_to :staff
end
end
end
app/models/staff.rb:
class Staff < ActiveRecord::Base
has_and_belongs_to_many :ranches
end
app/models/ranch.rb:
class Ranch < ActiveRecord::Base
has_and_belongs_to_many :staffs
end
run in rails console:
staff1 = Staff.create(name: "staff1")
staff2 = Staff.create(name: "staff2")
staff3 = Staff.create(name: "staff3")
staff4 = Staff.create(name: "staff4")
ranch1 = Ranch.create(name: "ranch1")
ranch2 = Ranch.create(name: "ranch1")
staff1.ranches << ranch1
staff2.ranches << ranch1
staff3.ranches << ranch2
staff4.ranches << ranch2
Now you can access ranch1's staff by
ranch1.staffs
=> #<ActiveRecord::Associations::CollectionProxy [#<Staff id: 1, name: "staff1">, #<Staff id: 2, name: "staff2">]>
See association.

ActiveRecord: Scope to get parents with no children created at a specific date

I want to get all parent records with no records created at a specific date on a one-to-many relation in Rails 4 with ActiveRecord and Postgres as database.
Migrations:
class CreateParents < ActiveRecord::Migration
def change
enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
create_table :parents, id: :uuid, default: 'gen_random_uuid()' do |t|
t.string :name
t.timestamps null: false
end
end
class CreateChilds < ActiveRecord::Migration
def change
create_table :childs, id: false do |t|
t.uuid :parent_id, null: false
t.date :created_at, null: false
t.string :name
end
add_foreign_key :childs, :parents
add_index :childs, [:parent_id, :created_at], :unique => true
end
end
Models:
class Parent < ActiveRecord::Base
has_many :childs
end
class Child < ActiveRecord::Base
belongs_to :parent
end
Now I want to get all parents with no child at a specific date with a scope:
class Parent < ActiveRecord::Base
has_many :childs
def self.with_no_childs_created_at(date)
...
end
end
Can anybody help me? I'm going really crazy with this. I tried a lot things with .includes, .references, .where, .not, .joins etc. but I don't get it.
Update 1
One suggested solution looked likes this:
def self.with_no_stats_created_at(date)
joins(:childs).where.not(childs: {created_at: date})
end
But this only works if the parent has already a child created in the past. The SQL should demonstrate the problem:
SELECT "parents".*
FROM "parents"
INNER JOIN "childs" ON "childs"."parent_id" = "parents"."id"
WHERE ("childs"."created_at" != $1) [["created_at", "2016-04-19"]]
Update 2
This solved the problem (suggested by #Ilya):
def self.with_no_childs_created_at(date)
preload(:childs).select {|p| p.childs.all? {|c| c.created_at != date }}
end
You can preload childs to avoid N+1 queries and process it like array:
def self.with_no_childs_created_at(date)
preload(:childs).select {|p| p.childs.all? {|c| c.created_at != date }}
end
You can do it entirely in the database, which is faster most of the time:
scope :without_children_at(date) = {
joins(:childs).where("DATE(created_at) != DATE(?)", date)
}

N-M relationship unknow attribute error with create method

Here are my migrations:
class CreateTests < ActiveRecord::Migration
def change
create_table :tests do |t|
t.string :value
t.timestamps
end
end
end
class CreateQuestions < ActiveRecord::Migration
def change
create_table :questions do |t|
t.string :title
t.timestamps
end
end
end
class CreateQuestionsTests < ActiveRecord::Migration
def change
create_table :questions_tests do |t|
t.integer :test_id
t.integer :question_id
t.timestamps
end
end
end
Now in the rails console I created a test object and a question object
test = Test.create(value: "10")
question = Question.create(title: "blablabla")
If now I do test.questions.create(question_id: question.id) I get the following error:
ActiveRecord::UnknownAttributeError: unknown attribute: question_id
How is that?
If you are using has_and_belongs_to_many relationship, you must to have relation table without id and stamps
class CreateQuestionsTests < ActiveRecord::Migration
def change
create_table :questions_tests, :id => false do |t|
t.integer :test_id
t.integer :question_id
end
end
end
I guess that you want to do a Rich association here, if so you should declare relations in your models like this :
Test.rb
class Test < ActiveRecord::Base
has_many :questions_tests
has_many :questions, :through => :questions_tests # here you tell rails that your Test model has many questions if you go through questions_tests
end
Question.rb
class Question < ActiveRecord::Base
has_many :questions_tests
has_many :tests, :through => :questions_tests # here you tell rails that your Question model has many tests if you go through questions_tests
end
QuestionTest.rb
class QuestionTest < ActiveRecord::Base
belongs_to :test
belongs_to :question
end
with this you can traverse the associations table (questions_tests) directly like you want : test.questions.create(question_id: question.id), and you have also this possibility :
test = Test.create(value: "10")
question = Question.create(title: "blablabla")
test.questions_tests << question # or question.questions_tests << test

Mass assign object_id to objects

I have two classes: Schedule and Interaction and they look the following:
class Schedule < ActiveRecord::Base
has_many :interactions
end
class Interaction < ActiveRecord::Base
attr_accessible :schedule_id
has_one :schedule
end
The migrations look like this:
class CreateSchedules < ActiveRecord::Migration
def change
create_table :schedules do |t|
t.timestamps
end
end
end
class CreateInteractions < ActiveRecord::Migration
def change
create_table :interactions do |t|
t.integer :schedule_id
t.timestamps
end
end
end
When I do this:
irb(main):003:0> interaction_high_1 = Interaction.create()
irb(main):003:0> interaction_high_2 = Interaction.create()
irb(main):003:0> interaction_high_3 = Interaction.create()
irb(main):003:0> interaction_high_4 = Interaction.create()
irb(main):003:0> interaction_high_5 = Interaction.create()
irb(main):003:0> schedule1 = Schedule.create(:name => "high1").interactions << interaction_high_1, interaction_high_2, interaction_high_3, interaction_high_4, interaction_high_5
only Interaction_high_1 gets the designated schedule_id and for the rest it's just nul
Can anybody tell me why this is and how I might fix it?
Thanks for an answer!!
You are creating the Interactions without associating them to a Schedule. Just appending them later won't do what you need. Do it like this instead:
schedule1 = Schedule.create(:name => "high1")
1...5.times do
schedule1.interactions.create
end
Also, change the :has_one in the Interaction model to a :belongs_to.

How to set a models attribute based on a parent model?

I want to set an attribute on a child model when its parent is changed
Here is an example:
create_table "children", :force => true do |t|
t.integer "parent_id"
t.string "parent_type"
t.integer "foo_id"
end
create_table "fathers", :force => true do |t|
t.integer "foo_id"
end
create_table "mothers", :force => true do |t|
t.integer "foo_id"
end
create_table "foos", :force => true do |t|
end
class Foo < ActiveRecord::Base
end
class Child < ActiveRecord::Base
belongs_to :parent, :polymorphic => true
belongs_to :foo
end
class Father < ActiveRecord::Base
belongs_to :foo
end
class Mother < ActiveRecord::Base
belongs_to :foo
end
Now, when I execute the following, I want child.foo_id to be set from parent:
foo = Foo.new {|foo| foo.id = 1}
parent = Father.new {|father| father.foo = foo}
child = Child.new
child.parent = parent
I need foo_id to be set right away, not in a before_validation callback or anything like that.
This is a simplified example, in the real case I have many more polymorphic types. I know this can be accomplished with an after_add callback on a has_many association on Father and Mother, but I'd rather not have to add a has_many association if possible since that requires me to add code in many more places. Is there a way to do this?
I don't clearly understand what you want to achieve.
May be this
parent = Parent.new(foo_id=>123456)
child = Child.new(:parent=>parent,:foo_id=>parent.foo_id)
if parent.save
child.save
end
or
parent = Parent.new(foo_id=>123456)
if parent.save
Child.create(:parent=>parent,:foo_id=>parent.foo_id)
end
not sure if this would work but maybe you could overwrite the setter for parent in the Child model
def parent=(p)
self.foo_id = p.foo_id
super(p)
end

Resources