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
Related
migrate file exists but no model for rails application.There are user and book model.I created join table between user and book model.
I write console : rails g migration CreateJoinTableBooksUsers books users
rake:db migrate
**schema.rb**
create_table "books", force: :cascade do |t|
t.string "title"
t.string "author"
t.integer "page_count"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.boolean "status"
t.string "user_id"
t.boolean "barter_status"
end
create_table "books_users", id: false, force: :cascade do |t|
t.bigint "book_id", null: false
t.bigint "user_id", null: false
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", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "username"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["username"], name: "index_users_on_username", unique: true
end
add_foreign_key "comments", "books"
add_foreign_key "comments", "users"
end
**migrate**
class CreateJoinTableBooksUsers < ActiveRecord::Migration[6.0]
def change
create_join_table :books, :users do |t|
t.index [:book_id, :user_id]
t.index [:user_id, :book_id]
end
end
end
A migration creates the tables in the database but doesn't create anything else.
But, for a true join table, you don't need a model:
https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
# app/models/user.rb
class User < ApplicationRecord
has_and_belongs_to_many :books
end
# app/models/books.rb
class Book < ApplicationRecord
has_and_belongs_to_many :users
end
IF you need scopes, callbacks, or methods on BooksUsers, you can use the has_many :through option:
https://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many
# app/models/user.rb
class User < ApplicationRecord
has_many :books_users
has_many :books, through: :books_users
end
# app/models/books.rb
class Book < ApplicationRecord
has_many :books_users
has_many :users, through: :books_users
end
In this case, you'll need to generate a model:
rails generate model BooksUsers
I don't know if anyone can help me as this is a little odd.
I have a moderately complicated set of relations in a database, which roughly has a structure something like this:
Delivery Director has Account Directors has Pods has Account Managers has Companies.
Therefore, Delivery Directors should have Companies.
This whole structure is working, all the way down to Companies, and then suddenly stops. The Delivery Director returns [] on companies.
class DeliveryDirector < User
has_many :account_directors
has_many :pods, through: :account_directors
has_many :account_managers, through: :pods
has_many :companies, through: :account_managers
end
And the company class looks like:
class Company < ApplicationRecord
belongs_to :account_manager
has_one :pod, through: :account_manager
has_one :account_director, through: :pod
has_one :delivery_director, through: :account_manager
end
Like I say, everything is working. The Company even has a Delivery Director! It's just the DeliveryDirector.all.first.companies returns [].
If anyone could even just point me in the right direction, that would be great. There is no error message, and nothing seems to be going wrong at all.
Oh, in case it helps, here is the SQL generated by the query:
Company Load (0.7ms) SELECT "companies".* FROM "companies" INNER JOIN "users" ON "companies"."account_manager_id" = "users"."id" INNER JOIN "pods" ON "users"."pod_id" = "pods"."id" INNER JOIN "users" "account_directors_companies" ON "pods"."account_director_id" = "account_directors_companies"."id" WHERE "users"."type" IN ('AccountDirector') AND "account_directors_companies"."delivery_director_id" = $1 [["delivery_director_id", 2]]
Thanks!
Edit: Request for other models, schema
Pod:
class Pod < ApplicationRecord
belongs_to :account_director
has_many :account_managers
has_many :companies, through: :account_managers
end
Account Manager:
class AccountManager < User
belongs_to :pod
has_one :account_director, through: :pod
has_one :delivery_director, through: :account_director
has_many :companies
end
Schema:
ActiveRecord::Schema.define(version: 2018_10_19_141416) do
enable_extension "plpgsql"
create_table "companies", force: :cascade do |t|
t.string "name"
t.string "officelocation"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "campaign_link"
t.string "company_logo"
t.string "website"
t.integer "account_manager_id"
end
create_table "images", force: :cascade do |t|
t.string "name"
t.string "location"
t.bigint "company_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["company_id"], name: "index_images_on_company_id"
end
create_table "jwt_blacklist", id: :serial, force: :cascade do |t|
t.string "jti", null: false
t.index ["jti"], name: "index_jwt_blacklist_on_jti"
end
create_table "markets", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "markets_users", id: false, force: :cascade do |t|
t.bigint "market_id", null: false
t.bigint "talent_manager_id", null: false
end
create_table "pods", force: :cascade do |t|
t.string "name"
t.integer "account_director_id"
t.integer "delivery_director_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "table_campaigns", force: :cascade do |t|
t.bigint "user_id"
t.bigint "company_id"
t.string "name"
t.integer "iterations"
t.integer "interviews"
t.index ["company_id"], name:
"index_table_campaigns_on_company_id"
t.index ["user_id"], name: "index_table_campaigns_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "jobtitle"
t.string "linkedin"
t.string "office"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "type"
t.integer "team_lead_id"
t.integer "delivery_director_id"
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.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.bigint "pod_id"
t.string "user_photo"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["pod_id"], name: "index_users_on_pod_id"
t.index ["reset_password_token"], name:
"index_users_on_reset_password_token", unique: true
end
add_foreign_key "table_campaigns", "companies"
add_foreign_key "table_campaigns", "users"
end
And now adding Account Director:
class AccountDirector < User
belongs_to :delivery_director
has_one :pod
has_many :account_managers, through: :pod
has_many :companies, through: :account_managers
end
You use Single Table Inheritance. 3 of your models: DeliveryDirector, AccountDirector and AccountManager are descendants of User model. When doing shallow request it works fine, but when you construct requests which involve all 3 models Rails cannot build the right query. If you try to project how to find all companies of a delivery director in terms of database, you will come to the chain of tables:
companies -> users (account managers) -> pods -> users (account directors) -> users (delivery directors)
The SQL query for your request may look like:
SELECT companies.* FROM companies
INNER JOIN users AS account_managers ON companies.account_manager_id = account_managers.id
INNER JOIN pods ON account_managers.pod_id = pods.id
INNER JOIN users AS account_directors ON pods.account_director_id = account_directors.id
INNER JOIN users AS delivery_directors ON account_directors.delivery_director_id = delivery_directors.id
WHERE delivery_directors.id = 2;
but obviously, Rails does not add AS clause to the query to distinguish user roles and uses users table name instead. To filter results it uses condition "users"."type" IN ('AccountDirector') which is not enough in your case, because in your query there should be also AccountManager (as a link between pods and companies).
Another sign that Rails is confused: despite correct association in your models Rails tries to use table account_directors_companies which you obviously do not have.
I would recommend to review your database schema and extract user roles and relationship between them into separate substances.
UPDATE:
For example, user authentication/registration data can be left in users table as it is now. All info about user roles and their relations can be moved to extra tables, backed up by models:
class DeliveryDirector < ApplicationRecord
belongs_to :user
has_many :account_directors
has_many :pods, through: :account_directors
has_many :account_managers, through: :pods
has_many :companies, through: :account_managers
end
class AccountDirector < ApplicationRecord
belongs_to :user
has_one :pod
has_many :account_managers, through: :pod
has_many :companies, through: :account_managers
end
class AccountManager < ApplicationRecord
belongs_to :user
has_many :companies
end
Each of these models has their own table in the database.
Thus, to fetch companies of delivery director you could call:
DeliveryDirector.find_by(user_id: user_id).companies
In my app User can have many Companies and vice versa. In Accounts table id of User and id of its Company is stored.
I want to find all Users who belong to Companies, which belong to current_user. Let's assume that the current_user is like master User (not Admin, as that would be system Admin) of those companies.
How do I do this? My guess is to do it with Arel, but then how should it look in Model, Controller, View? Many thanks for any help. I'm on Rails 5.
models/user.rb
class User < ApplicationRecord
has_many :accounts, dependent: :destroy
has_many :companies, through: :accounts
models/account.rb
class Account < ApplicationRecord
belongs_to :company
belongs_to :user
accepts_nested_attributes_for :company, :user
models/company.rb
class Company < ApplicationRecord
has_many :accounts, dependent: :destroy
has_many :users, through: :accounts
accepts_nested_attributes_for :accounts, :users
My schema.rb looks like this:
create_table "accounts", force: :cascade do |t|
t.integer "company_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["company_id"], name: "index_accounts_on_company_id"
t.index ["user_id"], name: "index_accounts_on_user_id"
end
create_table "companies", force: :cascade do |t|
t.string "name"
t.string "legal_name"
t.string "reg_number"
t.string "address"
t.string "bank_acc"
t.string "description"
t.string "website"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "role", default: 0
t.integer "currency", default: 0
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "remember_digest"
t.boolean "admin", default: false
t.index ["email"], name: "index_users_on_email", unique: true
end
You can find current user's companies, and eager load users who belong to those companies
#companies = current_user.companies.includes(:users)
To list all users(may be in a view), loop through #companies and all its users
#companies.each do |company|
company.users.each do |user|
puts user.name
end
end
Or use map/collect to assign them to a variable.
#users = #companies.map(&:users).flatten
Iv'e gotten myself into a bit of a brain mess up these past two days. I'd like to be able to allow my users to create a campaign (same concept as surveys), it will allow them to request certain data they wish such as an email address. This will then allow the person completing the form to proceed and receive a download link after entering an email. The email entered should be stored for the person who created the campaign to view.
Iv'e taken the approach with nested forms, however I ran into the trouble of allowing emails to be entered and saved for the campaign creator to view.
Any help is appreciated, thanks.
campaign.rb model
class Campaign < ActiveRecord::Base
belongs_to :user
has_many :queries
accepts_nested_attributes_for :queries
end
query.rb model
class Query < ActiveRecord::Base
belongs_to :campaign
has_many :results
end
result.rb model
class Result < ActiveRecord::Base
attr_accessible :content, :email, :query_id
belongs_to :query
end
schema.rb
create_table "campaigns", force: :cascade do |t|
t.string "title"
t.text "description"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "campaigns", ["user_id"], name: "index_campaigns_on_user_id", using: :btree
create_table "queries", force: :cascade do |t|
t.integer "campaign_id"
t.text "content"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "queries", ["campaign_id"], name: "index_queries_on_campaign_id", using: :btree
create_table "results", force: :cascade do |t|
t.integer "query_id"
t.text "content"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "results", ["query_id"], name: "index_results_on_query_id", using: :btree
Part of campaign_controller.rb
private
# Use callbacks to share common setup or constraints between actions.
def set_campaign
#campaign = Campaign.find(params[:id])
end
def campaign_params
params.require(:campaign).permit(:title, :description, :queries_attributes)
end
def query_params
params.require(:query).permit(:content, :email, :campaign_id)
end
Hello I do have this two models and I would like to check that my model associations are working the way it should trough rails console.
I am not able to do the association work. The relationship is the following:
One Event has one rule and one rule belongs to one event. It could not be a rule without an event and it could not be a event without a rule.
Any idea how to test this with rails console?
MODEL 1:
class Event < ActiveRecord::Base
has_and_belongs_to_many :users
has_one :rule
has_many :grand_prixes
belongs_to :eventable, polymorphic: :true
end
MODEL 2
class Rule < ActiveRecord::Base
belongs_to :events
end
Rules' Schema:
create_table "rules", force: :cascade do |t|
t.boolean "abs"
t.boolean "tc"
t.boolean "allow_auto_clutch"
t.boolean "allow_sc"
t.boolean "allow_throttle_blip"
t.boolean "dynamic_track"
t.integer "damage_mult"
t.integer "fuel_rate"
t.integer "tyre_wear_rate"
t.integer "quali_percentage"
t.integer "min_valid_laps"
t.integer "event_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "rules", ["event_id"], name: "index_rules_on_event_id"
Events' Schema:
create_table "events", force: :cascade do |t|
t.string "event_type"
t.string "name", null: false
t.datetime "starting_date"
t.datetime "ending_date"
t.integer "eventable_id"
t.string "eventable_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "events", ["eventable_type", "eventable_id"], name: "index_events_on_eventable_type_and_eventable_id"
Thanks in advance.
I think your belongs_to :events should be singular to follow the rails convention :
class Rule < ActiveRecord::Base
belongs_to :event
end
The conventional name of a relation is always singular for belongs_to and has_one, and always plural for has_many.
Related documentation : http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference
EDIT : There much left to say
You wrote :
ev = Event.create(:name "test1").save
rule = Rule.create.save
create is already a new followed by a save. No need to save afterwards.
the syntax key: value is something very common in ruby, and should be well understood : you're actually writing a hash, equivalent to {:key => value}, but the syntax allows you to write key: value ONLY IF your key is a Symbol.
the columns eventable_type and eventable_id should be in the table rules, who is hosting the polymorphic relation with eventable things. Event should not have these columns, and event_id should not exist at all in rules.
Here's an example of what you can write in your console to create an Event and a Rule :
ev = Event.create(name: "test1")
rule = Rule.create(abs: true, event: ev)
Change your code:
class Rule < ActiveRecord::Base
belongs_to :event
end
With belongs_to you should use singular like event not events.
In console you can check association like:
Event.first.rule if Event.first.present?
For more details you should go through http://guides.rubyonrails.org/association_basics.html documentation.
Current code:
class Rule < ActiveRecord::Base
belongs_to :event
end
class Event < ActiveRecord::Base
has_and_belongs_to_many :users
has_one :rule
has_many :grand_prixes
belongs_to :eventable, polymorphic: :true
end
SCHEMA:
create_table "rules", force: :cascade do |t|
t.boolean "abs"
t.boolean "tc"
t.boolean "allow_auto_clutch"
t.boolean "allow_sc"
t.boolean "allow_throttle_blip"
t.boolean "dynamic_track"
t.integer "damage_mult"
t.integer "fuel_rate"
t.integer "tyre_wear_rate"
t.integer "quali_percentage"
t.integer "min_valid_laps"
t.integer "event_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "rules", ["event_id"], name: "index_rules_on_event_id", unique: true
create_table "events", force: :cascade do |t|
t.string "event_type"
t.string "name", null: false
t.datetime "starting_date"
t.datetime "ending_date"
t.integer "eventable_id"
t.string "eventable_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "events", ["eventable_type", "eventable_id"], name: "index_events_on_eventable_type_and_eventable_id"
Tested on console:
ev = Event.create(:name "test1").save
rule = Rule.create.save
No idea how to link it both through console.