Why does `to_json` on DataMapper objects and collections cause infinite queries? - ruby

I'm using DataMapper in a Rails project, and have found that calling to_json on either a model instance or collection results in weird behavior: either a circular reference error from JSON, or an infinite series of the same query.
Assuming this was a problem with my model definitions, I opened a Rails console and created the simplest possible model:
class Foo
include DataMapper::Resource
property :id, Serial
property :name, String
end
Foo.auto_migrate! # create db table `foos` for this model
After saving an instance of this model, I did this:
f = Foo.first
f.to_json
At this point, the process appears to hang. If I tail -f log/development.log, I see this query executing over and over:
SQL (0.084ms) SELECT `id`, `name` FROM `foos` ORDER BY `id`
Ruby's memory usage continues to grow until I interrupt the command or kill the Ruby process. This is the same problem I had with my actual models, so I don't think it was related to a wrong model definition.
What causes this bizarre behavior?

It seems that the Object#to_json method, as implemented by ActiveSupport, serializes all the attributes of the object. Datamapper maintains a self-referential variable (#_repository) that sends the method into a recursive tailspin.
Good news is that the dm-serializer gem (as you mentioned) provides the necessary functionality, and the Rails has already stated how to fix the problem in the dm-rails project (it just hasn't happened yet).

Related

Why do I need to initialize a Cucumber DataTable with a Cucumber DataTable?

We recently started to drag a very old Rails app up to date (or at least out of obsolescence). One of the changes was an update of Cucumber from 1.3.x to 2.99.
Many of our rspec specs on this app used a helper for loading test data which leveraged Cucumber::Ast::DataTable. (The helper declared its own subclass which inherited from Cucumber::Ast::DataTable.) This was deprecated, so as suggested, I replaced the inheritance with Cucumber::MultilineArgument::DataTable.
The subclass looks like this:
class ParsedTable < ::Cucumber::MultilineArgument::DataTable
def initialize(string)
super(string.split("\n").map do |line|
line = line.split('|').map(&:squish)
line.slice(1, line.length)
end)
end
end
Then there are a bunch of test helpers which create test data like this (assume "role" is a model we'll be testing against):
def create_roles(string)
table = ParsedTable.new(string)
table.hashes.each do |hash|
create :role,
name: hash['Name'],
short_name: hash['Short Name'],
contracted_work_day: hash['Contracted workday in minutes']
end
end
These helpers get called like this:
create_roles <<-eos
| Name | Contracted workday in minutes |
| Therapist | 390 |
eos
But when that kind of call goes in, I get ArgumentError: data must be a Core::Ast::DataTable. The stack says this exception is from lib/cucumber/multiline_argument/data_table.rb:93:in 'initialize' which is the super call in the ParsedTable definition.
I've been trying to chase this around the Cucumber source and I can't figure out what I'm doing wrong.
The API documentation for Cucumber states that the constructor for Cucumber::MultilineArgument::DataTable takes a Core::Ast::DataTable object.
Source: https://www.rubydoc.info/gems/cucumber/Cucumber/MultilineArgument/DataTable#constructor_details
You will need an instance of Core::Ast::DataTable rather than custom parsing it from a string.
Creates a new instance. +raw+ should be an Array of Array of String or an Array of Hash You don't typically create your own DataTable objects - Cucumber will do it internally and pass them to your Step Definitions.
Source: https://www.rubydoc.info/gems/cucumber-core/1.3.1/Cucumber/Core/Ast/DataTable#constructor_details
It looks like cucumber should have already parsed the table as a Core::Ast::DataTable object in the step binding, so all you should need to do is pass this along to the constructor of your ParsedTable class.
When this isn't available, then you'll need to provide an array of array of strings instead.
Greg Burghardt's answer has the right cause for this; the Cucumber::MultilineArgument::DataTable class requires a Core::Ast::DataTable as an argument, which wasn't true of the old Cucumber::Ast::DataTable. (This class in general is a bit of a moving target between Cucumber 1.4.x and the current version.)
In the end I solved this by avoiding the problem. Looking carefully at the methods which used the custom ParsedTable class, I saw they didn't really depend on the class, but instead counted on calling hashes on each instance. What I needed was something which responded to hashes with the appropriate response.
So, rather than subclassing Cucumber::MultilineArgument::DataTable simply to get a custom constructor, I replaced the subclass definition with a method which accepted a string, split it into rows, and fed that Array of rows to Cucumber::MultilineArgument::DataTable#from. That returned an instance of Cucumber::MultilineArgument::DataTable which I could call hashes on. And that works.

Using Grape to create an API with just Ruby objects instead of a database or Rails

I am trying to use Grape to create an API using only Ruby objects. I do not want to use a database/Rails/ActiveSupport/etc -- just Rack, Ruby, and Grape.
I have defined a class for a Directory that I want to interact with through the API. So Directory#sort_by("last_name") returns JSON data with a list of People objects in my Directory. I also have a method Directory#create_person(attributes) that takes a string and uses it to add Person objects to the Directory. The directory is populated with people upon creation.
I am new to working with Rack and Grape, so I'm not sure where/how to create my Directory object and have it accessible through the GETs/POSTs in my Grape API class. Using a class variable inside this class appears to work, i.e.,:
module API
class DirectoryAPI < Grape::API
format 'json'
##directory = Directory.new("1.txt", "2.txt", "3.txt")
get 'last_name' do
##directory.sort_by("last_name")
end
end
end
but using class variables just seems wrong. Is there any better/cleaner way to create my Directory object? Perhaps inside my config.ru file? Or could I do it through a class method inside of Directory somehow?
What you are looking for is a singleton:
Usually singletons are used for centralized management of internal or external resources and they provide a global point of access to themselves.
Unfortunately, Ruby just doesn't play well with singletons. But you can use a "class consisting of only class methods," the second strategy advocated in this article.
I believe that you are working on a coding challenge that I completed a few months ago. In my answer, I used a "class consisting of only class methods" called API::Store. Here's the output from rspec -fd:
API::Store
::add
adds record to store
appends data line to file
::has_line?
instantiates a record from the data line
without the record in the store
should equal false
with the record in the store
should equal true
::new
should raise NoMethodError
::records
with original file
on initial access
should eq Records
on subsequent access
should eq Records
when file replaced
should eq OtherRecords
Finished in 0.07199 seconds (files took 2.68 seconds to load)
9 examples, 0 failures
Note that Store can't be instantiated; it throws a NoMethodError if you try. That's not a problem, though. In the Grape endpoint you can call Store.records to access the data.
As for sorting the records, this should be done in another class. Why should a Store or a Directory be sorting the data in its files?
Finally, you asked where to do the initial preparation (not initialization, of course). You can prepare your singleton in config.ru, so that it is ready when the application starts:
# config.ru
# load application code here
file = File.open('data/records.txt', 'a+')
at_exit { file.close }
API::Store.file = file
run API::Base
The challenge's instructions say "You may use any resources you need to complete it," so presumably, asking on Stack Overflow is allowed. If you are doing this challenge for a job application, please do mention so when you ask questions, because it's only fair for those answering to be informed. It would be wise to also mention at your interview that you got help on SO. Good luck, and happy coding.
The main problem I see with your example is not the use of class variables exactly, but instantiating your data inline in the API controller code. Ideally the data should be more self-contained, so you can access the exact same data from other places in your code. If you make an API similar to a light-weight data access module, then you will be using a familiar pattern in your route controllers - also it will become easy to migrate to using SQL or other data store if and when you need to.
There are lots of valid approaches, but I might create a new singleton object to represent your data source, and connect that to your hard-coded data as if it were tables. The end result here would feel a little like using Sequel (but you could follow any other pattern that you prefer):
inline_data.rb
module InlineDB
TABLES = Hash[
:directory => Directory.new("1.txt", "2.txt", "3.txt")
]
def self.[] table_name
TABLES[table_name]
end
end
app.rb
require_relative 'inline_data' # actual path may be more structured
module API
class DirectoryAPI < Grape::API
format 'json'
get 'last_name' do
InlineDB[:directory].sort_by("last_name")
end
end
end

Caching ActiveRecord model instance methods

Say I have a user model. It has an instance method called status. Status is not an association. It doesn't follow any active record pattern because it's a database already in production.
class User < ActiveRecord::Base
def status
Connection.where(machine_user_id: self.id).last
end
end
So I do this.
#users = User.all
First of all I can't eager load the status method.
#users.includes(:status).load
Second of all I can't cache that method within the array of users.
Rails.cache.write("user", #users)
The status method never gets called until the view layer it seems like.
What is the recommended way of caching this method.
Maybe this instance method is not what I want to do. I've looked at scope but it doesn't look like what I want to do.
Maybe I just need an association? Then I get the includes and I can cache.
But can associations handle complex logic. In this case the instance method is a simple query. What if I have complex logic in that instance method?
Thanks for any help.
Have You tried to encapsulate this logic inside some plain Ruby object like this (I wouldn't use this for very large sets though):
class UserStatuses
def self.users_and_statuses
Rails.cache.fetch "users_statuses", :expires_in => 30.minutes do
User.all.inject({}) {|hsh, u| hsh[u.id] = u.status; hsh }
end
end
end
After that You can use some helper method to access cached version
class User < ActiverRecord::Base
def cached_status
UserStatuses.users_and_statuses[id]
end
end
It doesn't solve Your eager loading problem, Rails doesn't have any cache warming up techniques built in. But by extracting like this, it's easily done by running rake task in Cron.
Also in this case I don't see any problems with using association. Rails associations allows You to submit different options including foreign and primary keys.

Thinking Sphinx index conflicting with Ruby method name

I ignorantly named a model in my Rails app System, which is also a ruby core method. This model is in a relationship with another model Project, which I am trying to index.
Ideally, I am looking to setup my index like this:
define_index do
indexes :name, :sortable => true
indexes system(:name), :sortable => true, :as => :system_name
end
I could change the model name, but I'd call that a compromise, and I'm not convinced I need to. Is there a good work-around for this?
ruby 1.8.7, rails 3.0.7, thinking_sphinx 2.0.3
The good work around for naming variables or user-level Classes with reserved words (language keywords and platform-level methods/classes) is not do it in the first place.
The second best workaround is to use scoping :: to make sure the name you are calling is the one you want
::system() # calls the actual system method as defined by Ruby
APPNAME::MODEL_NAME # would call the model defined as `APPNAME::MODEL_NAME`
I can't really think of a workaround without namespacing your models (although knowing Ruby, its more than possible some functionality exists-- just never needed it myself). Prolly all of them tbh since it would get even more confusing if only half your models were namespaced. In the long run, its just more typing remembering to namespace everything.

Using Modules to Define Properties with Datamapper

so I'm setting up some models and they are based off of 2 abstract base classes (or rather they used to be classes). After running into a lot of trouble with Datamapper's handling of STI for my use case, which appears to be an open bug on their lighthouse page, I decided instead to just do modules to define all the properties to keep my models DRY. Unfortunately, I'm having a scoping issue, and what complicates matters worse is that I have to use 2 levels of inheritance. Here's my code:
module Part
def self.included(receiver)
receiver.class_eval do
include DataMapper::Resource
property :id, Serial
#other junk
end
end
end
module HardDrive
def self.included(receiver)
receiver.class_eval do
include Part
property :kind, Enum[:magnetic, :flash]
#buncha crap here
end
end
end
class Fujitsu
include HardDrive
property :rev, String
end
The error I get is:
uninitialized constant HardDrive::Enum (NameError)
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.4/lib/active_support/dependencies.rb:80:in `const_missing'
from ./app/models/hard_drive.rb:6:in `included'
from ./app/models/hard_drive.rb:4:in `class_eval'
from ./app/models/hard_drive.rb:4:in `included'
from ./app/models/hard_drives/fujitsu.rb:2:in `include'
from ./app/models/hard_drives/fujitsu.rb:2
I'm at a loss here. Anyone know of how I could solve this or better yet, a smarter way I could do this?
It seems to me that Enum is defined under the DataMapper modules and the HardDrive scope does not resolve it. (Want to know why ?)
Just put DataMapper::Enum instead of Enum and it should work.
In a more general discussion, are you sure you really need these abstractions ? One drawback I see in your code is that you won't be able to query your database for parts and harddrives because the logic is stored in ruby modules instead of in the database.
Update (after comment from author)
The general answer is: forget about STI. While ORM are nice to have, the best part of them is SQL backend abstraction. While they give you the impression that you can have a persisten object model, the abstractions often leak and STI is a good example. I won't go in large details here but you can find resources online. Best is that you stay close enough to SQL modelling best practices, like one-one, one-many and many-many relationsships.
Here is an updated version. I didn't test it and the method names are probably wrong, but you will get the idea:
class Part
property :serial_number
has_one Manufacturer
end
class HardDisk
property :technology
property :platters
property :model
#...
is_one Part
end
class Manufacturer
property :name #Fujitsu, ...
property :website
#...
has_many HardDisk, [:trough=>Part]
end

Resources