ActiveRecord model subclasses don't show up - ruby-on-rails

Here is the trivial inheritance (STI) setup:
class Parent < ActiveRecord::Base
end
class Daughter < Parent
end
class Son < Parent
end
Quick try in console. Expecting Parent.subclasses to return two subclasses, but got nothing!
ruby-1.9.2-p0 > Parent.subclasses
=> []
Also, calling
ruby-1.9.2-p0 > Daughter.subclasses
=> []
,which correctly returns no children, makes Parent start recognizing Daughter as subclass:
ruby-1.9.2-p0 > Parent.subclasses
=> [Daughter(id: integer, type: string, created_at: datetime, updated_at: datetime)]
The same works for another subclass:
ruby-1.9.2-p0 > Son.subclasses
=> []
ruby-1.9.2-p0 > Parent.subclasses
=> [Daughter(id: integer, type: string, created_at: datetime, updated_at: datetime), Son(id: integer, type: string, created_at: datetime, updated_at: datetime)]
This is rails 3, but the same behavior exhibits on 2.3.10

This is a known issue
One workaround is to register the subclasses at the bottom of the base class file.
%w(daughter son).each {|r| require_dependency r } if Rails.env.development?

I suppose it's a autoloading issue. The class are load only when you really need. You can try by example with the cache_classes = true configuration and see if this result is allways the same. I suppose is not.

Related

Unable to assign value to an ActiveModel attribute (rails 4.2)

I have created a simple users model in rails 4.2. However I am unable to assign any attribute values in the rails console
2.1.5 :001 > u = User.new
=> #<User id: nil, name: nil, email: nil, auth_token: nil, created_at: nil, updated_at: nil, enabled: true>
2.1.5 :002 > u.name = 'sample'
=> "sample"
2.1.5 :003 > u.changed
=> []
2.1.5 :004 > u
=> #<User id: nil, name: nil, email: nil, auth_token: nil, created_at: nil, updated_at: nil, enabled: true>
As you can see despite setting name the value has not changed.
Here is the model file
class User < ActiveRecord::Base
self.primary_key = :id
include Tokenable
include Creatable
include Updatable
attr_accessor :name, :email, :auth_token, :created_at, :updated_at, :enabled
end
I know that this works fine in rails 3.2
One of the biggest "selling points" of ActiveRecord is that it automatically creates setters and getters in your models based on the DB schema.
These are not just your average accessors created by attr_accessor (which is plain Ruby), they cast values to the correct type and do dirty tracking among other things.
When you use attr_accessor you´re generating setters and getters that clobber those created by ActiveRecord - which means that AR will not track changes or persist the attributes.
This is what you really want:
class User < ActiveRecord::Base
include Tokenable
include Creatable
include Updatable
end
Only use attr_accessor in models when you need setters and getters for non-persisted ("virtual") attributes.
You need to save the record after assigning the new value. You can achieve that by calling update_attribute! or save! on your object. In your case:
u.name = "sample"
u.save!
or
u.update_attribute("name", "sample")
Note that update_attribute updates a single attribute and saves the record without going through the normal validation procedure

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.

Single table inheritance scopes

Is it possible to set-up a scope on a single table inheritance that returns the subclass?
For example:
class Post < ActiveRecord::Base
scope :sticky, -> { where(type: 'StickyPost') }
end
class StickyPost < Post
end
Now, when I call sticky on a collection of posts I get a collection of StickyPost instances.
But when I call posts.sticky.build, the type is set to StickyPost, but the class still is Post.
posts.sticky.build
=> #<Post id: nil, message: nil, type: "StickyPost", created_at: nil, updated_at: nil>
Update
Apparently this works.
posts.sticky.build type: 'StickyPost'
=> #<StickyPost id: nil, message: nil, type: "StickyPost", created_at: nil, updated_at: nil>
Which is strange, since the scope already sets the type, it seems a bit redundant. Any way to set this behaviour in the scope?
You can make the sticky scope return the correct class by using becomes method in the scope:
class Post < ActiveRecord::Base
scope :sticky, -> { where(type: 'StickyPost').becomes(StickyPost) }
end
The becomes method maps an instance of one class to another class in the single-table inheritance hierarchy. In this case, it maps each instance of Post returned by the sticky scope to StickyPost. With this change, calling posts.sticky.build will now return an instance of StickyPost:
posts.sticky.build
=> #<StickyPost id: nil, message: nil, type: "StickyPost", created_at: nil, updated_at: nil>

rails Model.create(:attr=>"value") returns model with uninitialized fields

This is really stumping me. The process works fine if I go about it with #new and then #save, but #create returns a model instance with all the fields set to nil.
e.g:
Unexpected behavior:
ruby-1.9.2-p0 > EmailDefault.create(:description=>"hi")
=> #<EmailDefault id: nil, description: nil, created_at: nil, updated_at: nil>
Expected behaviour:
ruby-1.9.2-p0 > e = EmailDefault.new
=> #<EmailDefault id: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p0 > e.description = "hi"
=> "hi"
ruby-1.9.2-p0 > e.save
=> true
ruby-1.9.2-p0 > EmailDefault.last
=> #<EmailDefault id: 4, description: "hi", created_at: "2011-02-27 22:25:33", updated_at: "2011-02-27 22:25:33">
What am I doing wrong?
--update--
Turns out I was mis-using attr_accessor. I wanted to add some non-database attributes, so I did it with:
attr_accessible :example_to, :cc_comments
which is wrong, and caused the situation #Heikki mentioned. What I need to do is:
attr_accessor :example_to, :cc_comments
You need to white list those properties with attr_accessible to enable mass-assignment.
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html#method-i-attr_accessible
--edit
By default all attributes are available for mass-assignment. If attr_accessible is used then mass-assignment will work only for those attributes. Attr_protected works the opposite way ie. those attributes will be protected from mass-assignment. Only one should be used at a time. I prefer the white listing with attr_accessible.

Resources