Users, form and tasks association rails - ruby-on-rails

I have an app allowing a user to fill a form (named "checklist") and then have a list of tasks he will have to do. The tasks (named "advices") are related to the answers that the user gave in the form.
For example, if a question is "have you cooked dinner ?" and the user answers "no", then an advice "go cook dinner" will be displayed.
Once a advice is done, the user can mark it as completed. Advices are the same for all users. They already are created in the app by admin.
So users have a checklist, checklist belongs to a user.
The problem I encounter is : when a user marks an advice as completed, it is marked as completed for all users. That should not be.
I am not really sure how to fix this. Associations "Has-many", and "Belongs_to" between advices and users should not work since the user does not create the advices ?
I am new to rails so I would be happy if someone could help.
Note that I use Devise to manage users.
Schema :
ActiveRecord::Schema.define(version: 20160407143608) do
create_table "advices", force: :cascade do |t|
t.string "name"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "category_id"
t.boolean "status"
t.string "linkname1"
t.text "link1"
t.text "link2"
t.string "linkname2"
t.text "link3"
t.string "linkname3"
t.integer "ref"
t.boolean "completed"
end
create_table "categories", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "checklists", force: :cascade do |t|
t.boolean "facebook"
t.boolean "twitter"
t.boolean "linkedin"
t.boolean "viadeo"
t.boolean "instagram"
t.boolean "community"
t.boolean "cms"
t.boolean "seo"
t.boolean "crowdfunding"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
add_index "checklists", ["user_id"], name: "index_checklists_on_user_id"
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
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Models :
class Advice < ActiveRecord::Base
belongs_to :category
end
class Checklist < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :checklists
end
View :
<%= advice.name %> | <%= link_to "Completed", complete_advices_path(advice), method: :put %>
controller :
def complete
#advice.completed = true
#advice.save
redirect_to root_path
end

You need a join model.
$ rails g model UserAdvice user:references advice:references
class UserAdvice
belongs_to :user
belongs_to :advice
end
in user.rb
has_many :user_advices
has_many :advices, through: :user_advices
in advice.rb
has_many :user_advices
has_many :users, through: :user_advices
Create a record in the join model when something gets checked off and then query that table to make sure the task is done for an individual user.
So, when a user checks off a task and submits, instead of using the completed boolean, you'd actually create a record that has the advice_id and the user_id. Then if that record exist for that advice, it should be checked off for that user. Does that make sense?
If you were hiding the completed tasks from users who had completed them, for instance, you could say something like
<% if UserAdvice.where(user_id: current_user.id, advice_id: advice.id).count > 0 %>
This will work, and is fine at first, but doing it this way could slow down your app, though. If there are a lot of advices, what you'd probably want to do is run the query once and get all the user_advice records and pluck the ids. Then check against that array of ids against the individual record.
In your controller
# this will return an array of advice_ids
#user_advices = UserAdvice.where(user_id: current_user).pluck(:advice_id)
Then, as you iterate through advices in your view:
<% unless #user_advices.include?(advice.id) %>
show the advice
<% end %>
EDIT:
To create the record inside that complete action:
def complete
UserAdvice.create(user_id: current_user.id, advice_id: #advice.id)
redirect_to root_path
end

Related

Ruby on rails associations - User (devise) can 'create a team' or 'join team' after sign up and log in

Hey guys im working on a rails application where users that have successfully signed up and logged in to their dashboard can create a team and become the leader (invite others to join via email, delete existing members etc.) or join an existing team as a member.( can only view other members and information about themselves) I'm using the Devise gem for user login / sign up since it has a lot of what i need. A team has one manager and has many members. Heres what ive tried, users belong to teams, and teams have many users. Ive also tried the association, Users have one team and teams have many users but one manager. I'm very confused thanks again.
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
has_one :team
end
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"
t.string "team_statement"
t.bigint "user_id"
t.index ["user_id"], name: "index_teams_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.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.string "middlename"
t.string "lastname"
t.string "phone"
t.string "city"
t.string "state"
t.string "street"
t.string "zip"
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.rb
class Team < ApplicationRecord
belongs_to :user, class_name: 'User'
end
routes.rb
devise_for :users, path: 'users' , controllers: { sessions: "users/sessions", confirmations: 'users/confirmations', registrations: 'users/registrations' }
You just add integer field leader_id to teams table. Also, you should add this row to team.rb:
belongs_to :leader, class_name: 'User'
that helps you find a leader from a team (like team.leader).
You should not use has_and_belongs_to_many association.

When I add has_many, some data is suddently not saved

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

My sqlite database is not able to persist anything?

I have a Rails Api with models, controllers, and serializers. The database is set up, and I have made several migrations, all of which have resulted in corresponding changes to the schema. However, nothing is being persisted to the database, either in the rails console or from the seed data. For instance, when I try to run User.create in the console, I see this message appear:
2.3.3 :003 > User.create
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> #<User id: nil, email: "", created_at: nil, updated_at: nil>
Similarly, I have this data in my seeds file:
users = User.create([{ email: 'adam#adam.com' }, { email: 'ryan#ryan.com'
}])
BankAccount.create(name: 'Adams Chase Checking Account', user_id: users.first)
When I run rake db:seed and attempt to call User.all or BankAccount.all in the rails console, I am given an empty array in both cases. I have heard of errors like this being caused by unmet validations on the models, but my models do not have any validations. I am at a loss as to what could be causing this issue. Any help is greatly appreciated! Also, for what it's worth, this project uses Rails 5.1.4, and I have only used 4.x.x previously. Here is the User model (using devise):
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :bank_accounts
has_many :credit_cards
has_many :investments
has_many :loans
has_many :assets
has_many :recurring_payments, through: :bank_accounts
has_many :recurring_payments, through: :credit_cards
has_many :recurring_payments, through: :investments
has_many :recurring_payments, through: :loans
end
And here is the bank account model:
class BankAccount < ApplicationRecord
belongs_to :user
has_many :recurring_payments
accepts_nested_attributes_for :recurring_payments
end
Here is the full schema:
ActiveRecord::Schema.define(version: 20180205231948) do
create_table "assets", force: :cascade do |t|
t.string "name"
t.integer "value"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "bank_accounts", force: :cascade do |t|
t.string "name"
t.integer "balance"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "credit_cards", force: :cascade do |t|
t.string "provider"
t.integer "balance"
t.integer "interest_rate"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "investments", force: :cascade do |t|
t.string "name"
t.integer "value"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "loans", force: :cascade do |t|
t.integer "user_id"
t.integer "interest_rate"
t.integer "remaining_balance"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "recurring_payments", force: :cascade do |t|
t.string "source"
t.boolean "status"
t.date "pay_date"
t.integer "pay_amount"
t.integer "duration"
t.integer "bank_account_id"
t.integer "credit_card_id"
t.integer "loan_id"
t.integer "investment_id"
t.datetime "created_at", null: false
t.datetime "updated_at", 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.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
end
My guess is this.
users = User.create([{ email: 'adam#adam.com' }, { email: 'ryan#ryan.com' }])
Devise validates the default password before saving (6 characters minimum). Try running this command in console and see if it throws any errors?
user = User.create(email: 'adam#adam.com')
user.errors

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.

ActiveRecord::RecordInvalid: Validation failed: User must exist

I'm working on a issue with Single Table Inheritance. I have two different types of Users. User model and Trainer model, Trainer user should inherit attributes from the User model. I created a User in the rails console and everything worked. As soon as I attempted to create a Trainer I get the following error.
Rails 5.0.4
ActiveRecord::RecordInvalid: Validation failed: User must exist
Am I setting up my model associations incorrectly?
Here is my User Model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
My Trainer Model
class Trainer < User
has_many :appointments
has_many :clients, through: :appointments
end
Schema for models
create_table "trainers", 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 "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.string "type"
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
As you can see from my User model, I added the required :type column
Here is the schema for my client and appointment
create_table "appointments", force: :cascade do |t|
t.integer "client_id"
t.integer "trainer_id"
t.datetime "appointment_date"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "start_time"
t.datetime "end_time"
t.integer "status", default: 0
end
create_table "clients", force: :cascade do |t|
t.string "name"
t.string "phone_number"
t.integer "price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
First I created a user in the console
User.create!(email:'ryan#test.com', password:'asdfasdf', password_confirmation:'asdfasdf')
Then I when on to create a Trainer
Trainer.create!(first_name:'Ryan', last_name:'Bent')
Trainers and Users should be associated. But I didn't think I needed add associations using Single Table Inheritance.
With Single Table Inheritance, one table must have all the attributes that any of the subclasses need (more information). So for your situation, you'd need to add the Trainer columns (first_name, last_name) to the users table as well, and then Users would leave that empty on the table and Trainers would fill them in.
If you want to keep the separate tables, what you are doing is no longer single table and would require some sort of joining between the 2.

Resources