I'm trying to seed an application database, but I'm getting the following error:
ActiveRecord::AssociationTypeMismatch: Role(#97332160) expected,
got Fixnum(#76482890)
Here is the part of my schema.rb concerning the two table related to the problem:
create_table "roles", force: :cascade do |t|
t.string "role", limit: 50, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "first_name", limit: 100, null: false
t.string "surname", limit: 100, null: false
t.string "email", limit: 255
t.integer "role_id", limit: 11
t.string "password", limit: 150
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "users", ["first_name"], name: "index_users_on_first_name", using: :btree
add_index "users", ["role_id"], name: "index_users_on_role_id", using: :btree
add_index "users", ["surname"], name: "index_users_on_surname", using: :btree
add_foreign_key "users", "roles"
And here are my seeds.rb commands:
rl = Role.create(role: "root")
User.create(first_name: "Edvaldo", surname: "Silva de Almeida Júnior", email: "edvaldo#my.domain.com", role_id: rl.id, password: "my_password")
rl = Role.create(role: "admin")
User.create(first_name: "Daiely", surname: "Fanchin", email: "daiely#my.domain.com", role_id: rl.id, password: "other_password")
rl = Role.create(role: "user")
User.create(first_name: "César", surname: "Silva", email: "cesar#my.domain.com", role_id: rl.id, password: "yet_other_password")
I found a question where the accepted answer suggested I should do something like:
rl = Role.create(role: "root")
User.create(first_name: "Edvaldo", surname: "Silva de Almeida Júnior", email: "edvaldo#my.domain.com", role_id: Role.find(rl.id), password: "my_password")
I tried that, but got the same error! Besides, I'm using the role_id as association, not the role itself. So, as far as I'm concerned, it should receive a Fixnum, not a Role.
add_foreign_key "users", "roles" this will add role_id column to users no required to separately write t.integer "role_id", limit: 11
there should be some associations
class User < ActiveRecord::Base
belongs_to :role
end
and
create_table "users", force: :cascade do |t|
t.string "first_name", limit: 100, null: false
t.string "surname", limit: 100, null: false
t.string "email", limit: 255
t.reference "role", index: true
t.string "password", limit: 150
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "users", "roles", column: :role_id
hope it might solve it.
I had added a method set_default_role to my User model.
class User < ActiveRecord::Base
has_one :role
after_initialize :set_default_role, :if => :new_record?
def set_default_role
if User.count == 0
self.role ||= 1
else
self.role ||= 2
end
end
end
But this method was wrong and as soon as I rewrote it as
def set_default_role
if User.count == 0
self.role_id ||= 1
else
self.role_id ||= 2
end
end
my seeding worked fine.
So, the problem was the fact I was tryind (inside this method) to set a Role to a Fixnum.
This serves as an advice to Rails newbies: Seeding is not just forcing something to your database. It is a full evaluation of your model and sometimes an error may be caused by the execution of some method, not just by the values you try to load.
The problem you have is simple:
role_id: Role.find(rl.id)
.find() returns a Role object.
Your create method is expecting a foreign_key (integer) (role_id: [int]).
The simple fix would be:
User.create(... role_id: Role.find(rl.id).id ...)
.. or ...
User.create(... role_id: rl.id ...)
... or even better ...
User.create(... role: rl ...)
--
Fix
The real fix would either to set a default in your database, or manually set the role_id, as you have in your submitted answer.
I would strong recommend using the database default because then it doesn't matter what happens in your app - your database will always "default" to the role you set:
$ rails g migration SetDefaultForRole
$ db/migrate/set_default_for_role_____.rb
class SetDefaultForRole < ActiveRecord::Migration
def change
change_column :users, :role_id, :integer, default: 0
end
end
$ rake db:migrate
The downside to this would be that this integer default would be set (unless you changed your db again). Whilst not a problem in itself, it would create an antipattern for your Role model (IE you can create any role, as long as it's after the initial one you made).
--
The other way would be to set the Role for the user.
You have this already; you could clean it up:
#app/models/user.rb
class User < ActiveRecord::Base
before_create :set_role, if: Proc.new { |m| m.role_id.blank? }
private
def set_role
self.role_id = "1" if User.count == 0
end
end
Related
When I seed my database, I get this error: unknown attribute 'user' for Post.
My schema looks like this:
create_table "posts", force: :cascade do |t|
t.string "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "collection_id"
t.index ["collection_id"], name: "index_posts_on_collection_id"
t.index ["user_id"], name: "index_posts_on_user_id"
end
My seeds.rb file looks like this:
require 'faker'
10.times do |user|
User.create!(
name: Faker::Name.name,
birthday: '2019-09-04',
skin_type: 'Dry',
email: Faker::Internet.email,
password:Faker::Internet.password
)
end
users = User.all
puts "Users seeded"
50.times do
Post.create!(
user: users.sample,
body: Faker::Lorem.paragraphs
)
end
My creating Post migration file looks like this:
class CreatePosts < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
t.string :body
t.timestamps
end
end
end
In my seeds.rb file, Post.create! cannot access the user attribute. Is this because the user attribute is not included in the CreatePosts migration file? Shouldn't this not matter since within the schema, posts and user_id are connected?
Based on your discussion on the comments, you need to add user association in the post model:
class Post < ApplicationRecord
belongs_to :user
end
I have 3 models, Challenge, Pun, User (managed by Clearance gem)
A User can create a Challenge. A Challenge contains many puns. A User can also create a Pun.
Everything is fine until I set a Pun to belong_to a User, then suddenly Puns are no longer saved.
class User < ApplicationRecord
include Clearance::User
has_many :challenges
has_many :puns
end
class Challenge < ApplicationRecord
has_many :puns, :dependent => :delete_all
belongs_to :user
end
class Pun < ApplicationRecord
belongs_to :challenge
belongs_to :user
end
In my PunController I have tried to establish the current_user id
def create
#pun = #challenge.puns.create(pun_params)
#pun.user_id = current_user.id if current_user
redirect_to #challenge
end
private
def set_challenge
#challenge = Challenge.find(params[:challenge_id])
end
def pun_params
params[:pun].permit(:pun_text,:user_id)
end
What am I doing wrong? I'm trying to keep it as simple as possible, but seems like Users don't want to be associated with more than one thing, particularly if nested. Is this a Clearance issue?
DB setup:
create_table "challenges", force: :cascade do |t|
t.text "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "start_time"
t.datetime "end_time"
t.bigint "user_id"
t.index ["user_id"], name: "index_challenges_on_user_id"
end
create_table "puns", force: :cascade do |t|
t.text "pun_text"
t.bigint "challenge_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id"
t.index ["challenge_id"], name: "index_puns_on_challenge_id"
t.index ["user_id"], name: "index_puns_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "tagline"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "encrypted_password", limit: 128
t.string "confirmation_token", limit: 128
t.string "remember_token", limit: 128
t.index ["email"], name: "index_users_on_email"
t.index ["remember_token"], name: "index_users_on_remember_token"
end
Well in you currrent code you don't save user_id after setting it. And if you do not expect creation to fail you can do "create!".
So you can try:
def create
#challenge.puns.create!(pun_params.merge(user_id: current_user.id))
redirect_to #challenge
end
You can do this using simply hidden_field like in the form
<%= object.hidden_field :user_id, value: current_user.id %>
it won't work without user session because the relationship does not optional, and remove this line from the controller
#pun.user_id = current_user.id if current_user
and redirect
redirect_to #pun
it will work
I am really stuck with dealing with querys from multiple tables in rails. If this is my controller:
def show
#user = User.find(params[:id])
#entry = Entry.joins(:event, :user).where(users: { id: '2' })
#event = Event.find(1)
end
and this is my models
class Entry < ApplicationRecord
belongs_to :user
belongs_to :event
class User < ApplicationRecord
has_many :entries, dependent: :destroy
class Event < ApplicationRecord
has_many :entries, dependent: :destroy
The query runs happily in rails console and returns the records but I don't know how to access specific units like entry.course in my embeded ruby.
Update
To clarify On a page I woulk like to output somthing for all the entries assosiated with the user logged in to the page.
eventName eventLocation eventDate course siCard
The schema:
ActiveRecord::Schema.define(version: 20171204183458) do
# These are extensions that must be enabled in order to support this
database
enable_extension "plpgsql"
create_table "entries", force: :cascade do |t|
t.string "course"
t.string "siCard"
t.bigint "user_id"
t.bigint "event_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["event_id"], name: "index_entries_on_event_id"
t.index ["user_id", "created_at"], name:
"index_entries_on_user_id_and_created_at"
t.index ["user_id"], name: "index_entries_on_user_id"
end
create_table "events", force: :cascade do |t|
t.string "eventName"
t.string "location"
t.date "eventDate"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "eventLink"
t.string "eventCoordinates"
t.index ["eventName"], name: "index_events_on_eventName", unique: true
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 "firstName"
t.string "remember_digest"
t.boolean "admin"
t.index ["email"], name: "index_users_on_email", unique: true
end
add_foreign_key "entries", "events"
add_foreign_key "entries", "users"
end
You can find it in 2 ways
1)
#entries = Entry.includes(:user,:event).where(user: { id: (params[:id]) })
2)
#entries = User.includes(entries: :event).find(params[:id]).entries
Than in the loop of entries you can access value course of particular entry
e.g.
#entries.each do |entry|
puts "#{entry.course}"
puts "#{entry.event&.name}"
end
Why are you using such a complicated query with joins? If you are looking for the Entrys of #user just use #user.entries. ActiveRecord knows from the has_many how to deal with that.
If you posted a simplified example, you should try to find your Entry with something like .where(user_id: user.id) as long as id is the foreign key. With that you don't need that join, too.
You could confine with one query using
eager_load
def show
#user = User.eager_load(:entries => [:event]).where(users: {id: params[:id]})
end
According to your models you can access entries though user.
#user.entries
#user.entries.map{|entry| entry.course}
Also you can access event (which belongs to entry) though entry.
#user.entries[1].event
#user.entries.map{|entry| entry.event}
I want to update the status in the winners table into trues if a user with same mail id is signed up. I'm stuck with active record.
schema.rb
create_table "winners", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "phonenumber"
t.boolean "status", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
table 2
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "nickname"
t.string "image"
t.integer "score", default: 0
end
model/user.rb
after_create :update_status
def update_status
#if self[:email] = #any mail in winners table
#update the corresponding winners row with status.
if Winners.where(email = :email)
update attributes
end
How to make such queries with active record?
If you want to do it with an after_create hook in your model/user.rb:
after_create :update_status
def update_status
Winner.where(:email => self.email).each do |winner|
winner.status = true
winner.save
end
end
You might argue updating winners based on a user signing up should be a controller task, not the models though. I am a friend of explicitly calling stuff in your controller, instead of letting it happen automagically. That's more a thing of personal taste though, this should be perfectly fine.
You can try this, I hope this will help you.
In model/user.rb
after_create :update_status
def update_status
#update the corresponding winners row with status.
winner = Winner.where("email = ?", self.email).first
unless winner.blank
winner.status = true # Or false
winner.save! # Or If You want to skip validation then use winner.save(validate: false)
end
end
I'm working on a sample project that allows Orders to be created, comprised of many Products, associated via LineItems (see my recent question at Should I include other fields in a HABTM table?).
Once my view for a new object gets rendered, I have access to the #orders object, and it seems like I should be collecting #line_items as I go. The problem is, the #orders object hasn't been saved yet.
The best example I could find on the web so far was at a Railscasts writeup that seemed to encourage storing object parameters in session:
def new
session[:order_params] ||= {}
#order = Order.new(session[:order_params])
#order.current_step = session[:order_step]
end
I just wasn't sure how to best pay this out when I'm dealing with storing multiple Products per Order (via LineItem). One thing that came to mind was to create and save the object in the database but to only mark it as "real" once the user actually saves it (vs just adding items to the order) - but given abandonment rates and such, it seemed like I might end up with too much garbage.
Is there a widely accepted convention to taking a new #order that's unsaved and "building" a has_many list of products "correctly?" For the record, I'm trying to replicate a project I built in PHP using RoR, if that helps provide context of my end game.
My schema (intended to support ordering gift cards for multiple Properties) looks a little something like:
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 3) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "line_items", id: false, force: true do |t|
t.integer "order_id"
t.integer "product_id"
t.decimal "pay_cost", precision: 8, scale: 2, null: false
t.decimal "pay_discount", precision: 8, scale: 2, default: 0.0
t.decimal "pay_linetotal", precision: 8, scale: 2, null: false
t.text "note"
end
add_index "line_items", ["order_id", "product_id"], name: "index_line_items_on_order_id_and_product_id", unique: true, using: :btree
create_table "orders", force: true do |t|
t.string "order_id", null: false
t.integer "property_id"
t.string "order_status", default: "new"
t.string "email_address"
t.string "bill_name"
t.string "bill_address1"
t.string "bill_address2"
t.string "bill_city"
t.string "bill_state"
t.string "bill_zip"
t.string "ship_name"
t.string "ship_address1"
t.string "ship_address2"
t.string "ship_city"
t.string "ship_state"
t.string "ship_zip"
t.string "pay_cardtype"
t.string "pay_pastfour"
t.text "order_summary"
t.boolean "is_gift", default: false
t.text "order_message"
t.boolean "pay_live", default: false
t.boolean "pay_paid", default: false
t.boolean "pay_refunded", default: false
t.decimal "pay_total", precision: 8, scale: 2, null: false
t.decimal "pay_discount", precision: 8, scale: 2, default: 0.0
t.integer "stripe_fee"
t.string "stripe_token"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "orders", ["order_id"], name: "index_orders_on_order_id", unique: true, using: :btree
add_index "orders", ["order_status"], name: "index_orders_on_order_status", using: :btree
create_table "products", force: true do |t|
t.string "name", null: false
t.decimal "price", precision: 8, scale: 2, null: false
t.boolean "active", default: true
end
create_table "products_properties", id: false, force: true do |t|
t.integer "product_id"
t.integer "property_id"
end
add_index "products_properties", ["product_id", "property_id"], name: "index_products_properties_on_product_id_and_property_id", unique: true, using: :btree
create_table "properties", force: true do |t|
t.string "name", null: false
t.string "slug", null: false
t.string "prefix", null: false
t.string "phone"
t.text "address"
t.string "email"
t.boolean "visible", default: true
end
add_index "properties", ["prefix"], name: "index_properties_on_prefix", unique: true, using: :btree
add_index "properties", ["slug"], name: "index_properties_on_slug", unique: true, using: :btree
create_table "properties_users", id: false, force: true do |t|
t.integer "user_id"
t.integer "property_id"
end
add_index "properties_users", ["user_id", "property_id"], name: "index_properties_users_on_user_id_and_property_id", unique: true, using: :btree
create_table "users", force: true do |t|
t.string "first_name"
t.string "last_name"
t.string "email", null: false
t.string "encrypted_password", default: "", null: false
t.datetime "locked_at"
t.boolean "active", default: true, null: false
t.boolean "superuser", default: false, null: false
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
end
To persist the shopping cart between requests you can either store the necessary details in session or use actual database records and mark the order as temporary/unconfirmed.
In simple scenarios using the database might be overkill but you definitely want to avoid having complex data in session. If you change things in the future it might break old sessions.
Im working on a simple web shop right now, so I'll show how Im using the session to store and retrieve the working order. I store a hash in session[:cart] with the keys being product ids and the values being the quantity
Add to cart action
def add_to_cart
product = Product.find(params[:product_id])
session[:cart][product.id] ||= 0
session[:cart][product.id] += 1
redirect_to :back
end
A before_action method restores #order between requests
class ApplicationController < ActionController::Base
before_action :restore_order
private
def restore_order
session[:cart] ||= {}
#order = Order.build_from_session(session[:cart])
end
end
The Order model
class Order < ActiveRecord::Base
has_many :line_items
def self.build_from_session(session_data)
order = self.new
session_data.each do |product_id, quantity|
product = Product.find_by_id(product_id)
next unless product && quantity > 0
order.line_items << LineItem.new(product: product, quantity: quantity)
end
return order
end
end
Storing it in the session should be fine. If you're configured to use CookieStore for sessions, try to keep the data as small as possible (just product IDs, ideally). You might also want to look at something like wicked if you've got a multi-step checkout process.
I would encourage you to think about storing orders directly in your database, though. That lets you do some cool stuff like show a user their abandoned cart when they come back to your site, or send out reminder emails/special offers to people who left in the middle of the process.
You would also want a scheduled cleanup process that purges incomplete orders older than a few weeks/months.