Rails Single table inheritance problem - ruby-on-rails

I'm trying to setup single table inheritance in my Rails app for a User model and its subclasses Member, Subscriber, and Staff.
I have a model file for each: user.rb, member.rb, etc
The user model is defined: class User < ActiveRecord::Base; end;
I subclassed the other models as such: class Member < User; end; and so on.
In my users table I have all the fields every class needs plus the type field. Now when I go to the console and try to create a new instance of say member or subscriber i get the following error:
TypeError: can't dup NilClass
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2184:in 'dup'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2184:in 'scoped_methods'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2188:in 'current_scoped_methods'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2171:in 'scoped?'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2439:in 'send'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2439:in 'initialize'
from (irb):6:in 'new'
from (irb):6
Rails know the subclasses models are there because in the console when I simply call Member or Subscriber, i get the class definition returned.
I've read the simple documentation, but I must be missing something?

I tried on my side starting from a scratch application and it works
Here is my User model (User.rb)
class User < ActiveRecord::Base
end
My member model (Member.rb)
class Member < User
end
I have one migration file to create my users table which contains:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.timestamps
end
end
def self.down
drop_table :users
end
end
Now launching the console:
➜ ./script/console
Loading development environment (Rails 2.3.4)
>> u = User.new
=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
>> m = Member.new
=> #<Member id: nil, name: nil, created_at: nil, updated_at: nil>
>> m.name="hop"
=> "hop"
>> m.save
=> true
However I did not manage to reproduce your error :(

Do you have a type column of type varchar (string in ruby)? Try the following commands (in a new rails project)
class Member < User
end
C:\projects\test\sti>ruby script\generate model user name:string type:string membertype:string
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/user.rb
create test/unit/user_test.rb
create test/fixtures/users.yml
create db/migrate
create db/migrate/20091019051506_create_users.rb
C:\projects\test\sti>rake db:migrate
(in C:/projects/test/sti)
== CreateUsers: migrating ====================================================
-- create_table(:users)
-> 0.0000s
== CreateUsers: migrated (0.0000s) ===========================================
C:\projects\test\sti>ruby script\console
Loading development environment (Rails 2.3.4)
>> u = User.new
=> #<User id: nil, name: nil, type: nil, membertype: nil, created_at: nil, updated_at: nil>
>> m = Member.new
=> #<Member id: nil, name: nil, type: "Member", membertype: nil, created_at: nil, updated_at: nil>
>> m.name = 'fred'
=> "fred"
>> m.save
=> true
>> u.name = 'rader'
=> "rader"
>> u.save
=> true
>> User.find :all
=> [#<Member id: 1, name: "fred", type: "Member", membertype: nil, created_at: "2009-10-19 05:17:11", updated_at: "2009-10-19 05:17:11">, #<User id: 2, name: "rader", type: nil, membertype: nil, created_at: "2009-10-19 05:17:24", updated_at: "2009-10-19 05:17:24">]
>>

Check this page, there are more than few solutions to this problem (even in comments).
http://strd6.com/2009/04/cant-dup-nilclass-maybe-try-unloadable/

I'm thinking that the problem is in one of your model definitions because of the stack trace you show. If you still are having a problem, pastie your code, and i'm sure you'll get a good answer.

I hade exactly this problem, after I extracted some functionality to a plugin.
But i my case it worked from the console, so i made sure id reloaded, with this line in init.rb
ActiveSupport::Dependencies.load_once_paths.delete(
File.expand_path(File.dirname(__FILE__))+'/app/models')

I ran into something similar a while back and this website helped:
http://www.dansketcher.com/2009/05/11/cant-dup-nilclass/
class User < ActiveRecord::Base
unloadable
...
end
Not sure why this occurs as I could not track down anything abnormal. I do believe it was a STI situation though.

Related

Model instance method returning wrong result the first time it is run, and the correct result subsequently

I have three models, related with has_many :through associations:
class Account < ApplicationRecord
has_many :account_owners
has_many :employees, through: account_owners
def is_owned_or_belongs_to_team_of_employees(employee)
employee.team.any? { |m| employees.include?(m) }
end
end
class AccountOwner < ApplicationRecord
belongs_to :account
belongs_to :employee
end
class Employee < ApplicationRecord
has_many :account_owners
has_many :accounts, through: :account_owners
def team
self.class.where(
'id IN (?)',
self. class.find_by_sql(['WITH RECURSIVE search_tree(id, path) AS (
SELECT id, ARRAY[id]
FROM employees
WHERE id = ?
UNION ALL
SELECT employees.id, path || employees.id
FROM search_tree
JOIN employees ON employees.manager_id = search_tree.id
WHERE NOT employees.id = ANY(path)
)
SELECT id FROM search_tree ORDER BY path',
self.id])
).order(:id)
end
end
I'm manually testing, in the Rails console in my development environment (using some fixtures that I first loaded on the database), the Account#is_owned_or_belongs_to_team_of_employees method.
When I run the method in the console this is what happens:
> a = Account.first
=> #<Account id: 534788375, name: "Sales Rep 1 (elena)-owned account", code: "EEE", created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55">
> e = Employee.find_by(first_name: 'Elena')
=> #<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>
> e.team
=> #<ActiveRecord::Relation [#<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>]>
> a.is_owned_or_belongs_to_team_of e
=> nil
> a.is_owned_or_belongs_to_team_of e
=> true
As you can see, the method returns nil (wrong!) the first time, and returns true (correct!) the following times.
The amazing thing is that I can correct the problem if I define the method like this:
def is_owned_or_belongs_to_team_of employee
puts "employees are #{employees.inspect}"
employee.team.any? { |m| employees.include?(m) }
end
Now the execution is correct, and the method returns consistently the same result (true in my example):
> a = Account.first
=> #<Account id: 534788375, name: "Sales Rep 1 (elena)-owned account", code: "EEE", created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55">
> e = Employee.find_by(first_name: 'Elena')
=> #<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>
> e.team
=> #<ActiveRecord::Relation [#<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>]>
> a.is_owned_or_belongs_to_team_of e
=> true
> a.is_owned_or_belongs_to_team_of e
=> true
If I remove the puts statement, we are back to square one: the method returns nil the first time, and true the following times.
And, amazingly, if I keep the puts statement but remove the inspect (that is, I just do puts "employees are #{employees}" we are also back to square one: nil the first time, and true the following times.
Any idea? What is going on here?
By the way, I'm running Ruby 2.5.1 y Rails 5.2.0.
I'm glad I stumbled upon this Unicorn of a bug!
After debugging this for hours, I found out the following:
any? had new changes in rails 5.2 release that was supposed to delegate it to Enumerable
the surprising thing, that if you put a binding.pry in the implementation of any? and call super it returns true even the first time and then the method returns nil. ~/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/relation.rb # line 228 ActiveRecord::Relation#any?:
if you add to employee.team .to_a everything works consistently.
if you put any? { |_| true } it returns true.
If you check for the value inside the block for include? it returns true but any? still returns nil!!!
If you avoid resolving the has_may through association (by calling .to_a before the block) or even using a different association inside the any? block everything works as expected.
using any other ruby version fixes the problem.
Summary
The problem was introduced in ruby 2.5.1 rails v5.2.0 when ActiveRecord::Relation started to include Enumerable.It happens with %w(none? any? one? many?) while trying to resolve a has many through association in its block.

Unable to set Rails model attribute from console or controller

I'm new to Rails and am working on getting an application set up in Rails 4.2.4. I have a model called List that looks like the following in the database (PostgresQL):
List(id: integer, user_id: integer, name: string, created_at: datetime, updated_at: datetime, friendly_name: string)
and in List.rb:
class List < ActiveRecord::Base
attr_accessor :name, :friendly_name
belongs_to :user
has_many :items
end
I am trying to modify the name attribute from a controller action:
def save_name
...
list_to_edit = List.find(params[:id].to_i)
list_to_edit.name = params[:name]
list_to_edit.save!
...
end
But the changes are not being persisted. I have confirmed that params[:name] and list_to_edit are not nil. When I try to change the attribute in the Rails console like this:
> l = List.last
> l.name = 'TestName'
> l.save!
I don't see any errors. After executing the above commands and executing l.name I do see TestName. When I type l or List.last, however I still see
#<List id: 29, user_id: 17, name: nil, created_at: "2015-11-07 18:55:04", updated_at: "2015-11-07 18:55:04", friendly_name: nil>
What do I need to do to set the name attribute of a List? I can post any additional file content if it is helpful.
After trying a few more things it looks like all I needed to do was remove name from the array being passed to attr_accessor in List.rb. I believe when I was trying to change the list name with my_list.name = 'something' I was modifying the instance variable, not the attribute stored in the database.

Building associations in a before-filter in rails4

I am migrating an app from rails3.2.13 to rails4.0.0-rc1. I am having the following code:
class Foo < ActiveRecord::Base
has_many :bars
before_create :build_bars
private
def build_bars
self.bars.build({name: 'Bar 1'})
self.bars.build({name: 'Bar 2'})
end
end
The code above worked in rails3, but creates empty records in rails4. Some try & error in the console revealed that, indeed, attributes are not assigned.
f = Foo.new
f.bars.build({name: 'Bar'})
=> #<Bar id: nil, name: nil>
What's the proper way to build associations and have them being saved together with its parent record?
i think that #Mischa is right. i've been migrating my app over to rails4 and it works:
user.authorizations.build provider: "bla"
=> #<Authorization id: nil, provider: "bla", uid: nil, user_id: 1, created_at: nil, updated_at: nil>
you can have a look at the changes i did: https://github.com/phoet/on_ruby/pull/83/files#L23L59
most probably it's removing:
# Mass assignment settings
config.active_record.whitelist_attributes = true

ActiveRecord derived attribute persistence which depends on id value

How do you persist a derived attribute which depends on the value of id in rails? The snippet below seems to work-- Is there a better rails way?
class Model < ActiveRecord::Base
....
def save
super
#derived_attr column exists in DB
self.derived_attr = compute_attr(self.id)
super
end
end
Callbacks are provided so you should never have to override save. The before_save call in the following code is functionally equivalent to all the code in the question.
I've made set_virtual_attr public so that it can be calculated as needed.
class Model < ActiveRecord::Base
...
# this one line is functionally equivalent to the code in the OP.
before_save :set_virtual_attr
attr_reader :virtual_attr
def set_virtual_attr
self.virtual_attr = compute_attr(self.id)
end
private
def compute_attr
...
end
end
I think the more accepted way to do this would be to provide a custom setter for the virtual attribute and then provide an after_create hook to set the value after the record is created.
The following code should do what you want.
class Virt < ActiveRecord::Base
def after_create()
self.virtual_attr = nil # Set it to anything just to invoke the setter
save # Saving will not invoke this callback again as the record exists
# Do NOT try this in after_save or you will get a Stack Overflow
end
def virtual_attr=(value)
write_attribute(:virtual_attr, "ID: #{self.id} #{value}")
end
end
Running this in the console shows the following
v=Virt.new
=> #<Virt id: nil, virtual_attr: nil, created_at: nil, updated_at: nil>
>> v.save
=> true
>> v
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17",
updated_at: "2009-12-23 09:25:17">
>> Virt.last
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17",
updated_at: "2009-12-23 09:25:17">

Issue with setter override on ActiveRecord

This is not exactly a question, it's rather a report on how I solved an issue with write_attribute when the attribute is an object, on Rails' Active Record. I hope this can be useful to others facing the same problem.
Let me explain with an example. Suppose you have two classes, Book and Author:
class Book < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :books
end
Very simple. But, for whatever reason, you need to override the author= method on Book. As I'm new to Rails, I've followed the Sam Ruby's suggestion on Agile Web Development with Rails: use attribute_writer private method. So, my first try was:
class Book < ActiveRecord::Base
belongs_to :author
def author=(author)
author = Author.find_or_initialize_by_name(author) if author.is_a? String
self.write_attribute(:author, author)
end
end
Unfortunately, this does not work. That's what I get from console:
>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author = "Lewis Carroll"
=> "Lewis Carroll"
>> book
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author
=> nil
It seems that Rails does not recognize it is an object and makes nothing: after the attribuition, author is still nil! Of course, I could try write_attribute(:author_id, author.id), but it does not help when the author is not saved yet (it still has no id!) and I need the objects be saved together (author must be saved only if book is valid).
After search a lot for a solution (and try many other things in vain), I found this message: http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/4fe057494c6e23e8, so finally I could had some working code:
class Book < ActiveRecord::Base
belongs_to :author
def author_with_lookup=(author)
author = Author.find_or_initialize_by_name(author) if author.is_a? String
self.author_without_lookup = author
end
alias_method_chain :author=, :lookup
end
This time, the console was nice to me:
>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author = "Lewis Carroll"=> "Lewis Carroll"
>> book
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author
=> #<Author id: nil, name: "Lewis Carroll", created_at: nil, updated_at: nil>
The trick here is the alias_method_chain, that creates an interceptor (in this case author_with_lookup) and an alternative name to the old setter (author_without_lookup). I confess it took some time to understand this arrangement and I'd be glad if someone care to explain it in detail, but what surprised me was the lack of information about this kind of problem. I have to google a lot to find just one post, that by the title seemed initially unrelated to the problem. I'm new to Rails, so what do you think guys: is this a bad practice?
I recommend creating a virtual attribute instead of overriding the author= method.
class Book < ActiveRecord::Base
belongs_to :author
def author_name=(author_name)
self.author = Author.find_or_initialize_by_name(author_name)
end
def author_name
author.name if author
end
end
Then you could do cool things like apply it to a form field.
<%= f.text_field :author_name %>
Would this work for your situation?
When you override the accessor, you have to set an actual DB attribute for write_attribute and self[:the_attribute]=, and not the name of the association-generated attribute you're overriding. This works for me.
require 'rubygems'
require 'active_record'
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
ActiveRecord::Schema.define do
create_table(:books) {|t| t.string :title }
create_table(:authors) {|t| t.string :name }
end
class Book < ActiveRecord::Base
belongs_to :author
def author=(author_name)
found_author = Author.find_by_name(author_name)
if found_author
self[:author_id] = found_author.id
else
build_author(:name => author_name)
end
end
end
class Author < ActiveRecord::Base
end
Author.create!(:name => "John Doe")
Author.create!(:name => "Tolkien")
b1 = Book.new(:author => "John Doe")
p b1.author
# => #<Author id: 1, name: "John Doe">
b2 = Book.new(:author => "Noone")
p b2.author
# => #<Author id: nil, name: "Noone">
b2.save
p b2.author
# => #<Author id: 3, name: "Noone">
I strongly recommend doing what Ryan Bates suggests, though; create a new author_name attribute and leave the association generated methods as they are. Less fuzz, less confusion.
I solved this problem using alias_method
class Book < ActiveRecord::Base
belongs_to :author
alias_method :set_author, :author=
def author=(author)
author = Author.find_or_initialize_by_name(author) if author.is_a? String
set_author(author)
end
end

Resources