Can't find the configuration or setting for this (already googled several times). When inspecting some model objects in Rails console, why is it sometimes the model fields are displayed alphabetically and most times in other deployments, it does not?
Example:
### FIELDS NOT SORTED
#rails console
(main)> l = Loan.find '123'
=> #<Loan:0x000000066722e8
id: "8f196106c00e",
user_id: "f90084a53972",
borrower_id: "043bb77b3aac",
parent_id: nil,
score_id: "f00c39e74570",
number: 11231321,
scoring_tag: nil,
.....
but in other deployments, when I go in rails console
# FIELDS SORTED ALPHABETICALLY
(main)> l = Loan.find '123'
=> #<Loan:0x007fca8b45a468
active_servicer_id: nil,
amortization: nil,
amount: 150000.0 (BigDecimal),
application_fee: nil,
borrower_id: "asdasdajdjasd",
borrower_requested_closing_attorney: nil,
channel: nil,
closed_date: nil,
commitment_end_at: nil,
How can I make rails console display output of models sorted? This is sometimes necessary when comparing two records.
If you just want to print out the attributes, you can do that more explicitly:
Loan.find(123).attributes.sort_by(&:first).to_h
If you really want the console print to do that, you may want to override the inspect method. At least for irb and some other consoles, this is the method that determines what gets printed. If you have some other plugin or gem that the console is using, you may have to look at the docs to find the appropriate method.
Something like (modify as you wish, e.g. to better mimic the default inspect, display object_id, etc)
class Loan ...
def inspect
(self.class.name + ": " + attributes.sort_by(&:first).to_h).to_s
end
end
In Ruby's Hash, the order of its elements is arbitrary. When it is inspect-ed, (as in the output of rails console), the method inspect just uses each_pair or something similar, and so the order can be arbitrary. In practice, the order for Hash#each_pair usually seems to follow the order each Hash element is created; however there is no guarantee! Also, how Rails (or ActiveRecord) creates those instances is another issue (I guess the alphabetical order you see originates how Rails created them; it may have something to do with how they are stored in the DB). Anyway, the bottom line is you can not control how they are stored, as long as a Hash is used. But you can tweak how they are inspect-ed.
The following strategy is to redefine ApplicationRecord#inspect for any of its child classes so the inspect method of any Models are modified but of none of any other classes.
Put the following code in whatever file in your app/ directory (e.g., an obvious filename is /app/models/application_record.rb):
class ApplicationRecord
alias_method :inspect_orig, :inspect if ! self.method_defined?(:inspect_orig)
def inspect
klass = self.class
if klass == ApplicationRecord
return inspect_orig
end
attr_list = attribute_names.sort.map { |name| "#{name}: "+attributes[name].inspect }.join ', '
sprintf('<%s:0x%s %s>', klass.name, object_id.to_s(16), attr_list)
end
end
Or, instead you can run it every time you open rails console if you want to activate it only temporarily during the session.
Note the name of the parent class of ApplicationRecord may be added as follows (in the case of Rails 5.2; but I don't think inclusion of the parent class is mandatory, for the class ApplicationRecord should be defined before the file is read).
class ApplicationRecord < ActiveRecord::Base
I notice your rails console outputs look slightly different from my Rails 5.2.1, maybe due to the difference in the versions(?). In the code snippet above, I have tried to mimic the outputs you seem to get. Anyway adjust it to your liking; now you have a total control on it!
EDIT1:
The description above applies to Rails 5. In Rails 4, the class name should be
class ActiveRecord::Base
EDIT2:
In the code snippet above, it shows #object_id (n.b., it is not displayed in Rails 5.2). However, what #inspect shows for Object instances is different from #object_id, as explained in detail in this answer. If you prefer to display it, a way is this (the left side value is the format for sprintf):
'0x%016x' % (object_id << 1)
Related
I'm working with a massive legacy code base, so I am looking for advice concerning this particular issue, please, not suggestions of better high-level implementations.
A simplified version of what I'm working with:
class Order < ActiveRecord::Base
has_many :line_items
#other stuff
def balance
#some definition
end
end
class LineItem < ActiveRecord::Base
belongs_to :order
#other stuff
end
module Concerns
module LineItems
module Aggregates
extend ActiveSupport::Concern
#stuff
def balance
#some other definition
end
end
end
end
Order has a method called 'balance,' and a module of LineItem also has a method called 'balance.' It seems that most of the time (in most places in the code base), when specific_line_item.balance is called, it used the method definition under the LineItem module, but there are a couple of places where it instead calls the method from Order.
Is there any way in Ruby/Rails to specify on method call which of these two I'd like to use? OR is there probably something else going on here because Ruby doesn't have method overloading, so the problem I'm describing here isn't possible?
All relevant cases where either method is called are coming from a line_item (i.e. specific_line_item.balance), so I would think it would always choose the method closer to home, rather than making the associative jump and calling Order's 'balance' method without being told to.
EDIT:
Thanks for the responses! It seems I wasn't clear enough with my question. I understand the difference between
Order.first.balance
and
LineItem.first.balance
and that the balance method being called is the one defined within the class for that object. In the situation I'm describing, I observed, in the actual live app environment, that at a place in the code where
LineItem.find(some_id).balance
was called it output not the result that would be computed by the LineItem 'balance' method, but the one from the Order class.
So I had hoped to learn that there's some ruby quirk that might have an object call an associate's method of the same name under some conditions, rather than it's own. But I'm thinking that's not possible, so there's probably something else going on under the covers specific to this situation.
Firstly, ActiveRecord::Concern can change a lot of behaviour and you've left out a lot of code, most crucially, I don't know where it's being injected, but I can make an educated guess.
For a Concern's methods to be available a given object, it must be include'd in the object's class's body.
If you have access to an instance of the Order object, at any point you can call the balance method:
order = Orders.last # grab the last order in your database
order.balance # this will call Order#balance
And if you have the Order then you can also get the LineItem:
order.line_items.first.balance # should call the Concerns:: LineItems::Aggregates#balance
You can open up a Rails console (with rails console) and run the above code to see if it works as you expect. You'll need a working database to get meaningful orders and balances, and you might need to poke around to find a completed order, but Ruby is all about exploration and a REPL is the place to go.
I'd also grep (or ag or ack) the codebase looking for calls to balance maybe doing something like grep -r "(^|\s)\w+\.balance" *, what you want to look for is the word before .balance, that is the receiver of the "balance" message, if that receiver is an Order object then it will call Order#balance and if it is a LineItem object then it will call Concerns:: LineItems::Aggregates#balance instead.
I get the feeling you're not familiar with Ruby's paradigm, and if that's the case then an example might help.
Let's define two simple Ruby objects:
class Doorman
def greet
puts "Good day to you sir!"
end
end
class Bartender
def greet
puts "What are you drinking?"
end
end
Doorman and Bartender both have a greet method, and which is called depends on the object we call greet on.
# Here we instantiate one of each
a_doorman = Doorman.new
a_bartender = Bartender.new
a_doorman.greet # outputs "Good day to you sir!"
a_bartender.greet # outputs "What are you drinking?"
We're still using a method called greet but the receiver is what determines which is called.
Ruby is a "message passing language" and each "method" is not a function but it's a message that is passed to an object and handled by that object.
References
How to use concerns in Rails 4
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
http://guides.rubyonrails.org/command_line.html#rails-console
I am upgrading an app to Rails 4.2 and am running into an issue where nil values in a field that is serialized as an Array are getting interpreted as an empty array. Is there a way to get Rails 4.2 to differentiate between nil and an empty array for a serialized-as-Array attribute?
Top level problem demonstration:
#[old_app]
> Rails.version
=> "3.0.3"
> a = AsrProperty.new; a.save; a.keeps
=> nil
#[new_app]
> Rails.version
=> "4.2.3"
> a = AsrProperty.new; a.save; a.keeps
=> []
But it is important for my code to distinguish between nil and [], so this is a problem.
The model:
class AsrProperty < ActiveRecord::Base
serialize :keeps, Array
#[...]
end
I think the issue lies with Rails deciding to take a shortcut for attributes that are serialized as a specific type (e.g. Array) by storing the empty instance of that type as nil in the database. This can be seen by looking at the SQL statement executed in each app:
[old_app]: INSERT INTO asr_properties (lock_version, keeps)
VALUES (0, NULL)
Note that the above log line has been edited for clarity; there are other serialized attributes that were being written due to old Rails' behavior.
[new_app]: INSERT INTO asr_properties (lock_version)
VALUES (0)
There is a workaround: by removing the "Array" declaration on the serialization, Rails is forced to save [] and {} differently:
class AsrProperty < ActiveRecord::Base
serialize :keeps #NOT ARRAY
#[...]
end
Changing the statement generated on saving [] to be:
INSERT INTO asr_properties (keeps, lock_version) VALUES ('---[]\n', 0)
Allowing:
> a = AsrProperty.new; a.save; a.keeps
=> nil
I'll use this workaround for now, but:
(1) I feel like declaring a type might allow more efficiency, and also prevents bugs by explicitly prohibiting the wrong data type being stored
(2) I'd really like to figure out the "right" way to do it, if Rails does allow it.
So: can Rails 4.2 be told to store [] as its own thing in a serialized-as-Array attribute?
What's going on?
What you're experiencing is due to how Rails 4 treats the 2nd argument to the serialize call. It changes its behavior based on the three different values the argument can have (more on this in the solution). The last branch here is the one we're interested in as when you pass the Array class, it gets passed to the ActiveRecord::Coders::YAMLColumn instance that is created. The load method receives the YAML from the database and attempts to turn it back into a Ruby object here. If the coder was not given the default class of Object and the yaml argument is nil in the case of a null column, it will return a new instance of the class, hence the empty array.
Solution
There doesn't appear to be a simple Rails-y way to say, "hey, if this is null in the database, give me nil." However looking at the second branch here we see that we can pass any object that implements the load and dump methods or what I call the basic coder protocol.
Example code
One of the members of my team built this simple class to handle just this case.
class NullableSerializer < ActiveRecord::Coders::YAMLColumn
def load(yaml)
return nil if yaml.nil?
super
end
end
This class inherits from the same YAMLColumn class provided by ActiveRecord so it already handles the load and dump methods. We do not need any modifications to dump but we want to slightly handle loading differently. We simply tell it to return nil when the database column is empty and otherwise call super to work as if we made no other modification.
Usage
To use it, it simply needs to be instantiated with your intended serialization class and passed to the Rails serialize method as in the following, using your naming from above:
class AsrProperty < ActiveRecord::Base
serialize :keeps, NullableSerializer.new(Array)
# …
end
The "right" way
Getting things done and getting your code shipped is paramount and I hope this helps you. After all, if the code isn't being used and doing good, who cares how ideal it is?
I would argue that Rails' approach is the right way in this case especially when you take Ruby's philosophy of The Principle of Least Surprise into account. When an attribute can possibly be an array, it should always return that type, even if empty, to avoid having to constantly special case nil. I would argue the same for any database column that you can put a reasonable default on (i.e. t.integer :anything_besides_a_foreign_key, default: 0). I've always been grateful to past-Aaron for remembering this most of the time whenever I get an unexpected NoMethodError: undefined method 'whatever' for nil:NilClass. Almost always my special case for this nil is to supply a sensible default.
This varies greatly on you, your team, your app, and your application and it's needs so it's never hard and fast. It's just something I've found helps me out immensely when I'm working on something and wondering if amount could default to 0 or if there's some reason buried in the code or in the minds of your teammates why it needs to be able to be nil.
In my model I have:
class Log < ActiveRecord::Base
serialize :data
...
def self.recover(table_name, row_id)
d = Log.where(table_name: table_name, row_id: row_id).where("log_type != #{symbol_to_constant(:delete)}").last
row = d.data
raise "Nothing to recover" if d.nil?
raise "No data to recover" if d.data.nil?
c = const_get(table_name)
ret = c.create(row.attributes)
end
And in my controller I calling it as:
def index
Log.recover params[:t], params[:r]
redirect_to request.referer
end
The problem is, if I access this page for the first time, I am getting error specified below, but after refresh, is everything OK. Where can be problem?
undefined method `attributes' for #<String:0x00000004326fc8>
In data column are saved instances of models. For the first time column isn't properly unserialized, it's just yaml text. But after refresh everything is fine. That's confusing, what is wrong? Bug in rails?
It's not every time, sometimes in first access everything is okey.
Deyamlizing an object of class Foo will do funny things if there is no class Foo. This can quite easily happen in development becauses classes are only loaded when needed and unloaded when rails thinks they might have changed.
Depending on whether the class is loaded or not the YAML load will have different results (YAML doesn't know about rail's automatic loading stuff)
One solution worth considering is to store the attributes hash rather than the activerecord object. You'll probably avoid problems in the long run and it will be more space efficient in the long wrong - there's a bunch of state in an activerecord object that you probably don't care about in this case.
If that's not an option, your best bet is probably to make sure that the classes that the serialized column might contain are loaded - still a few calls to require_dependency 'foo' at the top of the file.
I have a model and in that model I'm generating a more complex field than I've done before. I've serialized hashes and arrays, but this field is the result of Gibberish::RSA.generate_keypair ( https://github.com/mdp/gibberish ). Which is more or less a private/public key pair in a ruby wrapper, to my understanding.
Working from the command line, I can do an update_attributes and the result of the generation gets stored in the text field. When doing rake db:seed or creating an instance, this doesn't work, I get a yaml string that indicates several types of Gibberish objects.
How do I do more complex activerecord serialization beyond hashes and arrays? Or how do I approach a greater understanding of what I'm trying to do?
Code:
def generate_keypair
self.update_attributes(:rsakey => Gibberish::RSA.generate_keypair(1024) )
end
which I call on the associated model creation, basic call the Gibberish wrapper
Then the output I get for the field myresource.rsakey
"--- !ruby/object:Gibberish::RSA::KeyPair\nkey:
!ruby/object:OpenSSL::PKey::RSA {}\ncipher:
!ruby/object:OpenSSL::Cipher::Cipher {}\n"
Updating the attributes works from the rails command line, but not while seeding or creating. Other ways I attempted to add serialize so far have completely ruined the process or the created instances.
EDIT: solved bluntly by just calling 'to_s' on the result of the keypair generation method, which just saves it as a text field that 'works for now' until it needs to be more elegant.
The underlying issue seems to be that the openssl library doesn't implement YAML dumping:
YAML.dump(OpenSSL::PKey::RSA.generate(1024))
#=> "--- !ruby/object:OpenSSL::PKey::RSA {}"
If you are using rails 3.1 you can define custom serializers, like so
class KeySerializer
def dump(key)
key.to_pem
end
def load(data)
data && OpenSSL::Pkey::RSA.new(data)
end
end
Then in your class you can do
class Foo < ActiveRecord::Base
serialize :key, KeySerializer.new
end
I'm trying to use set_table_name to use one generic model on a couple different tables. However, it seems as though set_table name only works on the class once per application session. For instance in a rails 3 console (ruby 1.8.7) the following happens:
GenericModel.set_table_name "table_a"
puts GenericModel.table_name # prints table_a
pp GenericModel.column_names # prints the columns associated with table_a
GenericModel.set_table_name "table_b"
puts GenericModel.table_name # prints table_b
pp GenericModel.column_names # still prints the columns associated with table_a
Currently the workaround I've found is to also add .from(table_b) so that queries don't error out with 'table_b.id doesn't exist!' because the query still thinks it's FROM table_a.
Can others reproduce the issue? Is this the intended behaviour of set_table_name?
UPDATE
Adding
Model.reset_column_information
after set_table_name forces the model to work as I expect.
Reference found in http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000368
This is probably an undocumented limitation. Once the SHOW FIELDS FROM has been executed, which is where the results from column_names comes from, it is usually cached, at least for the duration of the request. If you must, try using the console reload! method to reset things.
your choice
rename_table
more info at AR TableDefinition