I have a user model and a shout model. I am trying to have a user be associated with a shout. I did not make this association upon creation of the shouts table so I had to run a new migration. Below is my table, the models of each, and the output when from my console I run a command to try and find the user_id of a shout. Can you see what I am doing wrong?
schema:
create_table "shouts", force: :cascade do |t|
t.string "title"
t.text "description"
t.integer "user_id"
end
add_index "shouts", ["user_id"], name: "index_shouts_on_user_id"
create_table "users", force: :cascade do |t|
t.string "email", null: false
t.string "password_digest", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
end
User Model:
class User < ActiveRecord::Base
validates :email, presence: true, uniqueness: true
validates :password_digest, presence: true
has_many :shouts
end
Shout Model:
class Shout < ActiveRecord::Base
belongs_to :user
end
Shout Controller:
class ShoutsController < ApplicationController
def new
#new_shout = Shout.new
end
def create
#new_shout = Shout.new(shouts_params)
if #new_shout.user_id == nil
render new_shout_path
elsif #new_shout.save
redirect_to dashboard_path
else
render new_shout_path
end
end
private
def shouts_params
params.require(:shout).permit(:title, :description, :user_id)
end
end
Some test code:
> Shout.find(4)
> #<Shout id: 4, title: "four", description: "four", user_id: nil>
Creating an instance of user from the console, working:
> User.first.shouts.create(title: 'four', description: 'four')
>[["title", "four"], ["description", "four"], ["user_id", 1]
Migration file:
class AddUserRefToShouts < ActiveRecord::Migration
def change
add_reference :shouts, :user, index: true, foreign_key: true
end
end
Here are a couple admittedly hacky options (but they'll work) if you don't want to follow the approach suggested in the comments. You could pass the user_id as a hidden field so it'll be included in the params or you can expressly set it in the create action.
If you want to pass as a hidden field, on your shout form add:
<%= f.hidden_field :user_id, value: current_user.id %>
Alternatively, if you want to handle in your create action:
def create
#shout = Shout.new(shout_params)
#shout.user_id = current_user.id
#shout.save
end
Related
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
I'm trying to implement a User model with a 1-to-many association with articles. So I added a User.rb model with
class User < ApplicationRecord
has_many :articles
before_save { self.email = email.downcase }
validates :username, presence: true,
uniqueness: { case_sensitive: false },
length: { minimum: 3, maximum: 25 }
VALID_EMAIL_REGEX = /\A\S+#.+\.\S+\z/i
validates :email, presence: true,
uniqueness: { case_sensitive: false },
length: { maximum: 105 }, format: { with: VALID_EMAIL_REGEX }
end
Also added belongs_to: user in the Article model:
class Article < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
validates :title, presence: true, length: { minimum: 5 }
validates :text, presence: true, length: { minimum: 10, maximum: 400}
validates :user_id, presence: true
end
I ran this migration
rails generate migration add_user_id_to_articles
and this is my migration file
class AddUserIdToArticles < ActiveRecord::Migration[5.2]
def change
add_column :articles, :user_id, :integer
end
end
Here is the definition of my schema.rb:
ActiveRecord::Schema.define(version: 2019_02_26_124924) do
create_table "articles", force: :cascade do |t|
t.string "title"
t.text "text"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "comments", force: :cascade do |t|
t.string "commenter"
t.text "body"
t.integer "article_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["article_id"], name: "index_comments_on_article_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
And lastly my change function in the articles_controller.rb file:
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render 'new'
end
end
I ran rails db:migrate so that shouldn't be a problem. I saw some people on Stackoverflow say I should just add
#article.user_id = current_user.id
but already did that and nothing happened.
I keep getting this error:
NoMethodError (undefined method `user_id' for #)
Did you mean? user
and I don't know what else to try. I'm starting with Ruby on Rails so keep that in mind.
Thanks a lot for reading, hopefully somebody knows a fix.
First, make sure your migration actually runs through; then your schema.rb should include the user_id column too.
Then, try changing the line creating the new article to:
#article = current_user.articles.new(article_params)
Alternatively, you can set
#article.user = current_user
Inside your Articles controller, you'll need to add user_id into your permitted parameters.
def article_params
params.require(:article).permit(:title, :text, :user_id)
end
hey guys im working on a application where a devise user sign ups and logs in, Once the user logs in they can 'create a team' or 'join a team'. I have my associations set up like this
user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
validates_presence_of :phone, :city, :state, :street, :zip, presence: true, on: :create
belongs_to :team
end
team.rb
class Team < ApplicationRecord
has_many :users
end
and my tables are set up
schema.rb
create_table "teams", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "team_name"
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", null: false
t.datetime "updated_at", null: false
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "firstname"
t.integer "team_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
team_controller.rb
class TeamController < ApplicationController
before_action :authenticate_user!
def index
#team = current_user.team
end
def new_team
end
def create_team
#team = current_user.create_team(sanitize_team)
if #team.save
redirect_to team_root_path
else
render json: #team.errors.full_messages
end
end
def join_team
#teams = Team.all
end
def team
end
private
def sanitize_team
params.require(:team).permit(:team_name, :team_statement)
end
end
I want the users 'team_id' attribute to update with the teams id when they create a team. or when they join a team. Are my associations correct? how would i make this happen in the controller ?
Yes, associations are correct. You can do it better only by adding foreign key to your database schema. It can be done by generator rails g migration AddTeamToUsers team:references
More information about associations can be found here: https://guides.rubyonrails.org/association_basics.html
In controller you have to change only the whitelisting params to allow team_id. And you probably need to add to your form in view something like this:
<%= f.select :team_id, Team.all.map { |t| [t.team_name, t.id] } %>
Let's strip your code example down to the minimum required:
# app/models/team.rb
class Team < ApplicationRecord
has_many :users
end
# app/models/user.rb
class User < ApplicationRecord
belongs_to :team
end
# db/migrate/20181124230131_create_teams.rb
class CreateTeams < ActiveRecord::Migration[5.2]
def change
create_table :teams do |t|
t.string :team_name
t.timestamps
end
end
end
# db/migrate/20181124230136_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.belongs_to :team
t.timestamps
end
end
end
Then in your controller:
team = Team.where(team_name: 'foo').first_or_create!
team.users << current_user
Start by setting the association up as optional:
class User < ApplicationController
belongs_to :team, optional: true
end
Otherwise the validations on the user model will not let the user be saved without a team.
Then setup the teams resource:
# config/routes.rb
resources :teams do
post :join
end
post :join creates an additional POST /teams/:team_id/join route.
Then setup the controller:
class TeamsController
# ...
# GET /teams/new
def new
#team = Team.find
end
# POST /teams
def create
#team = Team.new(team_params)
if #team.save
unless current_user.team
current_user.update(team: #team)
end
redirect_to 'somewhere'
else
render :new
end
end
# ...
def join
#team = Team.find(params[:team_id])
if current_user.update(team: #team)
redirect_to #team, notice: 'Team joined'
else
redirect_to #team, error: 'Could not join team'
end
end
#
private
def team_params
params.require(:team).permit(:team_name, :team_statement)
end
end
Note that prefixing your action names is neither needed nor compatible with the "Rails way". Prefixing column names is also largely superfluous.
I'm getting an ActiveRecord::AssociationTypeMismatch error on my self join in Rails 5 that I can't figure out how to fix.
It's a simple rails app where a user can share a quote by an Artist (such as David Bowie) about another Artist (such as Lou Reed). So, a quote might look like this:
Quote
Topic: David Bowie
Content: "He was a master."
Speaker: Lou Reed
I have a Quote model and an Artist model and the Topics and Speakers are defined as self joins on the Artist model.
Here are the models:
class Artist < ApplicationRecord
default_scope -> { order(name: :asc) }
belongs_to :user
has_many :spoken_quotes, class_name: "Quote", foreign_key: :speaker_id
has_many :topic_quotes, class_name: "Quote", foreign_key: :topic_id
validates :user_id, presence: true
validates :name, presence: true, length: { maximum: 60 },
uniqueness: { case_sensitive: false }
end
class Quote < ApplicationRecord
default_scope -> { order(created_at: :desc) }
belongs_to :user
belongs_to :speaker, class_name: "Artist"
belongs_to :topic, class_name: "Artist"
validates :speaker, uniqueness: {scope: :topic}
validates :topic, uniqueness: {scope: :speaker}
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 1200 }
validates :source, presence: true, length: { maximum: 60 }
end
Here's the database schema:
create_table "artists", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id", "created_at"], name: "index_artists_on_user_id_and_created_at"
t.index ["user_id"], name: "index_artists_on_user_id"
end
create_table "quotes", force: :cascade do |t|
t.integer "user_id"
t.integer "artist_id"
t.text "content"
t.string "source"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["artist_id", "created_at"], name: "index_quotes_on_artist_id_and_created_at"
t.index ["artist_id"], name: "index_quotes_on_artist_id"
t.index ["user_id", "created_at"], name: "index_quotes_on_user_id_and_created_at"
t.index ["user_id"], name: "index_quotes_on_user_id"
end
Here's the relevant code from my Quotes Controller:
def create
#quote = current_user.quotes.build(quote_params)
if #quote.save
flash[:success] = "Quote created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
def quote_params
params.require(:quote).permit(:content, :source, :topic, :artist_id)
end
And the dropdown for the Topic of a Quote (which is an Artist) on the new Quote form:
<%= f.collection_select :topic, Artist.all, :id, :name %>
The dropdown looks fine and appears to be creating the association correctly, but when I submit the form I get the following error:
Artist(#70317289606580) expected, got "15" which is an instance of String(#70317259521760)
And the error message highlights the first line in the create action:
#quote = current_user.quotes.build(quote_params)
Am I defining my params wrong? What is wrong about my create action to cause this error. I can't seem to figure it out after researching it a bunch and trying various solutions.
Lee -
Your Quote and Artist models look OK. Your schema, however, is wrong. It should look like:
create_table "artists", force: :cascade do |t|
t.integer "user_id"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "quotes", force: :cascade do |t|
t.integer "user_id"
t.integer "speaker_id"
t.integer "topic_id"
t.text "content"
t.string "source"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Note speaker_id and topic_id instead of artist_id.
I'd need to see your stack trace to see what might be wrong with how you have other things set up.
BTW, have you fixed your params whitelist? This is wrong:
def quote_params
params.require(:quote).permit(:content, :source, :topic, :artist_id)
end
Since your params look like:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"7xXgP3T1ZyxVhnr9TtBxeuYSRLBiuX01JSkQ4m4rN9pBS1W0iW6TJtsS7KyvunpCIZFiFltmdEwZGIYqsnxbyw==", "quote"=>{"topic_id"=>"2", "speaker_id"=>"1", "content"=>"asdfadsf", "source"=>"http://fuzz.com"}, "commit"=>"Post"}
It should be:
def quote_params
params.require(:quote).permit(:content, :source, :topic_id, :speaker_id)
end
As a shot in the dark, try changing:
validates :speaker, uniqueness: {scope: :topic}
validates :topic, uniqueness: {scope: :speaker}
To:
validates :speaker_id, uniqueness: {scope: :topic_id}
validates :topic_id, uniqueness: {scope: :speaker_id}
I'll update with explanation if that's the problem.
Try to change your select with:
<%= f.collection_select :topic_id, Artist.all, :id, :name %>
and permit topic_id with on your controller:
params.require(:quote).permit(:content, :source, :topic_id, :artist_id)
You can pass on object as an argument for belongs_to association in rails c:
Quote.new(topic: Artist.first)
and Rails'll do rest, but you can't pass an object via http request, but instead you should pass an object's id.
Updated
I'm confused about how do you want to bind Quote with speaker(Artist) and topic(Artist) having the artist_id column on quotes table only? It seems you should have different columns to store these connections. And then you'll be able to use the name of the column for select's name.
I have a user model and a patient model. Patients are not users of the application. Users are essentially staff members who are creating patient records. In some situations, the user who creates the patient's record is also that patient's physician. In other cases, the patient's physician could be a separate user.
I want to save the user id of the patient's physician to the patient model rather than the user who happened to create the patient. The implementation I am imagining is that I will have a dropdown field in the form for the user to select the patient's physician, including the option to select themselves. How can I do this? Am I even thinking about this the right way? Here is my current implementation:
class Patient < ApplicationRecord
belongs_to :user
class User < ApplicationRecord
has_many :patients
Patients Controller
class PatientsController < ApplicationController
def new
#patient = current_user.patients.build
end
def create
#patient = current_user.patients.build(patient_params)
if #patient.save
flash[:success] = "Patient Created!"
redirect_to new_referral_request_path(patient_id: #patient.id)
else
Rails.logger.info(#patient.errors.inspect)
render 'patients/new'
end
end
private
def patient_params
params.require(:patient).permit(:age, :user_id, insurance_ids: [], gender_ids: [], concern_ids: [], race_ids: [])
end
end
patient's schema:
create_table "patients", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "age"
t.string "user_id"
t.index ["user_id"], name: "index_patients_on_user_id"
end
I have two roles: one for staff and one for clinician. Staff users would be the ones creating patients. A staff user who creates a patient record may or may not be that particular patient's physician.
class User < ApplicationRecord
self.inheritance_column = :role
enum role: { Staff: 0, Clinician: 1}
Just add physician relation to Patient model:
class Patient < ApplicationRecord
belongs_to :user
belongs_to :physician, class_name: 'User'
end
Then modify schema:
create_table "patients", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "age"
t.string "user_id"
t.integer "physician_id"
t.index ["user_id"], name: "index_patients_on_user_id"
t.index ["physician_id"], name: "index_patients_on_physician_id"
end
Hint: use integer for your ids fields, if your ids are numeric.
(Of course, its better to do this through migration, see this post if you don't know how).
Then permit physician_id in params:
def patient_params
params.require(:patient).permit(:age, :user_id, :physician_id, insurance_ids: [], gender_ids: [], concern_ids: [], race_ids: [])
end
And finally add dropdown list in the form:
<%= form_for(#patient) do |f| %>
<%= f.select :physician_id, User.all.map { |u| [u.name, u.id] } %>
...other fields...
<% end %>
Now you can call both patient.user and patient.physician (which can be equal).