Rails db relations: Post.category.name = ? (undefined method `name') - ruby-on-rails

Every post has only one category, and i need to access category's name by something like
p = Post.new
p.category.name = "tech"
p.save
How to do that?
class Category < ActiveRecord::Base
has_many :posts, :dependent => :destroy
attr_accessible :name, :image
end
Post.rb
class Post < ActiveRecord::Base
belongs_to :category
attr_accessible :category_id, :name, :text, :lang, :image
end
Schema.rb
create_table "categories", :force => true do |t|
t.string "name"
t.string "image"
end

Your example contains a problem.
p = Post.new
p.category.name = "tech"
p.save
First, you create a new post. Second, you want to assign a name to the category of the post, BUT there is no category assigned. This results in a call like post.nil.name where nil would be the category object, if assigned, which isn't the case. Since nil has no method name, you get the described error undefined method name for nil class.
To solve this, you first need to assign a category to work on.
p.category = Category.first or p.category_id = 1. After that, p.category will return the category object, therefore p.category.name is valid then because it is called on a category object and not on nil.
tl;dr:
p = Post.new
p.category # => nil
p.category.name # throws an error
p.category = Category.first
p.category # => <#Category ...>
p.category.name # => 'actual name of category'
p.category.name = 'foo' # works now

The problem is that you need/want to explicitly build the category record if it doesn't exist.
To solve the problem, I'd think about creating a category_name= method in Post:
A category_name= settor would also take care of the "law of Demeter" issue
class Post < ActiveRecord::Base
belongs_to :category
attr_accessible :category_id, :name, :text, :lang, :image
attr_accessible :category_name=, :category_name
def category_name=(name)
self.category = Category.find_or_create_by_name(name)
end
def category_name
category && category.name
end
end
See also "Association Extensions" in the ActiveRecord docs for another way to do this.

Related

Permitting params with custom keys

I have a model names TeamInvite that I am trying to create a team_invite_params method for.
When I created the migration for the table, because I wanted to keep track of the sender and recipient, I opted for the custom field names
sender_id
and
recipient_id
to not mix up which user was which.
Anyway, when I try and permit the parameters for a team_invite, I am having trouble telling the permit method that the
Edit:
"team_invite"=>{"user"=>"2"}
is in fact what should go under recipient_id. All the methods I have tried either don't work or end up passing no value at all.
Controller
def create
#team_invite = TeamInvite.new(team_invite_params)
end
Model
class TeamInvite < ApplicationRecord
belongs_to :recipient, class_name: "User"
belongs_to :sender, class_name: "User"
end
Params:
{"utf8"=>"✓", "authenticity_token"=>"0QLA9YiTu9nAf6QJLX5rg0DWa7CAMjqGOlUBICbcL8Ucs2DsCDgGBnhiDXPa/ot8DK0xwTR1D7MASu4/9tVj0w==", "team_invite"=>{"recipient_id"=>"2"}, "commit"=>"Save Team invite"}
View (if it matters):
<%= form_for :team_invite, url: team_invites_path do |f| %>
<%= f.select :recipient_id, User.all.collect { |p| [ p.name, p.id ] }, include_blank: false %>
<%= f.submit %>
<% end %>
Migration:
class CreateTeamInvite < ActiveRecord::Migration[5.0]
def change
create_table :team_invites do |t|
t.references :team, foreign_key: true
t.integer :recipient_id
t.integer :sender_id
t.timestamps
end
end
end
You need:
params.permit(:team_invites => [ :user ]) #or whatever other nested team_invite params you want to pass in.
Docs for strong_params here: https://github.com/rails/strong_parameters

Showing index to current user, only their information

I have a model Order. In the index, I want the current_user to see the index with only their orders. Order belongs_to :admin_user. AdminUser has_many :orders. I am using activeadmin in my app, if that makes a difference. I am getting this error:
Couldn't find Order without an ID
The line giving the error is the second line in my order controller.(redacted unnecessary info)
index do
#order = Order.where(admin_user_id: current_admin_user.id, order_id: resource.id)
column "ID" do |order|
link_to order.id, admin_order_path(order)
end
column "Proof" do |order|
image_tag order.proof_url(:proof).to_s
end
column "Name" do |order|
link_to order.name, admin_order_path(order)
end
column(:customer, :sortable => :customer_id)
column "Category", :order_category
column "Status", :order_status
column "Priority", :order_priority
column "Due Date", :end_date
default_actions
end
here is my order model requested by #jamesw
class Order < ActiveRecord::Base
attr_accessible :color_back, :color_front, :color_sleeve, :end_date, :name, :start_date, :whiteboard, :customer_id, :order_category_id, :order_type_id, :order_status_id, :order_priority_id, :print_location_id, :artwork, :proof, :line_items_attributes, :assignee_id, :admin_user_id
mount_uploader :artwork, ArtworkUploader
mount_uploader :proof, ProofUploader
has_many :line_items
belongs_to :assignee, :class_name => "AdminUser"
belongs_to :customer
belongs_to :order_category
belongs_to :order_type
belongs_to :order_status
belongs_to :order_priority
belongs_to :print_location
belongs_to :admin_user
accepts_nested_attributes_for :line_items, :allow_destroy => true
scope :owned_by, lambda { |user| includes(:assignee).where("admin_users.id = ?", user.id) }
def default_values
if new_record?
self.start_date ||= Date.today
self.number ||= (Order.maximum(:number) + 1 rescue 1)
end
end
end
It looks like you're trying to filter the Orders table by order_id. Unless you've built your DB in a non-standard manner, the ID field of the orders table would typically be id (not order_id).
That issue aside, I doubt you want to be passing in an order id for the index action since that would only return a single record, and by it's nature the index action should list many records (in your case, all records for the current_admin_user).
If neither of those issues solve your problem, try commenting out the lines 1 by 1.
Try to add this controller method
ActiveAdmin.register Order do
controller do
def scoped_collection
Order.where(:admin_user => current_admin_user.id)
end
end
end
see more here:
Two pages for the same resource - ActiveAdmin
I fixed the issue by taking the line in question out and using scope_to :current_user. I am wondering though, how to add a conditional statement to still allow the admin to view this? here is a look at the controller now.
scope_to current_user
index do
column "ID" do |order|
link_to order.id, admin_order_path(order)
end
column "Proof" do |order|
image_tag order.proof_url(:proof).to_s
end
column "Name" do |order|
link_to order.name, admin_order_path(order)
end
column(:customer, :sortable => :customer_id)
column "Category", :order_category
column "Status", :order_status
column "Priority", :order_priority
column "Due Date", :end_date
default_actions
end

How can I get an ActiveRecord::Association to initialize a parent's auto-loaded children's parent references to point at the actual parent object?

This is in Ruby 1.9.3p194, with Rails 3.2.8
app/models/owner.rb:
class Owner < ActiveRecord::Base
attr_accessible :name
has_many :pets
end
app/models/pet.rb:
class Pet < ActiveRecord::Base
attr_accessible :name, :owner
belongs_to :owner
end
db/migrate/20120829184126_create_owners_and_pets.rb:
class CreateOwners < ActiveRecord::Migration
def change
create_table :owners do |t|
t.string :name
t.timestamps
end
create_table :pets do |t|
t.string :name
t.integer :owner_id
t.timestamps
end
end
end
Alright, now watch what happens...
# rake db:migrate
# rails console
irb> shaggy = Owner.create(:name => 'Shaggy')
irb> shaggy.pets.build(:name => 'Scooby Doo')
irb> shaggy.pets.build(:name => 'Scrappy Doo')
irb> shaggy.object_id
=> 70262210740820
irb> shaggy.pets.map{|p| p.owner.object_id}
=> [70262210740820, 70262210740820]
irb> shaggy.name = 'Shaggie'
irb> shaggy.name
=> "Shaggie"
irb> shaggy.pets.map{|p| p.owner.name}
=> ["Shaggie", "Shaggie"]
irb> shaggy.save
irb> shaggy.reload
irb> shaggy.object_id
=> 70262210740820
irb> shaggy.pets.map{|p| p.owner.object_id}
=> [70262211070840, 70262211079640]
irb> shaggy.name = "Fred"
irb> shaggy.name
=> "Fred"
irb> shaggy.pets.map{|p| p.ower.name}
=> ["Shaggie", "Shaggie"]
My question: How can I get rails to initialize the elements of shaggy.pets to have their owners set to shaggy (the exact object), not only when the pet objects are first built/created, but even when they are auto-loaded from the database via the association?
Bonus points: Make it work in Rails 2.3.5 as well.
If you don't care about 2.3.5 support, the following is much simpler:
app/models/owner.rb:
class Owner < ActiveRecord::Base
attr_accessible :name
has_many :pets, :inverse_of => :owner
end
app/models/pet.rb:
class Pet < ActiveRecord::Base
attr_accessible :name, :owner
belongs_to :owner, :inverse_of => :pets
end
Chris,
What you are missing is this;
When you query the database, you take a snapshot of the data in that exact moment.
Your associations do not point to the object reference, they are a shallow copy of the object queried from the database.
To make your code works, you need to save the object back to the database, so pets will appropriately fetch the updates.
I would also show more code, because I can clearly see you taking the wrong direction in what you're doing.
What you are showing is a desktop application pattern example, using patterns designed for web applications.
Figured it out. This works for me in Rails 3.2.8 and Rails 2.3.5:
class Owner < ActiveRecord::Base
attr_accessible :name
has_many :pets do
def load_target
super.map do |pet|
pet.owner = (proxy_association.owner rescue proxy_owner)
pet
end
end
end
end
Note that in Rails 2.3.5, the object_ids of shaggy.pets.map{|p| p.owner} are still different, since they are different ProxyAssociation objects, but they at least point at the same underlying object.
If you do this often, you may want to generalize...
lib/remember_parent_extension.rb:
class RememberParentExtension < Module
def initialize(parent_name)
parent_setter = "#{parent_name}="
super() do
define_method(:load_target) do
super.map do |child|
parent_proxy = (proxy_association.owner rescue proxy_owner)
child.send(parent_setter, parent_proxy)
child
end
end
end
end
end
app/models/owner.rb:
class Owner < ActiveRecord::Base
attr_accessible :name
has_many :pets, :extend => RememberParentExtension.new(:owner)
end

Select many box for HABTM

I have two models; question and category which have a HABTM association between them. Now I want to have a form where I can edit the questions categories, however I don't know how. I started with this but I am lost, I am unsure on what to name the "name" attributes etc and how it is automatically edited/created with the question, how do I set this up?
<%= f.fields_for :categories do |categories_form| %>
<%= categories_form.select "category_ids", Category.all.collect { |c| [c.description, c.id] }, {}, {:multiple => true, :size => 9} %>
<% end %>
I managed to set up question(has_many) --> answer with fields_for and accepts_nested_attributes_for, but not this.
You should take a look at the following screencasts by Ryan Bates Nested Model Form Part 1 and Nested Model Form Part 2.
Migrations
You need to create the migrations for the tables
You need to create the migration for the middle table of the association
+ the middle table name that is created by the association is :categories_questions
or :questions_categories, in the second case you must define the name in models as shown in the link
Do I need to manually create a migration for a HABTM join table?
class CreateCategoriesQuestions < ActiveRecord::Migration
def self.up
create_table :categories_questions, :id => false do |t|
t.references :category
t.references :question
end
add_index :categories_questions, [:category_id, :question_id]
add_index :categories_questions, [:question_id, :category_id]
end
def self.down
drop_table :categories_questions
end
end
Question Model
class Question < ActiveRecord::Base
has_and_belongs_to_many :categories
end
Category Model
class Category < ActiveRecord::Base
has_and_belongs_to_many :questions
end
Controller Stuf
questions_controller.rb
def new
#question = Question.new
#question.categories.build #Build a categories_questions so as to use fields_for
end
Form Stuff
= f.fields_for :categories do |categories_fields|
= categories_fields.text_field :name
= categories_fields.text_field :description
At this point i must tell you ( i am new in ruby & rails ) that to create a new object here you can use jquery to append a html block name properly, or create helpers (that use javascript in the end) to add a new object and on save, save the association.
In the next link someone demonstrated the exact way .
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for#512-Setting-child-index-while-using-nested-attributes-mass-assignment

how to use array variables inside action in controller

I want to fetch all posts posted by those users who have gone to same college as the current users...So inside my welcome controller i have written following code..
class WelcomesController < ApplicationController
def index
#col = Education.select(:college_id).where(:user_id => #current_user)
#user = Education.select(:user_id).where(:college_id => #col)
#welcome = Welcome.where(:user_id => #user)
end
end
Following is my shema code for welcome and education model:
create_table "welcomes", :force => true do |t|
t.text "message"
t.integer "user_id"
end
create_table "educations", :force => true do |t|
t.integer "college_id"
t.integer "user_id"
end
#col = Education.select(:college_id).where(:user_id => #current_user)....this line returns college ids associated with current logged in user.This is working perfectly on my console which is returning following output..
[#<Education college_id: 1>, #<Education college_id: 2>]
but i dont know how to use this output in my next line,so i have written this statement which should return all the users whose college id is the output of prevous statement
#user = Education.select(:user_id).where(:college_id => #col)
and my last line should return all the posts posted by those users whose ids are inside the #user array:
#welcome = Welcome.where(:user_id => #user)
but this is not working.When i run my project i cant see any output on my page and on console i am getting following output :
SELECT welcomes.* FROM welcomes WHERE (welcomes.user_id IN (NULL))
which means its not getting any user ids..
How can i solve this ...
You can try this:
#col = Education.select(:college_id).where(:user_id => #current_user.id).all
#users = Education.select(:user_id).where(:college_id => #col.collect(&:college_id)).all
#welcome = Welcome.where(:user_id => #users.collect(&:user_id)).all
The best way I see to accomplish this is to set up a has_many_and_belongs_to_many relationship between your User and Education models. (Each Education will have many Users and each User may have multiple Eductions.) You will need to create a joining table in your database to support this type of relationship - see the Rails Guide for more information on this.
I would set up your models in this manner:
class User < ActiveRecord::Base
has_one :welcome
has_and_belongs_to_many :educations
end
class Education < ActiveRecord::Base
has_and_belongs_to_many :users
end
class Welcome < ActiveRecord::Base
belongs_to :user
end
The join table for the has_many_and_belongs_to_many join table migration (be sure to double check this code, not sure I got this exactly right):
def self.up
create_table 'education_user', :id => false do |t|
t.column :education_id, :integer
t.column :user_id, :integer
end
end
Your controller code is now much simpler and looks like this:
#welcomes = #current_user.eductions.users.welcome.all
In your view:
<% #welcomes.each do |welcome| %>
<p><%= welcome.message %></p>
<% end %>
One of the more powerful features of Ruby on Rails is the model relationships. They are a little more work up front, but if you take the time to set them up correctly they can make your life much easier, as is evidenced by the simplified #welcomes query above.
I'd recommend you to make relation between User and Collage
class User < ActiveRecord::Base
has_many :educations
has_many :colleges, :through => :educations
has_many :posts
scope :by_college_id, lambda {|cid| where("exists (select educations.id from educations where educations.user_id = users.id AND educations.college_id in (?) limit 1)", Array.wrap(cid)) }
def college_mates
self.class.by_college_id(self.college_ids).where("users.id != ?", id)
end :through => :educations
end
class Education < ActiveRecord::Base
belongs_to :user
belongs_to :college
end
So now in your controller you can write
class WelcomesController < ApplicationController
def index
#posts = #current_user.college_mates.includes(:posts).map(&:posts).flatten
# or
#posts = Post.where(user_id: #current_user.college_mates.map(&:id))
end
end
Second variant generates 3 sql-requests, first variant - only two. But this is same work with data, I think time will be also same. Usually controllers contain only few lines of code, all logic written in models. Ideally controller should contain only Post.by_college_mates_for(#curren_user.id)

Resources