Alternative serialization for DataMapper objects in Rails - ruby-on-rails

I'm working on on a caching layer in my Rails app and I'm having trouble caching original DataMapper objects. They seem to have a lot of stuff attached that make marshaling fail (I get an error about Marshal being unable to serialize a Proc object).
So I am considering writing my own pre-serialization and post-deserialization methods for caching. Specifically I will turn the DataMapper object into a list of tuples with this:
o = Foo.get(1234)
as_list = o.model.properties.map { |p| [p.name, o.send(p.name)] }
And then cache that list.
My question is: How do I reconstruct the DataMapper object in a way that allows me to use it as it if were constructed by a normal DataMapper query?
My naive approach of Foo.new(foo=bar, goo=baz) doesn't seem to connect it up with all of the foreign keys and stuff.

After some "fun" code-spelunking I seem to have found something that works:
mc.set(key, HashWithIndifferentAccess[o.attributes])
as_hash = mc.get(key)
from_cache = Foo.load([as_hash], Foo.all.query).first
The load method on the model seems to be what get uses and the query seems to be required in order to get the repository names and a few other things.

Related

How to add attribute/property to each record/object in an array? Rails

I'm not sure if this is just a lacking of the Rails language, or if I am searching all the wrong things here on Stack Overflow, but I cannot find out how to add an attribute to each record in an array.
Here is an example of what I'm trying to do:
#news_stories.each do |individual_news_story|
#user_for_record = User.where(:id => individual_news_story[:user_id]).pluck('name', 'profile_image_url');
individual_news_story.attributes(:author_name) = #user_for_record[0][0]
individual_news_story.attributes(:author_avatar) = #user_for_record[0][1]
end
Any ideas?
If the NewsStory model (or whatever its name is) has a belongs_to relationship to User, then you don't have to do any of this. You can access the attributes of the associated User directly:
#news_stories.each do |news_story|
news_story.user.name # gives you the name of the associated user
news_story.user.profile_image_url # same for the avatar
end
To avoid an N+1 query, you can preload the associated user record for every news story at once by using includes in the NewsStory query:
NewsStory.includes(:user)... # rest of the query
If you do this, you won't need the #user_for_record query — Rails will do the heavy lifting for you, and you could even see a performance improvement, thanks to not issuing a separate pluck query for every single news story in the collection.
If you need to have those extra attributes there regardless:
You can select them as extra attributes in your NewsStory query:
NewsStory.
includes(:user).
joins(:user).
select([
NewsStory.arel_table[Arel.star],
User.arel_table[:name].as("author_name"),
User.arel_table[:profile_image_url].as("author_avatar"),
]).
where(...) # rest of the query
It looks like you're trying to cache the name and avatar of the user on the NewsStory model, in which case, what you want is this:
#news_stories.each do |individual_news_story|
user_for_record = User.find(individual_news_story.user_id)
individual_news_story.author_name = user_for_record.name
individual_news_story.author_avatar = user_for_record.profile_image_url
end
A couple of notes.
I've used find instead of where. find returns a single record identified by it's primary key (id); where returns an array of records. There are definitely more efficient ways to do this -- eager-loading, for one -- but since you're just starting out, I think it's more important to learn the basics before you dig into the advanced stuff to make things more performant.
I've gotten rid of the pluck call, because here again, you're just learning and pluck is a performance optimization useful when you're working with large amounts of data, and if that's what you're doing then activerecord has a batch api you should look into.
I've changed #user_for_record to user_for_record. The # denote instance variables in ruby. Instance variables are shared and accessible from any instance method in an instance of a class. In this case, all you need is a local variable.

Join two hash Tables (dynamoDb) with Ruby on Rails

I am new to Ruby for one project only - I need to join two tables with aws dynamodb. Basically the equivalent of sql left join. But since dynamodb apparently doesn't support I need to make it happen at the array level it seems.
Currently I am querying the one just fine, but I need to bring in this other table, but I'm having a heck of a time finding a simple example for ruby with rails without using ActiveRecord (to avoid causing an overhaul on pre-existing code).
client = Aws::DynamoDB::Client.new
response = client.scan(table_name: 'db_current')
#items = response.items
fake output to protect the innocent
db_current
{"machine_id"=>"pc-123435", "type_id"=>"t-56778"}
db_type
{"description"=>"Dell 5 Dev Computer", "Name"=>"Dell", "type_id"=>"t-56778"}
I thought I might have to make two:
client = Aws::DynamoDB::Client.new
db_c = client.scan(table_name: 'db_current')
#c_items = db_c.items
client = Aws::DynamoDB::Client.new
db_t = client.scan(table_name: 'db_type')
#t_items = db_c.joins(db_t['type_id']) <=== then merge them
here.
where I'll ultimately display description/name/machine_id
But sadly no luck.
I'm looking for suggestions. I'd prefer to keep it simple to really
understand (It might sound unreasonable, I don't want to pull in ActiveRecord just yet unless I'll be owning this project going forward).
I ended up doing it this way. There is probably a more elegant solution for those that are familiar with Ruby... that I am not.
basically for each of the items in the first hash array (table), I use the ID from that one to filter on the item for the 2nd hash array. Merging them in the process. then appending to a final destination which I'll use for my UI.
#c_by_id = Array.new
#b_items.each do |item|
pjoin = #c_items.first {|h| h['b_id'] == item['b_id']}
newjoin = item.merge(pjoin)
#c_by_id.append(newjoin)
end

Rails - ActiveRecord Dirty - Getting associated objects from the changes hash

I'm working on an audit trail of sorts for an app so that the user can see what is being changed throughout the system.
I have a hash of changes from ActiveRecord Dirty, like follows:
{"ingredient_type_id"=>[nil, 199575006], "name"=>[nil, "asdfg"], "amount"=>[nil, 3.0], "unit"=>[nil, "x"], "notes"=>[nil, "asdf"]}
This works great and I can parse what I need to output and create database records with the info.
I just have one question - How can I get associated objects from this? In this case, the ingredient_type? I actually want to output something like:
"Ingredient type was changed to #{IngredientType.find(199575006).name}."
But I'm not sure how I would parse that hash on a dynamic basis to do that.
Pretty much the way you've suggested I'd have thought, But you don't need to parse the hash for the changes, Dirty gives you much more than that
if ingredient_type_id_changed?
unless ingredient_type_id.blank?
ingredient_name = IngredientType.find(ingredient_type_id).name
else
ingredient_name = 'blank'
end
end
You might even be able to do ingredient_type.name, Not sure at that point if active record dirty will let you go through the association. If you test it (or if anyone else knows) let me know

Ensure my SQL is not injected receiving array of values

I need to receive an array of values of like:
['restaurant']
['restaurant', 'pharmacy']
I would like which approach to take to ensure that when I use this:
SELECT * FROM places WHERE type IN (array_joined_with_commas_and_quotes)
I don't get injection attacks.
I am writing the sentence without any library and I am working in Rails.
I don't have Active Record, I am doing a postgis query to an external server.
How about using the ActiveRecord functions for query building?
If you use rails, the gem should still be there.
Place.where(:type => ['foo', 'bar']).to_sql
You have two basic approaches (using ? for parameterization in these examples).
If this is an array, then your ['restaurant', 'pharmacy'] becomes '{"restaurant","pharmacy"}' and then you can:
SELECT * FROM places WHERE type = ANY (?);
You could also
SELECT * FROM places WHERE type IN (? ....);
Dynamically generating the number of parameters as you go. As scones mentioned ActiveRecord may be able to automate this for you. The point though is that these methods send data separately from the query, which means that the SQL cannot be injected into the query.

Rails cache & ActiveRecord eager fetching - Fetch only if the fragment hasn't been cached

I have a controller method which currently looks like:
#people = Person.where(conditions).includes(eager_fetch).all
I'm now trying to make the controller cache-aware. Since the eager fetch is rather expensive, I want to avoid loading as much data as possible. If it's relevant, the output is XML from an RPC style endpoint. I've arrived at:
#people = Person.where(conditions).all
#fragments = {}
#people.dup.each do |person|
cache_key = "fragment-for-#{person.id}-#{person.updated_at.to_i}"
fragment = Rails.cache.fetch(cache_key)
unless fragment.nil?
#fragments[person.id] = fragment
#people.delete person
end
end
#people = Person.where(:id => #people.collect(&:id)).includes(eager_fetch).all
There's another possibility, which is very much the same, except instead of re-querying on the last line,
Person.send :preload_associations, #people, eager_fetch
Am I missing an important piece of API for handling this correctly? Currently on Rails 3.0.12, but will be upgrading to 3.2.x, so a solution that only works with 3.2.x would be fine. Neither of my solutions seem elegant to me.
(I've anonymized and simplified this code, apologies if I've left out anything important)
Don't rely on ActiveRecord's eager loading. It will load everything that isn't in the ActiveRecord per-request query cache.
Instead query for your primary object, and then use your own crafty method to fetch the cached things and query the slower datastore for the missed ID's.

Resources