virtual attribute problems with undefined methods - ruby-on-rails

I've used Virtual attributes in the past but I can't seem to get past this, and I know the answer is probably staring me in the face.
I have a model like so:
model Confirmation.rb
class Confirmation < ActiveRecord::Base
#attr_accessible :confirmation, :confirmation_token
#attr_accessible :confirmation_token
def confirmation_token
confirmation.confirmation_token if confirmation
end
def confirmation_token=(token)
self.confirmation = Booking.find_by_confirmation_token(token)
end
end
Your average scaffold controller for
confirmations_controller.rb
def new
#confirmation = Confirmation.new(:confirmation_token => params[:confirmation_token])
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #confirmation }
end
end
new.html.erb
<h1>New confirmation</h1>
<% form_for(#confirmation) do |f| %>
<%= f.error_messages %>
<%= f.hidden_field :confirmation_token %>
...
routes.rb
map.confirmation "confirmation/:confirmation_token", :controller => "confirmations", :action => "new"
map.resources :confirmations
error
undefined method `confirmation=' for #
In the console Booking.find_by_confirmation_token(token) with a given token works perfectly fine.
Any ideas? suggestions?

What you really need is attr_accessor :confirmation. There's a difference between attr_accessible and attr_accessor.
attr_accessor :confirmation
is same as
def confirmation
#confirmation
end
def confirmation=(value)
#confirmation = value
end
Now since it's such a common pattern ruby introduced helper methods for that.
Attr_accesible on the other hand is rails method, which marks that certain fields can be mass updated.

I think it should be either:
def confirmation_token=(token)
#confirmation = Booking.find_by_confirmation_token(token)
end
Or you should uncomment attr_accessible :confirmation or define #confirmation and #confirmation=.

class Confirmation < ActiveRecord::Base
belongs_to :bookings
#attr_accessible :confirmation, :confirmation_token
#attr_accessible :confirmation
def confirmation_token
#confirmation.confirmation_token if #confirmation
end
def confirmation_token=(token)
#confirmation = Booking.find_by_confirmation_token(token)
end
end
this worked... however just uncovering the attr_accessible :confirmation, did not. self.confirmation still returned undefined method...

Related

Models associated with one another create undefined methods in Rails 5 undefined method `shoutouts' for #<User:0x007fd825472cf0>

Was using Michael Harti's Ruby on Rails Tutorial and when following it I get an undefined method between two models. I have renamed the models and tried playing around a lot with it but all attempts end up with undefined methods or an uninitialized constant.
Shout Out controller
class ShoutOutController < ApplicationController
def show
#user = User.find(params[:id])
end
def create
#shout_out = current_user.shout_out.build(shout_out_params)
if #shout_out.save
flash[:success] = "Shout Out Created"
redirect_to root_url
else
render user
end
end
def destroy
end
private
def shout_out_params
params.require(:shout_out).permit(:content)
end
end
User Controller
class UsersController < ApplicationController
def index
#users = User.paginate(page: params[:page])
end
def new
#user = User.new
end
def show
#user = User.find(params[:id])
#shoutouts = #user.shout_outs.paginate(page: params[:page])
#shoutouts.user = User.find(params[:id]).name
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome!"
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
#handles successful update of account info
flash[:success] = "Updated Profile Info"
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
user model
class User < ActiveRecord::Base
attr_accessor :remember_token
# :activation_token
has_many :scrimmages
has_many :ShoutOuts, dependent: :destroy
has_many :friendships
has_many :direct_messages, :through => :friendships
before_save :downcase_email
# before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: {with: VALID_EMAIL_REGEX}, uniqueness: { case_sensitive: false}
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#returns a random token for remember function
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
#returns true if token matches the digest
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
#forgets the user
def forget
update_attribute(:remember_digest, nil)
end
private
# converts emails to downcase
def downcase_email
self.email = email.downcase
end
#creates and assigns activation token and digest
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
micropost model
class ShoutOut < ApplicationRecord
belongs_to :user
default_scope -> { order(created_at: :desc)}
validates :user_id, presence: true
validates :content, presence: true, length: {maximum: 140}
end
view partial
<li id="shoutout-<%= ShoutOut.id %>">
<%= link_to gravatar_for(ShoutOut.user, size: 50), ShoutOut.user %>
<span class="user"><%= link_to ShoutOut.user.name, ShoutOut.user %></span>
<span class="content"><%= ShoutOut.content %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(shout_out.created_at) %> ago.
</span>
routes file
Rails.application.routes.draw do
# root to: 'users#new'
# these routes are for showing users a login form, logging them in, and logging them out.
get '/login' => 'sessions#new'
post '/login' => 'sessions#create'
get '/logout' => 'sessions#destroy'
get '/signup' => 'users#new'
post '/users' => 'users#create'
post '/users/id/edit' => 'users#edit'
resources :users
resources :account_activations, only: [:edit]
root to: 'landing#index'
end
Error Message Screenshot-
There are a couple of errors in the above code which is confusing rails.
In the micropost model, you have defined a class called ShoutOut. You may have heard the expression 'convention over configuration'. Rails is looking for the shoutout file. Your ShoutOut class should be in a file called shout_out.rb in the models folder.
In the user class, you have put has_many ShoutOuts, it should be has_many :shout_outs
I have a feeling the ShoutOutController should be ShoutOutsController, but you will need to post your routes file in order to confirm.
In the view partial, why are you calling ShoutOut.id? I suspect you want to use shout_out.id, but you need to post the view that is calling that partial, along with the controller method that is calling the view. In this view you currently only have access to #user variable which is set in the show method in the Users controller.
It looks like you are not clear on why some things should be capitalised and why they shouldn't. Read up on instance vs method variables and on class methods vs instance methods. Try the following in your view
<% #user.shoutouts.each do |shout_out| %>
<li id="shoutout-<%= shout_out.id %>">
<%= link_to gravatar_for(#user, size: 50), #user %>
<span class="user"><%= link_to #user.name, #user %></span>
<span class="content"><%= shout_out.content %></span>
<span class="timestamp"> Posted <%= time_ago_in_words(shout_out.created_at) %> ago. </span>
</li>
<% end %>
As Joe C commented, you need to post the actual error message along with the stack trace so we can help you debug the error. If you look carefully at the error message, it will give you a file name and line number of where the error is occurring.
Ty again for helping, but what I've been using is the guide from michael harti linked above and, after a lot of changes this was when i decided to post on Stack. I tried using User.shout_out and User.shoutout and User.shout_outs with the results being the same error. Is the guide wrong?

uninitialized constant and remove minutes selector

I have the following controller
class Dashboard::AvailabilitiesController < Dashboard::ApplicationController
def index
end
def new
end
def create
end
def destroy
end
end
with this model:
class Availability
include Mongoid::Document
include Mongoid::Timestamps
field :class_date, type: DateTime
belongs_to :kid
validates_uniqueness_of :class_date, :scope => [:class_date, :kid_id]
end
and this route:
namespace :dashboard do
resources :kids do
resources :availabilities
end
end
on my view, I have the following form:
= form_tag dashboard_kid_availabilities_url(current_kid), :method => 'post', :multipart => true do
.form-group
= datetime_select :class_date, {:start_year => Time.now.year, :order => [:day, :month, :year], :discard_minute => true}
.form-group
= submit_tag _('Save'), class: 'btn btn-blabloo btn-xs'
But I'm getting "uninitialized constant Dashboard::AvailabilitiesController" and I don't understand why. Also, I can remove the minutes selector of the forme using the discard_minute => true.
Any Idea please.
Thanks in advance
THe controller name should be within module Dashboard
module Dashboard
class AvailabilitiesController < ApplicationController
def index
end
def new
end
def create
end
def destroy
end
end
end
Another option is, probably, class Dashboard::AvailabilitiesController < ApplicationController.
Also make sure, that controller in under /controllers/dashboard/avaliabilities_controller.rb

getting null value in my table?

I am creating employee pay roll application. In that i am having two models one is employee and another is salary. I have added foreign key to get the employee_id in salaries table and to get the employee name in _form.html.erb(salaries) using the gem 'rails3-jquery-autocomplete'. I have followed all the steps in that gem. But foreign key relationship is not working. I am getting null value in the field employee_id in salaries table.
'
Here i have attached my model,view,controller,routes and everything. Kindly check it.
My Models
** Salary.rb**
class Salary < ActiveRecord::Base
belongs_to :employee, :foreign_key => "employee_id"
attr_accessible :basic, :da, :effective_from, :effective_to, :employeesalary, :hra, :ca, :sa, :employee_id
end
Employee.rb
class Employee < ActiveRecord::Base
has_many :salaries, :foreign_key => "employee_id"
attr_accessible :address, :age, :branch, :city, :date_of_birth, :designation, :employee_end_date, :employee_start_date, :gender, :name
end
My Controllers
salaries_controller.rb
class SalariesController < ApplicationController
autocomplete :employee, :name, :display_value => :name, :full => true
def new
#salary = Salary.new
end
def create
#salary = Salary.new(params[:salary])
if #salary.save
flash[:success] = "Created a new month salary!"
redirect_to salaries_path
else
flash[:error] = "Couldn't Create salary"
render 'new'
end
end
def index
#salary = Salary.order('created_at DESC').paginate(:page => params[:page], :per_page => 10)
end
def show
#salary = Salary.find(params[:id])
#employee =Employee.all
end
end
employees_controller.rb
class EmployeesController < ApplicationController
def new
#employee = Employee.new
#salary = #employee.salaries.build
end
def create
#employee = Employee.new(params[:employee])
if #employee.save
flash[:success] = "Created Employee Successfully!"
redirect_to employee_path(#employee)
else
flash[:error] = "Couldn't Create a Employee"
render 'new'
end
end
def index
#employee = Employee.order('created_at DESC').paginate(:page => params[:page], :per_page => 10)
end
def show
#employee = Employee.find(params[:id])
end
end
routes.rb
resources :salaries do
get :autocomplete_employee_name, :on => :collection
end
resources :employees
_form.html.erb(salaries)
<%= form_for #salary do |f| %>
<%= f.hidden_field :employee_id, :id => "real_employee_id" %>
<span class="help-block">Enter the Employee name</span>
<%= autocomplete_field_tag 'throwaway_employee', '',
autocomplete_employee_name_salaries_path,
:size => 75, :id_element => '#real_employee_id' %>
application.js
//= require jquery
//= require jquery_ujs
//= require autocomplete-rails
//= require_tree .
I have checked throughly but can't find the error. Kindly help me to get out of this issue.
Thanks in advance...
I had a play with this code tonight and I think I know what might be happening.
When you start typing in an employee name a green list of names pops up. If you just hit enter straight away the form takes whatever you have typed so far and uses it to look up the name. This fails because the name is incomplete. As you have no validation on the employee id the form works and a null employee id is saved.
What you need to do is to use the down arrow key or mouse to select one of the names. When you do that the name will change to light green and the text in the text box will be replaced with the full name.
Then when you submit the form the match is found and everything works!
So your code works, you are just using the GUI incorrectly.
You should also add this line to your salary.rb file:
validates :employee_id, presence: true
That's my theory. Please try it out and let us know how you go.

Acts_as_commentable_with_threading - comments are not adding

#post.comments.all is clear. and i dont see any errors after i send form. When i click "Submit" i sent to posts/id/comments, but
my routes.rb
resources :posts do
resources :comments
end
post controller
def show
#current_user ||= User.find(session[:user_id]) if session[:user_id]
#commenter = #current_user
#post = Post.find(params[:id])
#comment = Comment.build_from( #post, #commenter.id, "234234" )
#comments = Comment.all
respond_to do |format|
format.html
format.json { render json: #post }
end
end
comments controller
class CommentsController < ApplicationController
def index
#comments = #post.comments.all
end
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new params[:comment]
if #comment.save
redirect_to #post # comment not save, so i dont redirect to this page
else
# is that there
end
end
end
post model
acts_as_commentable
has_many :comments
comment model
class Comment < ActiveRecord::Base
acts_as_nested_set :scope => [:commentable_id, :commentable_type]
attr_accessible :commentable, :body, :user_id
validates :body, :presence => true
validates :user, :presence => true
# NOTE: install the acts_as_votable plugin if you
# want user to vote on the quality of comments.
#acts_as_votable
belongs_to :commentable, :polymorphic => true
# NOTE: Comments belong to a user
belongs_to :user
# Helper class method that allows you to build a comment
# by passing a commentable object, a user_id, and comment text
# example in readme
def self.build_from(obj, user_id, comment)
new \
:commentable => obj,
:body => comment,
:user_id => user_id
end
#helper method to check if a comment has children
def has_children?
self.children.any?
end
# Helper class method to lookup all comments assigned
# to all commentable types for a given user.
scope :find_comments_by_user, lambda { |user|
where(:user_id => user.id).order('created_at DESC')
}
# Helper class method to look up all comments for
# commentable class name and commentable id.
scope :find_comments_for_commentable, lambda { |commentable_str, commentable_id|
where(:commentable_type => commentable_str.to_s, :commentable_id => commentable_id).order('created_at DESC')
}
# Helper class method to look up a commentable object
# given the commentable class name and id
def self.find_commentable(commentable_str, commentable_id)
commentable_str.constantize.find(commentable_id)
end
end
post view
%h2 Add a comment:
- #comments.each do |c|
= #c.body
= form_for([#post, #comment]) do |f|
.field
= f.label :body
%br/
= f.text_area :body
.actions
= f.submit
Thanks in advance and sorry for bad english
First of all you can debug why #comment.save return false yourself - just add p #comment.errors in else block and check server log.
It seems for me that you try to save invalid comments because you don't have setup user for #comment in action CommentsController#create. Comment validates presence of user!
There are several ways how to fix it. Analyzing your code I think the simplest way for you is modify CommentsController#create
#CommentsController
def create
#current_user ||= User.find(session[:user_id]) if session[:user_id]
#post = Post.find(params[:post_id])
#comment = #post.comments.new params[:comment]
#comment.user = #current_user
if #comment.save
redirect_to #post # comment not save, so i dont redirect to this page
else
# is that there
end
end
Another way is to use some gem for authentication - I recommend devise
One more way (very bad way) is to pass user_id through hidden field (you have defined #current_user in PostsController#show and user_id in attr_accessible list in Comment). But this is easy way to hack your application and write comments on behalf of any user in system!

Am I using the rails form properly?

I feel like I have no idea what I'm doing.. I have a vague Idea. I'm hoping I did this all right so far.
Any way you can see to refactor this would be greatly appreciated.
One thing I noticed it does wrong is it won't load the proper options that were previously submitted if there is an error and it posts to the same URL. The text inputs seem to load the previous value but the select and the radio buttons reset to the default every submit.
ResourcesController#new
def new
#resource = Resource.new
#title = "Submit Resource"
#categories = Category.all
end
ResourcesController#create (notice I have #categories = Category.all in both... according to DRY im not sure where else it should go, or it only works on the first form submit.
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
category_ids = #categories.map { |c| c[1] }
if #resource.valid? and category_ids.include? params[:category_id]
#resource.cost = params[:cost]
#resource.category_id = params[:category_id]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
Resource.rb (model)
# == Schema Information
#
# Table name: resources
#
# id :integer not null, primary key
# upvotes :integer default(0)
# downvotes :integer default(0)
# url :string(255)
# title :string(255)
# cost :integer default(0)
# description :text
# flags :integer
# category_id :integer
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Resource < ActiveRecord::Base
belongs_to :category
belongs_to :user
has_many :favorites
has_many :resource_tags
has_many :tags, :through => :resource_tags
attr_accessible :url, :title, :cost, :description, :category_id, :user_id
# Pseudo-Enum
COST = [:free, :paid, :both]
url_regex = /^(?:http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}(:[0-9]{1,5})?(\/.*)?$/ix
validates :url, :presence => true,
:format => { :with => url_regex,
:message => "must be valid"},
:uniqueness => { :case_sensitive => false,
:message => "has already been submitted"}
validates :title, :presence => true,
:length => { :within => 6..75 }
validates :cost, :presence => true
validates :description, :presence => true,
:length => { :within => 25..200 }
validates :category_id, :presence => true,
:format => { :with => /\d+/ }
validates :user_id, :presence => true,
:format => { :with => /\d+/ }
def cost
COST[read_attribute(:cost)]
end
def cost=(value)
write_attribute(:cost, COST.index(value.downcase.to_sym))
end
def category_id
read_attribute(:category_id).to_i
end
def category_id=(value)
write_attribute(:category_id, value.to_i)
end
end
My view file for the Resource#new form
<div class="field">
<%= f.label :category %>
<%= select_tag(:category_id, options_for_select(#categories.map {|c|[c.name, c.id]})) %>
</div>
Last Q: i havent worked with the user_id field yet. This is going to be pulled from devise and will associate a User with a submitted resource. But how do I assign this without making some sort of input, like a hidden input. Would this go on behind the scenes in the controller?
To your last question:
devise adds a current_user method which is the logged in user. So if a user has multiple resources you could do something like:
#resource = current_user.resources.new(params[:resource])
First question:
When a form is rendered it is done so based on the #resource & #categories variables. When you post the form the create action is called which creates a new #resource. If the save fails for whatever reason the form is rerendered using the new #resource variable. The problem you have is that #resource.category is not set when you show the form again. So you'll have to do this before the is_valid? check.
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
#resource.category = Category.find(params[:category_id])
if #resource.valid? # won't be valid if there is no category found.
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
But the real problem is with your form. It should nest the category_id in the resource params so that the category is set when you do Resource.new(params[:resource]).
Check the POST request body in your console or something and see if it's nested in the resource or not. I don't know the exact syntax for it but if you change this you can drop the #resource.category = Category.find line.
To piggyback on Sandip, you can dry up your actions by using a before_filter
class ResourcesController < ApplicationController
before_filter :load_categories, :only => [:show, :create]
def new
#resource = Resource.new
end
def create
#resource = Resource.new(params[:resource])
#resource.category = Category.find(params[:category_id])
if #resource.valid? # won't be valid if there is no category found.
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
private
def load_categories
#categories = Category.all
end
end
also if you plan on sticking #title inside of your application layout, I would change #title in your view to:
yield(:title) || 'My Site'
and on the appropriate pages use:
content_for(:title) do
Submit Resource
It will default to 'My Site' unless otherwise specified.
Looks like there is problem with create action
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
if #categories.collect(&:id).include?(params[:category_id].to_i)
#resource.category_id = params[:category_id]
end
#resource.user = current_user
if #resource.valid?
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
view
<div class="field">
<%= f.label :category %>
<%= select_tag(:category_id, options_for_select(#categories.map {|c|[c.name, c.id]}, :selected => #resource.category_id)) %>
</div>

Resources