I'm creating a to-do list API in Rails 5, but I'm running into an error that I cannot seem to solve. Basically, I have users, lists and items. Items belong to lists, and lists belong to users.
Using curl in the command line, I have successfully created a user. However, when I try to follow the same steps to create a list, I get this error as seen from my rails server:
ActionController::ParameterMissing (param is missing or the value is empty: list):
app/controllers/api/lists_controller.rb:15:in 'list_params'
app/controllers/api/lists_controller.rb:5:in 'create'
After researching for hours here and elsewhere, my thoughts are:
1. My curl request isn't formatted correctly for the belongs_to relationship between lists and users
2. My strong parameters aren't formatted correctly for the belongs_to relationship between lists and users
3. I'm following a curriculum for Rails 4, but since I'm using Rails 5, something is different
Since I was able to create a user successfully, I'll include the user code as well as the list code. I've been racking my brain for way, way too long on this, so I sincerely hope someone can help me out.
Curl requests in the command line
To create a new user:
curl -d "user[username]=thisisatest" -d "user[password]=thisisatest" http://localhost:3000/api/users/
This created a user with the user ID of 10, which I'm using in the list example
To create a new list:
curl --digest -u thisisatest:thisisatest -d "list[title]=test list" -d "list[private]=true" http://localhost:3000/api/users/10/lists
I'm using bcrypt, so my database uses password_digest, which is why I've included the --digest flag
Rails/API setup
config/routes.rb
Rails.application.routes.draw do
root 'welcome#index'
namespace :api, defaults: { format: :json } do
resources :users do
resources :lists
end
resources :lists, only: [] do
resources :items, only: [:create]
end
resources :items, only: [:destroy]
end
end
db/schema.rb
ActiveRecord::Schema.define(version: 20171005175122) do
create_table "items", force: :cascade do |t|
t.text "body"
t.integer "list_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["list_id"], name: "index_items_on_list_id"
end
create_table "lists", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.boolean "private"
t.index ["user_id"], name: "index_lists_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
app/contollers/api_controller.rb
class ApiController < ApplicationController
skip_before_action :verify_authenticity_token
private
def authenticated?
authenticate_with_http_basic {|username, password| User.where( username: username, password: password).present? }
end
end
User
app/controllers/api/users_controller.rb
class Api::UsersController < ApiController
before_action :authenticated?
def index
users = User.all
render json: users, each_serializer: UserSerializer
end
def create
user = User.new(user_params)
if user.save
render json: user
else
render json: {errors: user.errors.full_messages }, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:username, :password)
end
end
app/models/user.rb
class User < ApplicationRecord
has_many :lists, dependent: :destroy
validates :username,
presence: true,
length: { minimum: 1, maximum: 100 },
uniqueness: { case_sensitive: false }
validates :password,
presence: true,
length: { minimum: 6 }
has_secure_password
end
app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :created_at, :username
end
List
app/controllers/api/lists_controller.rb
class Api::ListsController < ApiController
before_action :authenticated?
def create
list = List.new(list_params)
if list.save
render json: list
else
render json: {errors: list.errors.full_messages }, status: :unprocessable_entity
end
end
private
def list_params
params.require(:list).permit(:title, :private)
end
end
app/models/list.rb
class List < ApplicationRecord
belongs_to :user
has_many :items, dependent: :destroy
validates :title, length: { minimum: 1 }, presence: true
validates :user, presence: true
end
app/serializers/list_serializer.rb
class ListSerializer < ActiveModel::Serializer
attributes :id, :title, :user_id, :private
end
Related
I am new to rails and am trying to build a blog app where only signed in users can see the username of the person who created a post, however I keep getting this error NoMethodError in Posts#index undefined method `username' for nil:NilClass
screenshot of error in localhost:3000
Here is my routes.rb
```
Rails.application.routes.draw do
devise_for :users
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Defines the root path route ("/")
# root "articles#index"
root "posts#index"
resources :posts, only: [:new, :create, :index]
get "/posts/new.html.erb", to: "posts#create", as: "create"
get "/posts/new.html.erb", to: "posts#new", as: "new"
end
```
here is my posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index]
def new
#post = Post.new
end
def create
#post = current_user.posts.build(post_params)
#post.user = current_user
respond_to do |format|
if #post.save
format.html { redirect_to user_post_path(current_user, #post), notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: #post }
else
format.html { render :new }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
def index
#posts = Post.all.order(created_at: :desc)
end
private
def post_params
params.require(:post).permit(:title, :description)
end
end
```
here is my post.rb model
```
class Post < ApplicationRecord
belongs_to :user
end
```
here is my user.rb model
```
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts
validates :username, presence: true
validates :email, presence: true
validates :password, presence: true
end
``
here is my schema
```
ActiveRecord::Schema[7.0].define(version: 2022_11_14_173843) do
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
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.datetime "created_at", null: false
t.datetime "updated_at", 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
end
end
```
here is my AddNameToUsers migration
```
class AddNameToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :username, :string
end
end
```
Here is my AddUserIdToPosts migration
```
class AddUserIdToPosts < ActiveRecord::Migration[7.0]
def change
add_column :posts, :user_id, :integer
end
end
```
Here is my CreatePosts Migration
```
class CreatePosts < ActiveRecord::Migration[7.0]
def change
create_table :posts do |t|
t.string :title
t.text :description
t.timestamps
end
end
end
```
I finally got it working, for the link_to error, I simply needed to remove the only: function in my routes.rb to only say resources :posts and for the username error I referenced a user in my createPosts migration and then cleared the Posts and Users in the rails console and started over from signing up to making a post to viewing all posts to seeing a single post
Try debugging the post object which is raising the Exception.
It looks like there is a Post object which doesnt have a user associated with it. So post.user returns nil and when username method is called on the nil object an Exception is raised as it is not defined on nil.
You may check in the rails console which post doesnt have a user associated with it and based on your requirements either correct such objects or remove the objects from being displayed in index or just do not show a username for posts if it is not present.
I have two models: Account and Profile.
I want render json API with attributes of Profile and include there one attribute from Account.
profile_serializer.rb
class ProfileSerializer < ActiveModel::Serializer
attributes :first_name, :middle_name, :last_name, :role
def role
#UserAccountSerializer.new(object.role)
object.account.role
end
end
account_serializer.rb
class UserAccountSerializer < ActiveModel::Serializer
attributes :role
end
profile.rb
class Profile < Grape::API
desc 'Current user profile'
get '/', serializer: ProfileSerializer do
current_user.profile
end
end
accounts_controller.rb
class AccountsController < AdminController
def index
#accounts = Account.all
end
def show
render json: #account
end
def update
#account = Account.find(params[:id])
redirect_to accounts_path if #account.update(role: params[:role])
end
end
account.rb
class Account < ApplicationRecord
belongs_to :profile
enum role: %i[user admin]
after_initialize :set_default_role, if: :new_record?
def set_default_role
self.role ||= :user
end
end
profile.rb
class Profile < ApplicationRecord
has_one :account
end
schema.rb
create_table "accounts", force: :cascade do |t|
t.string "email", default: "", null: false
t.bigint "profile_id", null: false
t.integer "role", default: 0
t.index ["email"], name: "index_accounts_on_email", unique: true
t.index ["profile_id"], name: "index_accounts_on_profile_id"
end
create_table "profiles", force: :cascade do |t|
t.string "last_name", null: false
t.string "first_name", null: false
t.string "middle_name"
end
Grape give me this error: undefined method `role' for nil:NilClass
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 trying to create an association between two models in my Rails app (User and Coin) where Coin belongs_to User and User has_many Coins. When I add the belongs_to association in the Coin model, I am no longer able to edit or create Coin pages. Why would it do this? As soon as I remove the association, I'm able to create/edit again. Also, the corresponding has_many association on the User page does not have the same effect. I'd appreciate any help in understanding what is happening here and how I can properly make this association. Thanks.
User.rb
class User < ApplicationRecord
acts_as_votable
has_many :questions, dependent: :destroy
has_many :events, dependent: :destroy
has_many :links, dependent: :destroy
has_many :posts, dependent: :destroy
has_many :moderated_coins, class_name: "Coin"
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:validatable, authentication_keys: [:login]
validates :username, presence: :true, uniqueness: { case_sensitive: false }
validates_format_of :username, with: /^[a-zA-Z0-9_\.]*$/, :multiline => true
validate :validate_username
def validate_username
if User.where(email: username).exists?
errors.add(:username, :invalid)
end
end
def login=(login)
#login = login
end
def login
#login || self.username || self.email
end
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions.to_h).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
elsif conditions.has_key?(:username) || conditions.has_key?(:email)
where(conditions.to_h).first
end
end
end
Coin.rb
class Coin < ApplicationRecord
validates :currency_name, presence: true
has_many :questions, dependent: :destroy
has_many :events, dependent: :destroy
has_many :links, dependent: :destroy
mount_uploader :picture, PictureUploader
has_and_belongs_to_many :genres
# belongs_to :moderator, class_name: "User". <--- * The problem is here
validate :picture_size
private
def picture_size
if picture.size > 5.megabytes
errors.add(:picture, "Picture must be smalled than 5MB.")
end
end
end
coins_controller.rb
class CoinsController < ApplicationController
load_and_authorize_resource param_method: :question_params
before_action :find_coin, only: [:edit, :update, :destroy ]
before_action :authenticate_user!, except: [:index, :create, :show]
def index
#search = Coin.ransack(params[:q])
#coins = #search.result(distinct: true)
end
def new
#coin = Coin.new
end
def create
#coin = Coin.new(coin_params)
if #coin.save
flash[:success] = "Coin created"
redirect_to #coin
else
render 'new'
end
end
def show
#coin = Coin.find(params[:id])
end
def edit
authorize! :update, #coin
end
def update
if #coin.update(coin_params)
redirect_to #coin
else
render 'edit'
end
end
def destroy
Coin.find(params[:id]).destroy
redirect_to coins_url
end
private
def coin_params
params.require(:coin).permit( :currency_name, :currency_abbrev, :moderator_id, :accepted, :picture, :question1, :question2, :question3, :question4, genre_ids:[])
end
def find_coin
#coin = Coin.find(params[:id])
end
end
user_controller.rb
class UsersController < ApplicationController
before_action :authenticate_user!
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #user }
end
end
end
schema.rb
create_table "coins", force: :cascade do |t|
t.string "link_name"
t.string "currency_name"
t.string "currency_abbrev"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "permalink"
t.boolean "accepted", default: false
t.datetime "accepted_at"
t.string "genre"
t.integer "genre_id"
t.integer "moderator_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.string "username"
t.string "wallet"
t.boolean "admin", default: false
t.boolean "moderator", default: false
t.decimal "currentbalance", precision: 8, scale: 2
t.decimal "payout_to_date", precision: 8, scale: 2
t.text "bio"
t.string "link1"
t.string "link2"
t.string "link3"
t.string "link4"
t.string "link5"
t.string "name"
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
Use:
belongs_to :moderator, class_name: "User", optional: true
In rails 5, belongs_to enforces existence of the associated record by default. You need to use optional: true in order to allow moderator_id to be nil.
During a signup process I have a user model and Tenant model. Recently I added a serialized column to the Tenant model and I can update the this column fine. However when creating a new Tenant I have devise creating tenant through nested parameters and I get the following error:
ActiveRecord::SerializationTypeMismatch (Attribute was supposed to be a Hash, but was a String. -- "{}"): Important to note that I don't touch that column during the sign up process I have tried including the column on the sanitizer but it does the same. On the schema there is a default value which is '{}'. Below some of the code:
create_table "tenants", force: :cascade do |t|
t.string "tenant_name"
t.string "tenant_address"
t.string "tenant_city"
t.string "tenant_zip"
t.string "tenant_phone"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "authorized"
t.boolean "trial"
t.string "plan_id"
t.string "plan_name"
t.string "braintree_id"
t.string "subscription_id"
t.jsonb "preferences", default: "{}", null: false
t.string "tenant_state"
t.string "tenant_country"
t.index ["preferences"], name: "index_tenants_on_preferences", using: :gin
end
class Tenant < ApplicationRecord
has_many :users, :dependent => :delete_all
has_many :customers, :dependent => :delete_all
has_many :work_orders, :dependent => :delete_all
has_many :vehicles, :dependent => :delete_all
has_many :suppliers, :dependent => :delete_all
end
serialize :preferences, Hash
store_accessor :preferences, :state_tax, :mun_tax, :welcome_sms, :estimate_sms, :completed_sms, :disclaimer
Here is part of my User controller:
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
def new
build_resource({})
self.resource.tenant = Tenant.new
respond_with self.resource
end
# POST /resource
def create
super
if #user.save
#result = Braintree::Customer.create(
:first_name => #user.name,
:last_name => #user.lastname,
:company => #user.tenant.tenant_name,
:email => #user.email,
:phone => #user.phone
)
if #result.success?
#user.tenant.set_braintree_id(#result.customer.id)
flash[:notice] = 'Thanks you! and Welcome to Autokick.tech enjoy your free 30 days!'
else
flash[:notice] = #result.errors
end
end
end
t.jsonb "preferences", default: "{}", null: false
The default is a string "{}" like the error says.
Change it to default: {} without the quotes.