When I add has_many, some data is suddently not saved - ruby-on-rails

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

Related

ActiveRecord::NotNullViolation in ReviewsController#create

Receiving this error in Rails:
PG::NotNullViolation: ERROR: null value in column "reviewer_id" violates not-null constraint DETAIL: Failing row contains (26, 2222, 2222, 8, null, 2021-01-30 19:26:03.354983, 2021-01-30 19:26:03.354983).
This app is trying to allow users to give other users reviews.
I am new, so the error may be quite obvious, but not to the untrained eye. After hours of research, this was my last resort.
I'd appreciate in help fixing this. God bless.
routes.rb
resources :users do
resources :reviews, only: [ :new, :create ]
end
reviews_controller.rb
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
if #review.save
redirect_to user_path(current_user), notice: 'Review added!'
else
render :new
end
end
private
def review_params
params.require(:review).permit(:rating, :content)
end
schema.rb
create_table "reviews", force: :cascade do |t|
t.text "content"
t.integer "rating"
t.bigint "user_id", null: false
t.bigint "reviewer_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["reviewer_id"], name: "index_reviews_on_reviewer_id"
t.index ["user_id"], name: "index_reviews_on_user_id"
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.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.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 "offers", "games"
add_foreign_key "offers", "users"
add_foreign_key "rentals", "offers"
add_foreign_key "rentals", "users"
add_foreign_key "reviews", "users"
add_foreign_key "reviews", "users", column: "reviewer_id"
user.rb
has_many :reviews, dependent: :destroy
has_many :given_reviews, source: :reviews, foreign_key: :reviewer_id
has_many :received_reviews, source: :reviews, foreign_key: :user_id
review.rb
belongs_to :user
validates :content, presence: true
validates :rating, presence: true
You have a database constraint on Review, for the reviewer_id field, defined in your schema here:
t.bigint "reviewer_id", null: false
In your controller#create action you are defining a user_id, but I don't see where you are passing in the reviewer_id or setting it. If you have a field in the form, you'll need to add that param to the review_params so it can be passed from the form to the controller action. Or you'll need to define it in the create action similar to how you've defined the user_id.
Since your reviewer is the person leaving the review, and the User is being reviewed, I would do this:
In your view where you are leaving the review, (I assume this would be User#show) you are viewing a specific user. You know which user this is and most likely in that view it is defined as #user, so you can most likely use <%= hidden_field_tag :user_id, #user.id %> in the form for the review. This will pass user_id to the Reviews#create action in the params. You will also need to add user_id to review_params so it can be accessed in the #create action.
Then in your Reviews#create action, assign #review.reviewer_id = current_user.id. #review.user_id will be assigned when you call Review.create(review_params) assuming you have added it the the form and the review_params as suggested above.
Try adding a breakpoint in the create action and look at the params object getting sent from the review form, this may be helpful for you to understand what's happening.
Typical Rails practice is to use a model validation as well. This way you'd get an app error before the request ever hits the database.

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

I have problems with my comments when my app is deployed. Locally everything is working. The logs from heroku says:
ActiveModel::MissingAttributeError (can't write unknown attribute `user_id`):
2018-01-02T15:52:43.461794+00:00 app[web.1]: [79cd7190-e2d9-4dd0-bf71-
709552e5c6e5] app/controllers/comments_controller.rb:15:in `create'
I have no ideas what is occuring the error. Maybe some database thing?
My CommentsController
class CommentsController < ApplicationController
def create
#post =Post.find(params[:post_id])
#comment =#post.comments.create(params[:comment].permit(:name, :body).merge(user: current_user))
redirect_to post_path(#post)
end
def destroy
#post = Post.find(params[:post_id])
#comment= #post.comments.find(params[:id])
if current_user.id == #comment.user_id
#comment.destroy
end
redirect_to post_path(#post)
end
end
My Models
class User < ApplicationRecord
has_many :posts
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
class Post < ApplicationRecord
belongs_to :user, optional: true
has_many :comments, dependent: :destroy
validates :title, presence: true, length: {minimum: 5}
validates :body, presence: true
end
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
end
My migration-file
class CreateComments < ActiveRecord::Migration[5.1]
def change
create_table :comments do |t|
t.string :name
t.text :body
t.references :post, index: true
t.timestamps
end
end
end
if you need more code or have any ideas please let me know
EDIT: if i add a user_id column i get a SQLite3::SQLException: duplicate column name: user_id: ALTER TABLE "comments" ADD "user_id" integer error
My schema.rb
`create_table "comments", force: :cascade do |t|
t.string "name"
t.text "body"
t.integer "post_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.index ["post_id"], name: "index_comments_on_post_id"
end
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
t.string "theme"
t.integer "user_id"
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.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
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
You'll need to add a user_id column to your comments table. The belongs_to requires this. You're also going to need a post_id column, and user_id for your posts table to.
You can customise the column name, but the convention is to use the format parent_table_id.
Here's the key quote, from the docs:
Associations are implemented using macro-style calls, so that you can
declaratively add features to your models. For example, by declaring
that one model belongs_to another, you instruct Rails to maintain
Primary Key-Foreign Key information between instances of the two
models, and you also get a number of utility methods added to your
model.
This means, for example, if your first user has an id of 1, all of their comments and posts will have a user_id value of 1, which does the actual tying together of the records.
Here's an example migration with the relevant line included:
def change
create_table :comments do |t|
...
t.belongs_to :user, index: true
...
end
end
Does that make sense? Let me know if you've any questions and I can update as needed :)
You need to add user_id
Create the migration with
rails g migration AddUserIdToComment user:references
Then do
rake db:migrate
And you should be fine.
Your migration have missing
t.references :user, index: true
So you need to add user_id column within comments table
Update : It seems like you have some migration problem. I suggest you to check for rake db:migrate:status comment and look for any down migration. Once all are up then just run rake db:migrate:down VERSION='VERSION_NUMBER_HERE' and add your user t.references :user, index: true to the same migration and migrate.
PS: Change existing migration if and only if you have not pushed it.

How to show a specific unit of data from a record queried in rails?

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}

Rails - collection_select does not select a values from tables having translations

I am relatively new to RoR.
This works nicely:
<td><%= collection_select :competitions_members, :member_id, Member.all, :id, :first_name %></td>
This one picks no value (actually all such calls to tables with translations):
<td><%= collection_select :competitions_members, :tull_id, Tull.all, :id, :name %></td>
seeded data in competitions_members table
Member can be involved in many competition. Basically I have N:M relationship between members and competitions via competitions_members table.
Tull is a dictionary. Value to be set during the process of assigning members to a competition.
Data model classes:
class Member < ApplicationRecord
has_and_belongs_to_many :competitions
end
class Competition < ApplicationRecord
has_and_belongs_to_many :members
end
class CompetitionsMember < ApplicationRecord
end
Tull table has also translations in separate table.
class Tull < ApplicationRecord
translates :name
has_many :competitions_members
# separate different localizations of the record
def cache_key
super + '-' + Globalize.locale.to_s
end
end
Relevant schema.db excerpt
create_table "members", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "competitions", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "competitions_members", force: :cascade do |t|
t.integer "member_id"
t.integer "competition_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "tull_id"
t.index ["tull_id"], name: "index_competitions_members_on_tull_id"
end
create_table "tull_translations", force: :cascade do |t|
t.integer "tull_id", null: false
t.string "locale", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.index ["locale"], name: "index_tull_translations_on_locale"
t.index ["tull_id"], name: "index_tull_translations_on_tull_id"
end
create_table "tulls", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Any help apreciated. I just realized this might be connected with translated tables somehow.
class Tull < ApplicationRecord
has_many :translations
translates :name, :fallbacks_for_empty_translations => true
attr_accessible :translations_attributes
end
Try to execute below code in rails console:
Tull.first.translations - If this gives you translation records that means the associations are correct.
Now check at view side, how would you generate attributes for multilingual stuffs. I would suggest to use globalize_accessors.
Please send me the codebase.

How would I go about allowing users to create surveys

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

Resources