Rails 4 - How to store an array on postgres - ruby-on-rails

I need to save this array:
params[:products]
The array contains these information:
[{"'name'"=>"Product Name 1 ", "'id'"=>"2", "'quantity'"=>"2", "'accessory'"=>{"'id'"=>"8", "'name'"=>"Accessory Name 1"}}, {"'name'"=>"Product Name 2 ", "'id'"=>"5", "'quantity'"=>"1", "'accessory'"=>{"'id'"=>"40", "'name'"=>"Accessory Name 2"}}]
As you can see, accessory is another array.
The process is this: A front-end guy is givin me that array, So I want to store all data on order.rb model.
So, I have a couple of questions:
Do I need to have "array type field" on database?.
Which fields do I need?
I was looking for some examples and I've been trying this on my order model:
serialize :product
order = Order.new
order.product = [:product]
order.save
order.product
I red about this method too: http://api.rubyonrails.org/classes/ActiveRecord/Store.html
Maybe this is a basic question but I really don't know how to solve it. As you can see, I don't have code in any controller because I really don't know what I need to write.
Thank you for your help.

Besides hstore, another solution would be JSON, specifically I suggest you use the PostgreSQL type "jsonb" because it's more efficient, see the documentation:
There are two JSON data types: json and jsonb. They accept almost identical sets of values as input. The major practical difference is one of efficiency. The json data type stores an exact copy of the input text, which processing functions must reparse on each execution; while jsonb data is stored in a decomposed binary format that makes it slightly slower to input due to added conversion overhead, but significantly faster to process, since no reparsing is needed. jsonb also supports indexing, which can be a significant advantage.
(more info here https://www.postgresql.org/docs/current/static/datatype-json.html )
So you have, similarly to hstore, a data structure that you can execute queries against, and this queries are quite fast as you can read above. This is a significant advantage over other strategies, e.g. serializing a Ruby hash and saving it directly in the DB.

Charles,
I suggest you to consider using hstore type of your postgres. There are few benefits of using it (performance, indexing of objects etc..).
enable_extension 'hstore'
This actually enables your PSQL have this support.
Your migration is going to be like this:
class AddRecommendationsToPages < ActiveRecord::Migration
def change
add_column :pages, :recommendations, :hstore
end
end
And after that you can pass into your hstore anything you want..
irb(main):020:0> Page.last.recommendations
Page Load (0.8ms) SELECT "pages".* FROM "pages" ORDER BY "pages"."id" DESC LIMIT 1
=> {"products"=>"34,32"}
irb(main):021:0> Page
=> Page(id: integer, block: string, name: string, slug: string, title: string, h1: string, annotation: text, article: text, created_at: datetime, updated_at: datetime, position: integer, parent: integer, show: boolean, recommendations: hstore)
irb(main):022:0> Page.last.recommendations["products"]
Page Load (0.6ms) SELECT "pages".* FROM "pages" ORDER BY "pages"."id" DESC LIMIT 1
=> "34,32"
irb(main):023:0>

Related

ActiveRecord model.update_column with subquery

I want to update a column in a Supply model that updates with its stock based on a sum of a subset. The thing is that actually hits two queries to make it works.
total_stock = History.sum(:quantity) # first query
Supply.update_columns(stock: total_stock) # second query
Then, I tried to write a subquery so:
total_stock_subquery = mysubset.select(Arel.sql('SUM(quantity)')).to_sql
Supply.update_columns(stock: total_stock_subquery) # it should be only one query
But, ActiveRecord will replace the total_stock_subquery variable as a string inside the resulting sql statement, then query fails.
I tried to wrap the total_stock_subquery parameter inside an Arel.sql thinking that ActiveRecord will detect that the parameter is not a string, but Arel.sql, and would escape this as raw sql, but it didnt work.
Supply.update_columns(stock: Arel.sql(total_stock_subquery)) # dont work too
Then, do you know how can I pass an sql subquery as parameter for a column inside .update_column, o maybe how to tell ActiveRecord that some parameter should be treated as pure sql not as string?
I know I could write this update as raw sql but I think it would be off Rails style.
EDIT: Table definitions
History
id: integer
supply_id: reference
quantity: decimal(9,2)
Supply
id: integer
product: string
stock: decimal(9,2)
class History
belongs_to: supply
end
class Supply
has_many: histories
end
We use the History model to track in and out (positive for in, negative for out) of supplies, then the sum of the histories will be the current stock.
Of course I can implement an after_save on History model to update the stock when some quantity changes it, but for this example, Im implementing a "rebase" or "recalculate" method for all supplies stock, then I need to make the sum for each Supply.

Rails and Postgres, group by hstore

I have an 'Audit' model with name:string and data:hstore attributes - as follows:
#<Audit
id: 1,
name: "my audit",
data: {"publisher"=>"some publisher", "display_version"=>"17.012.20098"},
created_at: "2017-10-10 13:09:56",
updated_at: "2017-10-10 13:09:56">
I want to produce a report that contains 3 columns:
name, display_version, count
To achieve this I assume that I need to group by "name" and "data -> 'display_version'"
I am struggling with the query and AR syntax.
What I have so far is:
Parent.audits.group(:name, :data -> 'display_version)'
Even this doesn't work. Can someone help? I have searched for examples without any luck.
You can pass a string containing the SQL grouping expression to group so you could say:
Parent.audits.group(%q{name, data -> 'display_version'})
or:
Parent.audits.group(%q{audits.name, audits.data -> 'display_version'})
And then you could throw a #count call on the end to get the data you want:
Parent.audits.group(%q{name, data -> 'display_version'}).count
Use jsonb instead of hstore (for a variety of reasons)
'Audit' model with name:string and data:jsonb
Audit.rb
store_accessor :data, :publisher, :display_version
Then try:
Parent.audits.pluck(:name, :publisher, :display_version)
Good Resource on JSONB and Postgresql
hstore columns don’t allow a nested structure; they also store all values as strings, which will require type coercion on both database and application layers. With json/jsonb columns you don’t have this problem; numbers (integers/float), booleans, arrays, strings and nulls are accepted, and you can even nest structures in any way you want.
The recommendation is that you stop using hstore in favor of jsonb;
just remember that you have to use PostgreSQL 9.4+ for this to work.

In a Rails form, can select options be saved to the database as a hash?

Let's say I have the following select boxes in my form for a Gate object:
f.select(:type_use, options_for_select([['Enter & Exit', 8],
['Only Enter', 4],
['Only Exit', 4],
['Only Enter for visitors', 2],
['Only Exit for visitors', 2]],
#gate.type_use),
{ :include_blank => '- select -' },
{ :class => 'form-control'})
These options will never change. The integers like 4 and 8 represent the number of cycles per day, and I later use these integers to do calculations. However I still want to retain the correct string labels for each integer for use in my view, so i can show that a gate has an 'Only Exit' type of use, for example.
So, is it possible to save select options as a hash, or does Rails have some other way to map integers to strings for use in my application?
Previously what i've been doing is just saving the string values, and then using if statements, manually assigning to a variable the corresponding integer based on the string value, before doing calculations. But I have yet to discover a more efficient and elegant way to do this.
In my research I did find information about Enums, but I don't think it applies to this situation because I have duplicate integers and Enums seem more like sequential indexes to me. Also it seems that other questions about hashes in select boxes have revolved around populating options for select with a hash, rather than saving as a hash
You can save Hash and Array by using Serialize on the Model you want to save the Hash or Array
Ex:
class User < Active::Record
serialize :data, Hash # for hash
serialize :other_data, Array # for array
end
There is also a DataType called hstore in postgres that allows you to save Hash data and then be able to perform searches and index items in the Hash
To use it you first have to enable the hstore extension and then use it in your migrations
.... add_column :data_set, :hstore, default: {}

Not able to index multiple timestamp for an object in thinking sphinx

I have two models user and orders.
I have one in User model as has_many orders.
I am creating one index in thinking sphinx like :
has association(:created_at), as: :order_time, type: :timestamp
Now I want to search for users who have created any order in some time range. and using above index as
User.search with: {:order_time => t1..t2}
But, this is not giving accurate results. Any idea what am I doing wrong here.
Also I tried writing a sql query also something like
user_order_time = <<-SQL
SELECT orders.created_at
FROM orders
WHERE (orders.creator_id = users.id)
SQL
and added index in this way
has "#{user_order_time}", as: :order_time, type: :timestamp
and tries to use this index, even this isn't working.
Can anyone tell me the problem with each approach.
Firstly, this answer is written presuming you're using SQL-backed indices (using the :with => :active_record option in your index definition) rather than real-time indices, and you're using Thinking Sphinx v3.
To cover your second approach first:
user_order_time = <<-SQL SELECT orders.created_at FROM orders WHERE (orders.creator_id = users.id) SQL
has "#{user_order_time}", as: :order_time, type: :timestamp
This will not work. You can refer to SQL snippets in attributes and fields, but only the sections that go in the SELECT clause. You cannot use full SQL queries.
However, with this approach, you're on the right track:
has association(:created_at), as: :order_time, type: :timestamp
Are you using association there just when writing this question, not in your actual code? Because it should be something like this:
has orders.created_at, as: :order_time
I've not specified the type - Thinking Sphinx will automatically detect this from the database.
If that doesn't work, it's worth looking at the generated SQL query in the Sphinx configuration file for clues as to why it's not returning the values you're expecting (locally, that's config/development.sphinx.conf, and you're looking for the sql_query setting in source user_core_0).

When creating objects, created_at stores microsecond precision that json can't match

I just noticed something which is causing me a tad bit of trouble.
I am creating an object. This object goes into the database with this:
created_at: "2015-11-12 08:37:35.413663"
I didn't realize it stores beyond .413
You can confirm this is saved and not simply for our viewing pleasure by doing the following: object.created_at.nsec #=> 413663000
When my front-end receives my JSON, the created_ at attribute becomes: "2015-11-12T08:37:35.413Z", which I would expect. The problem here is that we lose valuable information because now we don't have the 633 microseconds.
Indeed, if you do "2015-11-12T08:37:35.413Z".to_time.nsec #=> 413000000.
What this means is when you do the following, you're going to get no item:
Object.where(created_at: object.created_at.as_json).first #=> nil
How does one mitigate this? How does one account for the microseconds, or rather, query for created_at, with only millisecond precision hope.
Ultimately, I decided on changing the datetime columns in my DB.
I basically applied the following wherever relevant in a migration:
change_column table, column, :datetime, limit: 3
That successfully makes sure that my :datetime columns all only go to the milliseconds.
I think it is a good change because when the sole purpose of my back-end is to render a JSON API, then I think through and through it should be cohesive. JSON API standards are ISO 8601. As such, I'd like my DB to adhere to that.
DateTime.now.to_s doesn't return the fractional seconds, as you need more precision, you can use DateTime.now.iso8601(10) at your created parameter.
m = ModelName.create(created_at: DateTime.now.iso8601(10))
or
m = ModelName.create()
m.created_at = DateTime.now.iso8601(10)
m.save
I have tried somewhat similar what have you described to test:
p=Product.create(created_at: DateTime.now.iso8601(10))
p.created_at.nsec
=> 61103188
Product.where(created_at: p.created_at)
=> #<ActiveRecord::Relation [#<Product id: 172, manufacturer: nil, created_at: "2015-11-12 10:11:31", updated_at: "2015-11-12 10:11:31", avatar: nil>]>

Resources