I'm new to RoR and I want to create simple page like a task manager (to add and remove tasks) so I created 2 tables with association between them (Track and Item).
Here is 2 models:
class Item < ApplicationRecord
belongs_to :track, optional: :true
end
class Track < ApplicationRecord
has_many :items, dependent: :destroy
end
And I need to set association when I create or delete any track item. But when I create it I just see my track item (with an empty field in associated table)
For example:
rails c
Track.create(item: 'Asafa Pauel', description: 'This is a description') - works fine (added all field to db)
Item.all - track_id field is empty - but it should show id of track item. Why is this?
And my Tracks controller:
class TracksController < ApplicationController
def index
#track = Track.all
end
def show
#track = Track.all
end
def new
#track = Track.new
end
def create
#track = Track.new(track_params)
#item = Item.new(track_id: #track.id)
if #track.save! && #item.save!
flash[:success] = "It works!"
redirect_to tracks_path
else
flash[:success] = "Its wrong!"
end
end
private
def track_params
params.require(:track).permit(:item, :description)
end
end
And Items controller:
class ItemsController < ApplicationController
def create
#item = Item.new(item_params)
end
private
def item_params
params.require(:item).permit(:track_id)
end
end
And db schema:
ActiveRecord::Schema.define(version: 2019_05_23_112947) do
enable_extension "plpgsql"
create_table "items", force: :cascade do |t|
t.bigint "track_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["track_id"], name: "index_items_on_track_id"
end
create_table "tracks", force: :cascade do |t|
t.string "item"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Thanks in advance
Your new 'Track' object doesn't have an ID yet, so you can't assign its value to Item.track_id.
First you'll have to save the Track, then create a new Item.
Also, if you create a new Track from console, you won't trigger your "create" method in the controller: it will be called only if you create a new Track from browser.
If you want to create a new Item every time you create a Track, you'll have to do something like this in your model file "track.rb":
after_save :create_new_item
def create_new_item
self.items.create
end
P.S.: the "track.rb" file is in "app/models" in your Rails application.
Related
This question is a bit complicated to ask and probably more difficult to understand so I will do my best to add context to explain my goal.
My app allows logged-in users to run a web scraping function on a car dealership's website and stores it's inventory to a database. I would like to associate the current_user.id of who ran the scraping function to the posts created by it.
First, I generated a migration to associate "vehicles" with "users" by running
rails g migration AddUserRefToVehicles user:references
then, I added a belongs_to :user association to the Vehicle model
then, I updated the create action in VehiclesController
The VehiclesController calls on a "scrape" function which runs the "vehicles_spider.rb" model and creates the posts and saves it to a database.
vehicles_controller.rb :
class VehiclesController < ApplicationController
before_action :authenticate_user!
before_action :set_vehicle, only: %i[ show edit update destroy ]
...
#Calls vehicles_spider.rb
def scrape
url = 'https://www.example.com/vehicles'
response = VehiclesSpider.process(url)
if response[:status] == :completed && response[:error].nil?
flash.now[:notice] = "Successfully scraped url"
else
flash.now[:alert] = response[:error]
end
rescue StandardError => e
flash.now[:alert] = "Error: #{e}"
end
def new
#vehicle = Vehicle.new
end
def create
#This didn't work
#vehicle = Vehicle.new(vehicle_params.merge(user_id: current_user.id))
end
...
private
# Use callbacks to share common setup or constraints between actions.
def set_vehicle
#vehicle = Vehicle.find(params[:id])
end
# Only allow a list of trusted parameters through.
def vehicle_params
params.require(:vehicle).permit(:title, :stock_number, :exterior_color,
:interior_color, :transmission, :drivetrain, :price)
end
end
vehicle.rb
class Vehicle < ApplicationRecord
belongs_to :user
end
vehicles_spider.rb
class VehiclesSpider < Kimurai::Base
require 'nokogiri'
require 'httparty'
require 'byebug'
require 'watir'
#name = 'vehicles_spider'
#engine = :mechanize
def self.process(url)
#start_urls = [url]
self.crawl!
end
def parse(response, url:, data: {})
url = "https://www.example.com/new-vehicles/"
{ ... }
pagination_vehicle_listings.each do |vehicle_listing|
vehicle = {title: ...
price: ...
color: ...
}
// This line is what creates a post and saves the scraped data to my database.
Vehicle.where(vehicle).first_or_create
{ ... }
end
schema.rb
ActiveRecord::Schema.define(version: 2021_07_14_104441) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
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", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
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
end
create_table "vehicles", force: :cascade do |t|
t.string "title"
t.string "stock_number"
t.string "exterior_color"
t.string "interior_color"
t.string "transmission"
t.string "drivetrain"
t.integer "price"
t.integer "miles"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "user_id", null: false
t.index ["user_id"], name: "index_vehicles_on_user_id"
end
add_foreign_key "vehicles", "users"
end
The program was saving the posts just fine until I associated "vehicles" to "users" and now the scraped data is refusing to save. My hunch is that the vehicle posts that are being created by the "vehicle_spider.rb" model arn't being associated with the logged-in user.
If I understood the problem correctly, you should have access to the current_user object when running the scrape action.
You can pass that object to VehiclesSpider parse method and something like below to persist the data in the database.
current_user.vehicles.where(vehicle).first_or_create ...
I found a solution. The problem is rails models don't have access to the current_user object by default. We can give models access to the current_user object by adding the following to the Application Controller.
application_controller.rb
before_action :set_current_user
def set_current_user
Vehicle.current_user = current_user
end
added this to vehicle model
vehicle.rb
cattr_accessor :current_user
now that our models have has access to current_user I refactored this line from vehicles_spider.rb
Vehicle.where(vehicle).first_or_create
to
Vehicle.where(vehicle.merge(user_id: Vehicle.current_user.id)).first_or_create
and now the user ids are saved and associated with the posts!
Im very new tor ails and still learning. My project that I am working on is set up for a user to have many cars and cars belong to a user.
class User < ApplicationRecord
validates :name, presence: true, uniqueness: true
has_secure_password
has_many :cars
end
class Car < ApplicationRecord
belongs_to :user
end
My flow is set up to where after a user signs in they can create their vehicle. However I am having trouble creating the car so that it will attach to the user and I am getting an error each time I try different methods.
def create
#car = Car.new(car_params)
#user = User.find(params[:id])
#car.save
redirect_to user_path
end
private
def car_params
params.require(:car).permit(:user_id, :make, :model, :color)
end
my schema seems to be set up correctly
create_table "cars", force: :cascade do |t|
t.string "make"
t.string "model"
t.string "color"
t.integer "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["user_id"], name: "index_cars_on_user_id"
end
But again, I am clearly doing something wrong and I am having trouble finding my answers online.
Well, if you have a login, you probably have a current_user set up. The logged one. Inside your create method on your controller you can set the car information using the logged in user like:
def create
#car = Car.new(car_params)
#car.user_id = current_user.id
#car.save
redirect_to user_path
end
You can also remove the :user_id from the car_params if you have the information about the logged user.
If you don't have the current_user or whatever method to get the logged in user created, you will need to select the user inside the form via a HTML select or something like that, then you can keep the :user_id inside car_params and just remove the line about setting the user_id on #car to save.
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 am currently working on a scoring system for my game application and for some reason points are not adding up. The goal is whenever the player guesses the correct answer, points are added. For each new player, the score is set to 0.
question#validate_answer:
def validate_answer
#correct_answer = Answer.where(correct: true).map(&:text)
#selected_answer = params[:answer]
#player_score = Player.where(id: params[:id]).map(&:score)
if #correct_answer.include? (#selected_answer)
#player_score[0] += 1
render :success
else
render :error
end
end
Quesiton.rb
class Question < ActiveRecord::Base
belongs_to :category
has_many :answers
has_one :video_clue
has_many :answers
def correct_answer
answers.find_by correct: true
end
end
Answer.rb
class Answer < ActiveRecord::Base
belongs_to :question
end
Player.rb
class Player < ActiveRecord::Base
has_secure_password
def admin?
self.admin == 'admin'
end
end
Schema Tables for Answers and Players
create_table "answers", force: :cascade do |t|
t.integer "question_id"
t.string "text"
t.boolean "correct"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "answers", ["question_id"], name: "index_answers_on_question_id", using: :btree
create_table "players", force: :cascade do |t|
t.string "user_name"
t.string "password_digest"
t.integer "score"
t.string "role"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I would restructure the code the like this:
def validate_answer
#question = Question.find(params[:q_id])
#correct_answer = Answer.find_by_question_id(params[:q_id]).text.downcase
#selected_answer = params[:answer].downcase
#player = Player.find(params[:id])
if #selected_answer == #correct_answer
#player.increment!(:score)
render :success
else
render :error
end
end
increment! automatically pings the #player object for the column specified. It saved the trouble of trying to convert it to an array and access the index which was sort of roundabout.
NOTE: Edited Answer per chat conversation.
just see how less lines of code are need to be done for that
def validate_answer
#i think this is a bad approach to compare the text of the answer
#with the text of the given answer
#the better one would be to assing UUID to the answer or just use the regular ID
# something like this params[:answer][:uuid]
# or params[:answer][:id]
# we need to find the answer related to the question, otherwise we
# could throw in just random IDs and they are still saying "correct"
given_answer = Questions.find(params[:question_id]).answers.find params[:answer][:id]
# if we use UUID we dont need to give the question id, since then the ID can't be guessed
if given_answer.correct?
# current_user is a player.
# current_user is a method from devise (u should use devise)
current_user.award_points! given_answer
render :success
else
ender :error
end
end
class Player
def award_points! answer
# adding the points to the user
# and save it
current_user.award_points += answer.points
current_user.save
#better approach would be to save the question which was answered for the user
# like has_many :answered_questions
# so then u could also track which questions already been answered by the user and not awarding him twice
end
end
I have a nested resource with 'posts' containing many 'comments' and associations set up between these models. But when I create a comment for a post the 'post_id' in the comments table remains empty and no link is established. The comment text itself gets created ok.
I'm using Rails ver 4.2.1 and a postgresql database.
The associations are set up like this:
class Comment < ActiveRecord::Base
belongs_to :post
end
class Post < ActiveRecord::Base
has_many :comments, dependent: :destroy
end
This is the route set up:
resources :posts do
resources :comments
end
I create the comments from the comments/new view with this code:
= form_for [#post, #comment] do |f|
= f.label :comment
= f.text_field :comment
= f.submit "Add Comment"
My comments controller is like this:
class CommentsController < ApplicationController
def new
#post = Post.find(params[:post_id])
#comment = Comment.new
end
def create
#post = Post.find(params[:post_id])
#comment = Comment.create(comment_params)
redirect_to posts_path
end
def comment_params
params.require(:comment).permit(:comment)
end
end
I have the column 'post_id' set up in the comments table and my schema is this:
ActiveRecord::Schema.define(version: 20150404204033) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "comments", force: :cascade do |t|
t.string "comment"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "post_id"
end
add_index "comments", ["post_id"], name: "index_comments_on_post_id", using: :btree
create_table "posts", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "comments", "posts"
end
Just can't work out what is going on, I've used almost identical code on another project and that worked.
Any help would be great.
In this code:
def create
#post = Post.find(params[:post_id])
#comment = Comment.create(comment_params)
redirect_to posts_path
end
you find the post but never do anything with it. The comment has no knowledge of that post. You need to set comment's post to #post.