before_action and nested attributes for models - ruby

The question is really simple.
Please, take a look at my schema:
ActiveRecord::Schema.define(version: 20170812094528) do
create_table "items", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "parent_id"
end
create_table "parents", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "item_id"
end
end
This is how models looks like:
class Parent < ApplicationRecord
has_one :item
accepts_nested_attributes_for :item
before_create :set_something
def set_something
self.item.build
end
end
and
class Item < ApplicationRecord
belongs_to :parent
end
The question: When creating a Parent, why does it raise the following error?
undefined method `build' for nil:NilClass
How should I set this up so that I can add and item record at the same time as creating a parent?

Try using
def set_something
build_item
end
The model Parent has_one :item. In Rails, the has_one association adds a helper method build_association(attributes = {}) to the owner class, and not association.build(attributes = {}). This is the reason that you are getting the error undefined method 'build' for nil:NilClass.
Please see the has_one documentation for more details on has_one association.

Related

Sinatra and Ruby error: ActiveRecord::RecordNotUnique at /shoes/1

Good morning, I'm using Sinatra and Ruby to create a running app that logs your runs. I keep getting this error anytime I try to save a run:
ActiveRecord::RecordNotUnique at /shoes/1
Here is my controller for this route:
post '/shoes/:id' do
#shoe = Shoe.find(params[:id])
if logged_in?
#runs = #shoe.runs.build(params)
#newest_run = (#shoe.current_mileage.to_i + #runs.latest_run.to_i)
#shoe.current_mileage = #newest_run
#runs.save
flash[:message] = "A new run has been added!"
redirect to("/shoes/#{#shoe.id}")
else
redirect to("/login")
end
end
I've been trying to debug this all morning and can't find the reason as to why the run can't happen. I have 3 models as well: A user has_many shoes; a shoe belongs_to a user and has_many runs; a run belongs_to a shoe. Could there be an issue with the associations I have set up?
Edited: Here is my schema:
ActiveRecord::Schema.define(version: 20200708142633) do
create_table "runs", force: :cascade do |t|
t.integer "latest_run"
t.date "date"
t.string "location"
t.integer "shoe_id"
end
create_table "shoes", force: :cascade do |t|
t.string "name"
t.date "date"
t.integer "new_mileage"
t.integer "current_mileage"
t.integer "latest_run"
t.decimal "price", precision: 10, scale: 3
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "email"
t.string "password_digest"
end
end
Edited #2:
Here are my params:
{"latest_run"=>"150", "location"=>"Kansas", "date"=>"2020-07-07", "id"=>"1"}
Here is are my Shoe and Run models:
class Shoe < ActiveRecord::Base
belongs_to :user
has_many :runs
validates :name, :date, :new_mileage, :current_mileage, :price, presence: true
end
class Run < ActiveRecord::Base
belongs_to :shoe
validates_presence_of :latest_run, :date, :location, :shoe_id
end
Thanks everyone!

undefined method `following' for nil:NilClass

I am working on a project similar to a twitter clone. However, I am running into an error that is the following: undefined method `following' for nil:NilClass in my controller. So, to start, here is my controller:
class EpicenterController < ApplicationController
def feed
#Here we initialize the array that will hold tweets from the current_user's
following list.
#following_tweets = []
Tweet.all.each do |tweet|
if current_user.following.include?(tweet.user_id)
#following_tweets.push(tweet)
end
end
end
def show_user
#user = User.find(params[:id])
end
def now_following
#user = User.find(params[:follow_id])
#Adding the user.id of the user you want to follow to your 'follow' array
attribute
current_user.following.push(params[:follow_id].to_i)
current_user.save
end
def unfollow
end
end
Here is my model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
serialize :following, Array
has_many :tweets
validates :username, presence: true, uniqueness: true
end
My Schema:
ActiveRecord::Schema.define(version: 20160811164903) do
create_table "tweets", force: :cascade do |t|
t.string "message"
t.integer "user_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.string "name"
t.string "username"
t.text "bio"
t.string "location"
t.text "following"
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
Finally my application controller:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :username,
:bio,
:location, :following])
devise_parameter_sanitizer.permit(:account_update, keys: [:name,
:username, :bio, :location, :following])
end
end
Thank you for any help that may be provided. Please let me know if you need to see something else and I will add it.
Stack Trace:
NoMethodError (undefined method `following' for nil:NilClass):
app/controllers/epicenter_controller.rb:8:in `block in feed'
app/controllers/epicenter_controller.rb:7:in `feed'
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/actionpack-
4.2.6/lib/action_dispatch/middleware/templates/rescues/_source.erb (3.9ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/actionpack-
4.2.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
(2.7ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/actionpack-
4.2.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (0.9ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/actionpack-
4.2.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
within rescues/layout (63.4ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/web-console-
2.3.0/lib/web_console/templates/_markup.html.erb (0.4ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/web-console-
2.3.0/lib/web_console/templates/_inner_console_markup.html.erb within
layouts/inlined_string (0.3ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/web-console-
2.3.0/lib/web_console/templates/_prompt_box_markup.html.erb within
layouts/inlined_string (0.3ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/web-console-
2.3.0/lib/web_console/templates/style.css.erb within layouts/inlined_string
(0.5ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/web-console-
2.3.0/lib/web_console/templates/console.js.erb within layouts/javascript
(62.0ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/web-console-
2.3.0/lib/web_console/templates/main.js.erb within layouts/javascript
(0.3ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/web-console-
2.3.0/lib/web_console/templates/error_page.js.erb within layouts/javascript
(0.6ms)
Rendered /Users/mikecuddy/.rvm/rubies/ruby-
2.2.1/lib/ruby/gems/2.2.0/gems/web-console-
2.3.0/lib/web_console/templates/index.html.erb (139.5ms)
current_user is nil for not logged in users, so you'll need to handle it:
if user_signed_in?
current_user.following # ...
else
store_location_for :user, request.path
redirect_to new_user_session_path, alert: 'You need to be logged in to do that.'
end
or
class EpicenterController < ApplicationController
before_action :authenticate_user! #, only: [:feed, :now_following] (optional)
# ...
end
if your controller is mostly intended for signed in users (which seems like it).

How can I combine two Rails 4 where queries that query either side of a boolean condition?

Question:
Is there a way to combine the following two queries (including the assignments) into one query? I'm not sure how much time I'd really save. In other words, I'm not sure if it is worth it, but I'd like to be as efficient as possible.
#contacts = #something.user.contacts.where.not(other_user_id: 0)
#user_contacts = #something.user.contacts.where(other_user_id: 0)
More context:
Here is my contacts table from schema.rb:
create_table "contacts", force: true do |t|
t.string "name"
t.string "email"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "profile_picture"
t.string "phone_number"
t.integer "other_user_id"
end
And here is the important stuff from the users table:
create_table "users", force: true do |t|
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
...
t.string "first_name"
t.string "second_name"
end
And here is the pertinent information of the models:
class Contact < ActiveRecord::Base
belongs_to :user
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :name, presence: true
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }
validates :user_id, presence: true
def get_email_from_name
self.email
end
end
[...]
class User < ActiveRecord::Base
has_many :contacts
has_many :relationships,
foreign_key: 'follower_id',
dependent: :destroy
has_many :reverse_relationships,
foreign_key: 'followed_id',
class_name: 'Relationship',
dependent: :destroy
has_many :commitments,
class_name: 'Commitment',
dependent: :destroy
has_many :followers,
through: :reverse_relationships
has_many :followed_users,
through: :relationships,
source: :followed
[...]
before_save { email.downcase! || email }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email,
presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
[...]
def follow!(other_user)
relationships.create!(followed_id: other_user.id)
if create_contact?(self, id, other_user.id)
create_contact(other_user.name,
other_user.email,
self.id,
other_user.id,
other_user.gravatar_url)
elsif create_contact?(other_user, other_user.id, id)
create_contact(name, email, other_user.id, id, gravatar_url)
end
end
def create_contact?(user, followed_id, follower_id)
user.admin? && ! Relationship.where(followed_id: followed_id, follower_id: follower_id).empty?
end
def create_contact(name, email, user_id, other_user_id, profile_picture)
Contact.create!(name: name,
email: email,
user_id: user_id,
other_user_id: other_user_id,
profile_picture: profile_picture)
end
def unfollow!(other_user)
relationships.find_by(followed_id: other_user.id).destroy
Contact.destroy_all(user_id: self.id, other_user_id: other_user.id)
end
[...]
end
The other contacts that may not have an account with the website (yet), and I'd like to distinguish that in the view. So I keep track of which contacts I import through Google contacts using the omniauth gem. For the other contacts, I gather the other users that are friends with current_user.
Goal:
I'd like to save these two record collections to use in the view, but I'd like to avoid looking through all the user's contacts twice, checking the same column in each pass-through.
Any ideas? I'm sure there are lots of ways this can be done, and I'd like to learn as much as I can from this! Thanks in advance!
You can use Array#partition to split up the array in memory, after the query was performed.
#user_contacts, #contacts = #something.user.contacts.partition{|u| other.id == 0 }
However checking for this magic 0 id is smelly. You should try to get rid of such special cases whenever possible.
It is not the best solution, but if you think partition hard to understand, it can be an optional.
#user_contacts, #users = [], []
#something.user.contacts.each do |record|
if record.other_user_id == 0
#user_contacts << record
else
#users << record
end
end

RSpec and ActiveRecord : Examples failing while checking One To Many association

I have been developing a sample application after reading Rails 3 Tutorial book. In this application there are many Sections and each section has one Video.
Here are the artifacts of this application:
Models
class Video < ActiveRecord::Base
has_many :sections
end
class Section < ActiveRecord::Base
belongs_to :video
validates :name, presence: true, length: { maximum: 50 }
end
RSpec
require 'spec_helper'
describe Section do
let(:video) { Video.new(name: "Dummy Video Name", path: "/home/data/video/test.mov") }
before do
#section = video.sections.build(name: 'Test Section')
end
subject { #section }
# Check for attribute accessor methods
it { should respond_to(:name) }
it { should respond_to(:video) }
it { should respond_to(:video_id) }
its(:video) { should == video }
# Sanity check, verifying that the #section object is initially valid
it { should be_valid }
describe "when name is not present" do
before { #section.name = "" }
it { should_not be_valid }
end
describe "when name is too long" do
before { #section.name = "a" * 52 }
it { should_not be_valid }
end
describe "when video_id not present" do
before { #section.video_id = nil }
it { should_not be_valid }
end
...
end
And the schema definitions of both Models
..
create_table "sections", :force => true do |t|
t.string "name"
t.integer "video_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "videos", :force => true do |t|
t.string "name"
t.string "path"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
...
While running above rspec I am getting following error.
Failures:
1) Section
Failure/Error: it { should be_valid }
expected valid? to return true, got false
# ./spec/models/section_spec.rb:22:in `block (2 levels) in <top (required)>'
2) Section video
Failure/Error: its(:video) { should == video }
expected: #<Video id: nil, name: "Dummy Video Name", path: "/home/data/video/test.mov", created_at: nil, updated_at: nil>
got: nil (using ==)
# ./spec/models/section_spec.rb:19:in `block (2 levels) in <top (required)>'
I could map my requirement with User-Micropost relation describe in the book and aligned RSpec with them. I have limited knowledge on Rails and the whole echo system.
Please help me to resolve this issue and some reference to validation Models with RSpec(and shoulda) is highly appreciable.
Amit Patel
I am able to resolve this issue by saving video in before block.
Here is the snippet
before do
video.save
#section = video.sections.build(name: 'Test Section')
end
The only difference between the Micropost model spec of Rails 3 Tutorial book and the above one is , the former one is using FactoryGirl#create. From https://github.com/thoughtbot/factory_girl/wiki/How-factory_girl-interacts-with-ActiveRecord I found that FactoryGirl.create method actually creates and save instance internally. So it was working there while it was no working in my code.
If you have better insight with RSpec for ActiveRecord then please share with us.
Thanks.
Amit Patel

Rails ActiveRecord Ignoring Params and Saving Nil Data

This is stumping me; For some reason the db is saving the record with nil fields instead of my params. Can anyone help me understand why ActiveRecord isn't using my params?
db migration:
class CreateRoutes < ActiveRecord::Migration
def change
create_table :routes do |t|
t.integer :user_id
t.string :start_address
t.string :end_address
t.text :waypoints
t.text :schedule
t.integer :duration
t.timestamps
end
add_index :routes, :user_id
end
end
route.rb:
class Route < ActiveRecord::Base
attr_accessor :start_address, :end_address, :waypoints, :schedule, :duration
belongs_to :user
#serialize :waypoints, :schedule
validates :user_id, presence: true
validates :start_address, presence: true
validates :end_address, presence: true
validates :schedule, presence: true
validates :duration, presence: true, numericality: { only_integer: true, greater_than: 0 }
end
routes_controller.rb:
class RoutesController < ApplicationController
.
.
.
def create
logger.debug "\n\n*** #{params[:route]} ***"
#route = current_user.routes.build(params[:route])
logger.debug "*** The route is #{#route.inspect} ***\n\n"
if #route.save
flash[:success] = "Route saved!"
redirect_to user_path(current_user)
else
render 'new'
end
end
.
.
.
end
logger output:
*** {"start_address"=>"123 Sample St.", "end_address"=>"321 Elpmas St.", "waypoints"=>"None", "schedule"=>"Mondays", "duration"=>"15"} ***
*** The route is #<Route id: nil, user_id: 1, start_address: nil, end_address: nil, waypoints: nil, schedule: nil, duration: nil, created_at: nil, updated_at: nil> ***
The attr_accessors will overwrite the accessors generated by ActiveRecord, causing them to not be persisted in the DB--they'll be like plain old Ruby properties/members instead of the meta-programmed magic of ActiveRecord.
DB properties (persistent properties) can have things like attr_accessible, though.

Resources