I am having trouble adding an object to associate class.
the parent class, user, has a has_many relation with the ad class.
When i try to access user's has_many object ":ads" from the ad controller, it returns me an "undefined method ads" exception. I am posting my model and my controller code below. Please help me on this issue.
user model
class User < ActiveRecord::Base
has_many :ads
has_secure_password
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "50x50#" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
def self.searchID(query)
where("id like ?", "#{query}")
end
end
ad model
class Ad < ActiveRecord::Base
belongs_to :user
scope :orderNewestFirst , lambda { order("id DESC") }
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100#" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
def self.search(query)
where("title like ?", "%#{query}%")
end
end
Ad Controller
class AdController < ApplicationController
layout false
before_action :confirm_logged_in
def index
#ads = Ad.orderNewestFirst
end
def new
#ad = Ad.new()
end
def create
#find = session[:user_id]
#user = User.searchID(#find)
#ad = Ad.new(ad_params)
if #user.ads << #ad #***this is where the error is occuring***
flash[:notice] = "Ad created Successfully"
redirect_to(:controller => 'user' , :action => 'index')
else
render('new')
end
end
def show
#ad = Ad.find(params[:id])
end
def ad_params
params.require(:ad).permit(:title, :category, :description , :priceRange, :avatar )
end
end
EDIT
Here are my migrations for User and Ad
User Migration
class CreateUsers < ActiveRecord::Migration
def up
create_table :users do |t|
t.string "firstName" , :limit => 50 , :null => false
t.string "lastName" , :limit => 50
t.string "email" , :null => false
t.string "password" , :limit => 30 , :null => false
t.integer "rating" , :default => 0
t.string "location" , :default => "Lahore"
t.timestamps null: false
end
end
def down
drop_table :users
end
end
#user = User.new(:firstName => "" , :lastName => "" , :email => "" , :password => "" , :rating => "" , :location => "")
Ad Migration
class CreateAds < ActiveRecord::Migration
def up
create_table :ads do |t|
t.references :user
t.string "title" , :null => false
t.string "category" , :null => false
t.text "description"
t.string "priceRange" , :null => false
t.attachment :avatar
t.timestamps null: false
end
add_index("ads" , "user_id")
end
def down
drop_table :ads
end
end
#ad = Ad.new(:title => "" , :category => "" , :description => "" , :priceRange => "")
The error that i get..
Your User.searchID(query) returns multiple user records (a relation object), not a single one.
If you change it to User.searchID(query).take, it fetches only a single record, and the error vanishes.
I don't know what value session[:user_id] holds, but looking at your users table, this would simply be an integer ID? In that case, there isn't much sense in the current approach. I'd recommend something like this:
def create
#user = User.find(session[:user_id])
# Rest of create method
end
The User.find method looks for a user record with that exact ID, and will return a single record straight away. This is one of the most common ways to fetch records using Ruby on Rails.
I am not sure how like query with id is working for you, In postgres it doesn't
def self.searchID(query)
where("id like ?", "#{query}")
end
You don't need above method you can find user directly by id as
#user = User.find(session[:user_id])
Related
I'm building a friendship feature in my Rails/Angular app and currently I can create relations between users but I'm stuck on a small problem.
I have a friendships table,
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
When a user adds another users as a friend a new record is created,
This is the index definition on the friendships_controller
def index
friend = current_user.friendships
render :json => friend.to_json()
end
And this is the json output.
{"id":1,"user_id":1,"friend_id":2,"created_at":"2015-12-29T13:18:44.499Z","updated_at":"2015-12-29T13:18:44.499Z"},
{"id":2,"user_id":1,"friend_id":3,"created_at":"2015-12-29T13:18:45.463Z","updated_at":"2015-12-29T13:18:45.463Z"},
{"id":3,"user_id":1,"friend_id":4,"created_at":"2015-12-29T13:18:46.420Z","updated_at":"2015-12-29T13:18:46.420Z"}
But this data is hardly usable. Is it possible to inject the user data in the friendship json?
{"id":1,"email":"peter#peter.nl","name":"Peter Boomsma"},
{"id":2,"email":"jan#jan.nl","name":"Jan Jansen"},
{"id":3,"email":"kees#kees.nl","name":"Kees Keesen"},
{"id":4,"email":"piet#piet.nl","name":"Piet Pietersen"}
So that the friendship json output looks like this,
{"id":1,"user_id":1,"friend_id":2,"email":"jan#jan.nl","name":"Jan Jansen","created_at":"2015-12-29T13:18:44.499Z","updated_at":"2015-12-29T13:18:44.499Z"},
{"id":2,"user_id":1,"friend_id":3,"email":"kees#kees.nl","name":"Kees Keesen","created_at":"2015-12-29T13:18:45.463Z","updated_at":"2015-12-29T13:18:45.463Z"},
{"id":3,"user_id":1,"friend_id":4,"email":"piet#piet.nl","name":"Piet Pietersen","created_at":"2015-12-29T13:18:46.420Z","updated_at":"2015-12-29T13:18:46.420Z"}
* REMOVE FUNCTION *
app.factory('removeFriend', ['$http', function($http) {
return {
removeFriend: function(friend) {
var _friendID = parseInt(friend.id);
console.log (_friendID)
return $http.delete('/friendships/'+_friendID + '.json');
}
};
}])
This is the remove service in angular, here it passes the friend.id and not the friendship.id.
I will implement something like following:
Add association in user model to get friends of the user i.e user objects
has_many :friends, through: :friendships, foreign_key: 'friend_id'
now get friends data in controller
friends = current_user.friends.as_json(:only => [:name, :email, :id])
render :json => friends # [{:name => 'test', :email => 'test#test.com', :id => 1},{...}]
If you need current user data in the response you can do something as following:
render :json => current_user.as_json(only: [:id, :name, :email]).merge(:friends => friends)
# {:id => 1, :name => 'user', :email => 'testuser#test.com', friends: [{:name => 'test', :email => 'test#test.com', :id => 1},{}]}
In above response you can send exact data required without repetition.
I have a form for a model called isp, which 'has_many' isp accounts. the isp account belongs to to 'isp'.
There is a validation on the isp_account that means it cant be added if there isnt an isp_id, so my reasoning is to created a nested form. I created the nested form like so
= simple_form_for #isp, :html => { :multipart => true } do |f|
= f.input :title
= f.simple_fields_for :isp_accounts do |tag|
= tag.input :title, label: "Tag Name"
however the nested attribute isnt being displayed. There are no errors etc. Why is this? Am I approaching this in the best way? is this the only way?
here's the code
ISP MODEL
class Isp < ActiveRecord::Base
has_many :isp_accounts, dependent: :destroy
has_many :deployments, through: :servers
has_many :servers, through: :isp_accounts
validates :title, presence: true
accepts_nested_attributes_for :isp_accounts
end
ISP ACCOUNTS MODEL
class IspAccount < ActiveRecord::Base
belongs_to :isp
has_many :deployments, through: :servers
has_many :servers, dependent: :destroy
validates :title, presence: true
validate :check_associates
private
def check_associates
associated_object_exists Isp, :isp_id
end
end
ISP ACCOUNT CONTROLLER
....
def new
#isp_account = IspAccount.new
end
def update
#isp_account.update_attributes(isp_accounts_path)
if #isp_account.save
record_saved
return redirect_to(isp_accounts_path)
else
check_for_errors
return render('/isp_accounts/edit')
end
end
private
def get_isp_accounts
#isp_account = IspAccount.all
end
def get_isp_account
#isp_account = IspAccount.find(params_isp_accounts)
end
def params_isp_accounts
params.require(:isp_account).permit!
end
end
....
def new
#isp = Isp.new
end
def update
#isp.update_attributes(params_isp)
if #isp.save
record_saved
return redirect_to(isps_path)
else
check_for_errors
return render('new')
end
end
private
def params_isp
params.require(:isp).permit(:title, isp_accounts_attributes: [:id, :title])
end
def get_isp
#isp = Isp.where(id: params[:id]).first
unless #isp
record_not_found
return redirect_to(isps_path)
end
end
def get_isps
#isp = Isp.all.order(:title)
end
end
SCHEMA
create_table "isp_accounts", force: true do |t|
t.string "title"
t.integer "isp_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "isps", force: true do |t|
t.string "title"
t.datetime "created_at"
t.datetime "updated_at"
end
ok i got it. I was missing the new bit for that attribute in my controller. pretty basic really.
def new
#isp = Isp.new
#isp.isp_accounts.new
end
My seeds.rb:
doc = Document.new(
:name => "Document"+i,
:description => "Description of Document"+i,
:content => "hello world",
:public => false
)
doc.save! #encrypted_content and content will be nil after this line
doc.content = "hello"
doc.save! #encrypted_content will be set correctly after this line
My Document model:
class Document < ActiveRecord::Base
include KeyCreator
has_many :document_users, dependent: :destroy
has_many :users, :through => :document_users
before_create :before_create
validates :name, presence: true
validates :description, presence: true
attr_encrypted :content, key: 'secret'
def self.admin_in(user)
Document.joins(:document_users)
.where(:document_users => {:user => user, :role => ["owner", "admin"]})
.order(:name)
end
def self.non_admin_in(user)
Document.joins(:document_users)
.where(:document_users => {:user => user})
.where.not(:document_users => {:role => ["owner", "admin"]})
.where.not(:document_users => {invite_accepted_date: nil})
.order(:name)
end
def current_doc_user(current_user)
self.document_users.find_by(user: current_user)
end
def to_param
"#{link_key}".parameterize
end
def after_create(current_user)
#setting email to avoid validation error
self.document_users.create!(user: current_user, email: current_user.email, role: "owner")
end
private
def before_create
now = DateTime.now
self.content = nil
self.api_key = create_key
self.link_key = create_key
end
end
My migration:
class CreateDocuments < ActiveRecord::Migration
def change
create_table :documents do |t|
t.string :name
t.text :description
t.text :encrypted_content #I also tried t.string. It doesn't work either
t.boolean :public
t.string :api_key
t.string :link_key
t.timestamps
end
add_index :documents, :api_key, unique: true
add_index :documents, :link_key, unique: true
#add_index :users, :reset_password_token, unique: true
end
end
My environment is rails 4.0.3, ruby 2.1.1.
Setting content on new and also create doesn't seem to work. It works when I set the field and then save again. There are no raised errors.
I have the following 2 tables defined in migrations
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.string :phone
t.string :email
t.string :address
t.string :resume
t.timestamps
end
end
end
Class CreateResumeSections < ActiveRecordMigration
def self.up
create_table :resume_sections do |t|
t.string :section_name
t.string :html
t.timestamps
end
end
end
I have following 2 models
class User
has_many :resume_sections, :dependent => :destroy
attr_accessor :section_layout
after_save :save_sections
private
def save_sections
self.section_layout = ###Someother logic here that sets this variable
end
end
class ResumeSection
belongs_to :user
end
In my users_controller, I have the following code
class UserController < ApplicationController
def create
#user = User.new(params[:user])
#user.save
#user.section_layout.each {|key,value|
rs = ResumeSection.new(:section_name => key, :html => value, :user => #user)
rs.save
}
end
end
In my view I have the following code
<% #user.resume_sections.each do |section| %>
<%= section.section_name %>
<%= section.html %>
<% end %>
I get Undefined method error for Nil:NilClass in the view. The expression #user.resume_sections is not returning to me the records that I just created and saved in the UsersController. Instead it returns nil to me. When I check the database the records are there.
Is the expression #user.resume_sections the correct expression to access these records?
Thanks
Paul
It seems to me that your you missed something in you migrations. ResumeSection needs to have and integer field called user_id. Just create a new migration that has something like this in it:
def change
add_column :resume_section, :user_id, :integer
end
Lets say I have two tables.
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :type, :default => 'User'
t.string :user_name, :null => false
t.boolean :is_registered, :default => true
# ... many more fields
end
end
end
class CreateContactInfo < ActiveRecord::Migration
def self.up
create_table :contact_info do |t|
t.integer :resource_id
t.string :resource_type
t.string :first_name
t.string :last_name
t.string :middle_initial
t.string :title
end
end
end
class ContactInfo < ActiveRecord::Base
belongs_to :contactable, :polymorphic => true
end
class User < ActiveRecord::Base
has_one :contact_info, :as => :contactable
# composed_of :contact_info # ... It would be nice if magics happened here
end
I would like to have the User's contact_info automatically merged into my User object as attributes of the user object without having to say #user.contact_info.first_name; instead, I would prefer to be able to write #user.first_name.
The reason I am breaking out attributes to the contact_info table is that these are common attributes to multiple models. That is why I am making setting up the contact_info as a polymorphic association.
Does anyone know of a good way to aggregate/merge the attributes of contact_info directly into my user model?
Use delegate:
class User < ActiveRecord::Base
has_one :contact_info, :as => :contactable
delegate :name, :name=, :email, :email=, :to => :contact_info
end
Not necessarily a good way to do it, but I did something similar by overriding the method_missing method and then calling my aggregated object. So, it would look something like:
class User
def method_missing(method_id)
self.contact_info.send(method_id)
end
end
Edit 1: Better implementation (I think this will work):
class User
alias_method :orig_method_missing, :method_missing
def method_missing(method_id)
if (self.contact_info.respond_to?(method_id))
self.contact_info.send(method_id)
else
orig_method_missing(method_id)
end
end
end
The above has the advantage that all other unknown method calls will get passed correctly.
I finally got it! Thank you both amikazmi and Topher Fangio. I had to implement both the delegate and method_missing techniques to get this to work.
Here is the total madness that finally ended up working for me! If anybody has suggestions on how to further improve this, I'd love to hear your suggestions.
class User < ActiveRecord::Base
attr_accessible *([:user_name, :udid, :password, :password_confirmation, :contact_info] + ContactInfo.accessible_attributes.to_a.map {|a| a.to_sym})
has_one :contact_info, :as => :contactable
def method_missing(method_id, *args)
if (!self.respond_to?(method_id) && self.contact_info.respond_to?(method_id))
self.contact_info.send(method_id, *args)
elsif (!self.class.respond_to?(method_id) && ContactInfo.respond_to?(method_id))
ContactInfo.send(method_id, *args)
else
super(method_id, *args)
end
end
# delegating attributes seems redundant with the method_missing above, but this secret sauce works.
ContactInfo.accessible_attributes.to_a.each do |a|
delegate a.to_sym, "#{a}=".to_sym, :to => :contact_info
end
def initialize(*args)
options = args.extract_options!
contact_attrs = ContactInfo.accessible_attributes.to_a.map{|a| a.to_sym}
#ci = ContactInfo.new(options.reject {|k,v| !contact_attrs.include?(k) })
super(*(args << options.reject { |k,v| contact_attrs.include?(k) }.merge(:contact_info => #ci) ) )
self.contact_info = #ci
end
validates_presence_of :user_name
validates_uniqueness_of :user_name
validates_associated :contact_info
def after_save
# automatically save the contact info record for the user after the user has been saved.
self.contact_info.save!
end
end
class ContactInfo < ActiveRecord::Base
set_table_name "contact_info"
belongs_to :contactable, :polymorphic => true
validates_presence_of :email
validates_uniqueness_of :email
attr_accessible :first_name,
:last_name,
:middle_initial,
:title,
:organization_name,
:email,
:email_2,
:twitter_name,
:website_url,
:address_1,
:address_2,
:city,
:state,
:zip,
:phone_work,
:phone_mobile,
:phone_other,
:phone_other_type
def full_name
[self.first_name, self.last_name].compact.join(' ')
end
end