Inheritance with ActiveRecord at the same time - ruby-on-rails

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')

Related

ActiveModel::MissingAttributeError (can't write unknown attribute `flights_count`):

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

How to inherit from another Rails migration?

I have a Rails migration for a simple User model:
class Users < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name, :default => :null
t.float :weight
t.datetime :recorded_at
t.timestamps
end
end
end
I would like to have a second table for the history of the user. It should have the same columns but another name, obviously. Also it should reference the user table.
require_relative '20130718143019_create_history.rb'
class History < Users
def change
create_table :history do |t|
t.references :user
# ...?
end
end
end
How can use inheritence to avoid copying all the migration configuration?
After leaving the keyboard tomatoes fell off my eyes and it was clear how I can set this up:
class Users < ActiveRecord::Migration
def change
create_table :users do |t|
prepare_columns(t)
end
end
protected
def prepare_columns(t)
t.string :name, :default => :null
t.float :weight
t.datetime :recorded_at
t.timestamps
end
end
...
require_relative '20130718143019_create_history.rb'
class History < Users
def change
create_table :history do |t|
t.references :user
prepare_columns(t)
end
end
end

Do I need to create an assocation table for a :has_many :through association?

I'm trying to use a :has_many :through type association, but I'm getting the following error:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: work_units.developer_id:
Many other posts about this sort of thing have just had spelling mistakes, but I've checked mine.
class Developer < ActiveRecord::Base
attr_accessible :skype_name, :language_ids, :user_attributes
has_many :work_units
has_many :projects, :through => :work_units
...
end
class Project < ActiveRecord::Base
attr_accessible :complete, :description, :finalised, :price
has_many :work_units
has_many :developers, :through => :work_units
...
end
class WorkUnit < ActiveRecord::Base
attr_accessible :hours_worked
belongs_to :project
belongs_to :developer
end
I've run db:migrate and it didn't complain. I did make a mistake and had to rollback the db then re-migrate, but I think I did it right. I use the annotate gem and it doesn't show any of the relationship ids I'd expect. So, do I need to create a WorkUnits table or am I missing something? The rails guide didn't mention manually making tables.
Edit
Here's the migration I used to create the WorkUnit model and stuff:
class CreateWorkUnits < ActiveRecord::Migration
def change
create_table :work_units do |t|
t.integer :hours_worked, :default => 0
t.timestamps
end
end
end
Edit 2
Snippets from my schema.rb:
create_table "work_units", :force => true do |t|
t.integer "hours_worked", :default => 0
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "projects", :force => true do |t|
t.string "description"
t.decimal "price", :precision => 8, :scale => 2
t.boolean "complete", :default => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
Similarly for :developers. So, why doesn't my migration add the association information for me?
Your WorkUnit migration should look like this:
class CreateWorkUnits < ActiveRecord::Migration
def change
create_table :work_units do |t|
t.integer :hours_worked, :default => 0
t.references :developer
t.references :project
t.timestamps
end
add_index :work_units, :developer_id
add_index :work_units, :project_id
end
end
You need to add the foreign keys to your work_units table.
class CreateWorkUnits < ActiveRecord::Migration
def change
create_table :work_units do |t|
t.integer :hours_worked, :default => 0
t.integer :project_id, null: false
t.integer :developer_id, null: false
t.timestamps
end
add_index :work_units, :project_id
add_index :work_units, :developer_id
end
end
Another way:
class CreateWorkUnits < ActiveRecord::Migration
def change
create_table :work_units do |t|
t.integer :hours_worked, :default => 0
t.belongs_to :project
t.belongs_to :developer
t.timestamps
end
add_index :work_units, :project_id
add_index :work_units, :developer_id
end
end
You can also define these fields when generating your model, then they'll be added to the migration automatically as show in the second snippet.
$ rails g model WorkUnit hours_worked:integer project:belongs_to developer:belongs_to
Hope that helps.
A table for WorkUnit needs to exist, whether that means it migration was automatically generated via scaffolding or if the migration was manually written by you.
If you don't have a migration yet that creates that table, you'll need to create that migration because the table does need to exist.
You do need a work_units table with a project_id and developer_id column.
Have a look at http://xyzpub.com/en/ruby-on-rails/3.2/activerecord_datenbank_anlegen.html if you don't know how to create a table.

Associated model isn't being created

I'm a complete rails newbie, so forgive me if this is trivial.
I have an Inventory model that either belongs_to a Store or a Traveling Party:
class Inventory < ActiveRecord::Base
belongs_to :trader, :polymorphic => true
end
class Store < ActiveRecord::Base
has_one :inventory, :as => :trader, :dependent => :destroy
end
class TravelingParty < ActiveRecord::Base
has_many :travelers, :dependent => :destroy
has_one :inventory, :as => :trader, :dependent => :destroy
validates_presence_of :speed, :ration, :position
accepts_nested_attributes_for :travelers, :reject_if => :reject_traveler, :allow_destroy => true
accepts_nested_attributes_for :inventory, :allow_destroy => true
def reject_traveler(attributes)
attributes['profession'].blank? and attributes['name'].blank?
end
end
I created a form that, when submitted, creates a Traveling Party and a number of Travelers. Now I'd like the form to also create an Inventory and initialize all the variables to 0. I know the following doesn't address variable initialization, but it doesn't even seem to put a row of null values into the Inventory database table.
class TravelingPartiesController < ApplicationController
def new
#traveling_party = TravelingParty.new
5.times do
traveler = #traveling_party.travelers.build
end
#inventory = #traveling_party.inventory.create
end
def create
#traveling_party = TravelingParty.new(params[:traveling_party])
if #traveling_party.save
flash[:notice] = "Successfully created traveling party and travelers."
redirect_to '/store/'
else
flash[:error] = "Please specify a leader."
redirect_to '/new/'
end
end
def index
end
end
For good measure, here is what the database schema looks like:
ActiveRecord::Schema.define(:version => 20111018224808) do
create_table "inventories", :force => true do |t|
t.integer "ox"
t.integer "food"
t.integer "clothing"
t.integer "ammunition"
t.integer "money"
t.integer "axle"
t.integer "wheel"
t.integer "tongue"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "trader_id"
end
create_table "stores", :force => true do |t|
t.string "name"
t.integer "location"
t.integer "priceScale"
t.datetime "created_at"
t.datetime "updated_at"
end
# Could not dump table "travelers" because of following StandardError
# Unknown type 'relations' for column 'traveling_party_id'
create_table "traveling_parties", :force => true do |t|
t.integer "speed"
t.integer "ration"
t.integer "position"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Is there a reason the inventory database table isn't being affected at all? And once that works, what would be the best way to initialize a traveling_party.inventory to have all 0s? (i.e., values for ox, food, clothing, etc).
This may because your inventories table does not include a 'trader_type'. This is required for polymorphic associations.
create_table "inventories", :force => true do |t|
t.integer "trader_id"
t.string "trader_type"
end
Edit:
To set all the values initially to 0, the best way would be to put a default value onto the fields in the table. (If you want it to always be initialized to 0 if there is no other option, otherwise they will default to nil)
I believe you can create a migration with
change_table(:inventories) do |t|
t.change :ox, :integer, :default => 0
end

STI and has_many association with "type" column as Key

I am using Single Table Inheritance for managing different types of projects.
I decided to store some information associated with each project type. So i created new table "project_types" with "model_type" field as primary key. Primary key values are values of "type" field of "projects" table. Problem: When i trying to get associated with Project object ProjectTypes object it always returns null.
>> p = Project.find(:first)
=> #<SiteDesign id: 1, type: "SiteDesign", name: "1", description: "dddd", concept: "d", client_id: 40, created_at: "2009-10-15 08:17:45", updated_at: "2009-10-15 08:17:45">
>> p.project_type
=> nil
Getting projects associated with ProjectTypes project is OK. Is there way to make it works properly?
Models:
class Project < ActiveRecord::Base
belongs_to :project_type, :class_name => "ProjectTypes", :foreign_key => "model_name"
end
class SiteDesign < Project
end
class TechDesign < Project
end
class ProjectTypes < ActiveRecord::Base
self.primary_key = "model_name"
has_many :projects, :class_name => "Project", :foreign_key => "type"
end
Migrations:
class CreateProjectTypes < ActiveRecord::Migration
def self.up
create_table :project_types, :id => false do |t|
t.string :model_name , :null => false
t.string :name, :null => false
t.text :description
t.timestamps
end
add_index :project_types, :model_name, :unique => true
#all project types that are used.
models_names = {"SiteDesign" => "Site design",
"TechDesign" => "Tech design"}
#key for model_name and value for name
models_names.each do |key,value|
p = ProjectTypes.new();
p.model_name = key
p.name = value
p.save
end
end
def self.down
drop_table :project_types
end
end
class CreateProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.string :type
t.string :name
t.text :description
t.text :concept
t.integer :client_id
t.timestamps
end
end
def self.down
drop_table :projects
end
end
Not surprising you're getting problems. By moving from a pure STI system to your current system you are horribly breaking the patterns you are using by intermingling parts of one with parts of another.
I'd personally go for something like:
class Project < ActiveRecord::Base
attr_readonly(:project_type)
belongs_to :project_type
before_create :set_project_type
def set_project_type()
project_type = ProjectType.find_by_model_name(this.class)
end
end
class SiteProject < Project
end
class TechProject < Project
end
class ProjectType < ActiveRecord::Base
has_many :projects
end
with migrations:
class CreateProjectTypes < ActiveRecord::Migration
def self.up
create_table :project_types do |t|
t.string :model_name , :null => false
t.string :name, :null => false
t.text :description
t.timestamps
end
add_index :project_types, :model_name, :unique => true
#all project types that are used.
models_names = {"SiteDesign" => "Site design",
"TechDesign" => "Tech design"}
#key for model_name and value for name
models_names.each do |key,value|
p = ProjectTypes.new();
p.model_name = key
p.name = value
p.save
end
end
def self.down
drop_table :project_types
end
end
class CreateProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.string :type
t.references :project_type, :null => false
t.text :description
t.text :concept
t.integer :client_id
t.timestamps
end
end
def self.down
drop_table :projects
end
end
It just cleans things up and it also helps clarify what you're doing. Your 'ProjectType' table is purely for extra data, your inheritance tree still exists. I've also thrown in some checks to make sure your project type is always set (and correctly, based on the model name) and stops you from changing project type once it's been saved by making the attribute read only.

Resources