Is a ruby's Timestamp class equivalent to PostgreSQL's timestamp? - ruby-on-rails

If I make a model and create a field like "name" along with a Timestamp, will it be equivalent to a timestamp created manually through a query like:
CREATE TABLE whatever (
name varchar(25)
created timestamp NULL DEFAULT CURRENT_TIMESTAMP
)
In other words, if I'm dealing with a database that has the timestamp pre-created by hand, how do I create a model to match pre-existing records?
I imagine it's important so that I'm able to ActiveRecord queries without anything breaking on me.

ActiveRecord will understand the timestamp part of the schema so you should get an ActiveSupport::TimeWithZone out of the database just like you'd get with any other timestamp column. However, AR won't understand default current_timestamp so it will set your created column to NULL (or nil in Ruby land) and the default current_timestamp will never be triggered; I usually get around that with an after_initialize hook:
after_initialize :set_defaults, :if => :new_record?
#...
def set_defaults
self.created = Time.now
end

Related

Issue with check times overlaps

There are two models:
# Table name: activities_people
#
# activity_id :integer not null
# person_id :integer not null
# date :date not null
# id :integer not null, primary key
# == Schema Information
#
# Table name: activities
#
# id :integer not null, primary key
# name :string(20) not null
# description :text
# active :boolean not null
# day_of_week :string(20) not null
# start_on :time not null
# end_on :time not null
Relations:
activity.rb
has_many :activities_people
has_many :people, through: :activities_people
activity_people.rb
belongs_to :person
belongs_to :activity
I try to create validation that person can join to one activity taking place in specific date and time(start_on, end_on). If I will try sign up to another activity while before I joined to other exercises(same date, and times overlap) should throw error.
What I try:
def check_join_client
activities_date = person.activities_people.where('date = date', date: date)
if activities_date.exists && person.activities.where('id IN (?)', activities_date)
end
I don't know how to use create query(person.activities.where ...) to getting person activities related with activies_people. activities_date check if we joined to activities taking place in same date. Second I want get check start_on and end_on.
Thanks in advance.
If I'm understanding you correctly, you want to find the activites_people for a user that match a query by the date array and then raise an error unless an associated activity for those matched activities_people.
Your original code for check_join_client uses if incorrectly:
def check_join_client
activities_date = person.activities_people.where('date = date', date: date)
if activities_date.exists && person.activities.where('id IN (?)', activities_date)
end
To translate this to pseudocode, you're essentially saying:
result = query if result.condition_met?
However the if condition (the expression after the if) will be evaluated before you define results. It might be more clear if I show a correct approach:
result = query
return result if result.condition_met?
Now to go back to your question about loading associated records, try something like this:
activities_people_ids_matching_date = person.activities_people
.where(date: self.date)
.pluck(:id)
# use pluck to get an array of ids because that's all that's needed here
# not sure how you're getting the date variable, so I'm assuming self.date will work
# I can use `.any?` to see if the ids list is not empty.
condition_met = activities_people_ids_matching_date.any? &&\
person.activities
.where(activities_people_id: activities_people_ids_matching_date)
.any?
condition_met ? true : raise(StandardError, "my error")
There surely is a way to get this done with one query instead of two, but it seems like where you're at with Ruby concepts it's more important to focus on syntax and core functionality than SQL optimization.
The correct syntax (one of several options) is:
person.activities_people.where(date: date)

How to enforce uniqueness of an entire row?

I've seen other SO questions like - How do you validate uniqueness of a pair of ids in Ruby on Rails? - which describes adding a scoped parameter to enforce uniqueness of a key pair, i.e. (from the answer)
validates_uniqueness_of :user_id, :scope => [:question_id]
My question is how do you do this kind of validation for an entire row of data?
In my case, I have five columns and the data should only be rejected if all five are the same. This data is not user entered and the table is essentially a join table (no id or timestamps).
My current thought is to search for a record with all of the column values and only create if the query returns nil but this seems like a bad work around. Is there an easier 'rails way' to do this?
You'll need to create a custom validator (http://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations):
class TotallyUniqueValidator < ActiveModel::Validator
def validate(record)
if record.attributes_for_uniqueness.values.uniq.size == 1
record.errors[:base] << 'All fields must be unique!'
end
end
end
class User
validates_with TotallyUniqueValidator
def attributes_for_uniqueness
attributes.except :created_at, :updated_at, :id
end
end
The important line here is:
if record.attributes_for_uniqueness.values.uniq.size == 1
This will grab a hash of all the attributes you want to check for uniqueness (in this case everything except id and timestamps) and converts it to an array of just the values, then calls uniq on it which returns only uniq values and if the size is 1 then they were all the same value.
Update based on your comment that your table doesn't have an id or timestamps:
You can then simply do:
if record.attributes.except(:id).values.uniq.size == 1
...because I'm pretty sure it still has an id unless you're sure it doesn't then just remove the except part.
You can add a unique index to the table in a migration:
add_index :widgets, [:column1, :column2, :column3, :column4, :column5], unique: true
The resulting index will require that each combination of the 5 columns must be unique.

Advice on migrating from MongoMapper to Mongoid?

It seems like Mongoid is now the superior ORM for Mongo based on performance and development activity. Unfortunately, we're on MongoMapper and need to migrate.
Are there any concerns or stumbling blocks we should be aware of? We have found a few outdated articles on Google and tried posting on the Mongoid Google Groups (though we were prohibited), but would love thoughts from SO members who have done this in the past.
We're on Rails 3.2.12.
Thanks!
Both of them are great MongoDB Libraries for Ruby. But if you want to switch, here are some notes:
Migrating MongoMapper ORM to Mongoid ORM - Notes
Configure the database connection.
Replace configuration yaml file(includes replica configuration).
Configure Mongoid specific options. e.g - raise_not_found_error: false. if you don't want an error every time a query returns nothing...
Change all models definations - include MongoMapper::Document to include Mongoid::Document
Change the format for all fields definitions.
In mongoid, you should specipy the timestamp: include Mongoid::Timestamps
Change validation. e.g: :in => ARRAY, will be: validates :name, presence: true, inclusion: { in: ARRAY }
Change indexes.
Change order_by format. e.g: MM: Model.all(:order => 'name'). Mongoid: Model.order_by('name ASC')
Error is a keyword in Mongoid. So if you have a model named Error, you should change it.
Pagination format is different, using another gem.
The primary key index entry in MM is id. In Mongoid it's _id, if you have other code relying on .id in the object JSON, you can override as_json function in your Model to create the JSON structure you want.
In MM, Model.fields(:id, :name) ,limits the fields returned from the database to those supplied to the method. In Mongoid it's Model.only(:name,:id)
Some queries changes:
Selecting objects by array: MM: Model.where(:attr.in => [ ] ) and Model.where(:attr => [ ] ) . Mongoid is only: Model.where(:attr.in => [ ] )
Map option of MM is equivalent to the Mid's pluck. Model.map(&:name) --to-- Model.pluck(:name)
Mongoid doesn't support find query for nil. e.g: value = nil. Model.find(value) will throw an error : "Calling Document .find with nil is invalid". So in mongoid we should do: Model.find(value || "").
In MM: Model.find_or_initialize_by_name("BOB"). In Mongoid Model.find_or_initialize_by(name: "BOB").
MM can be used in those two options: Model.where({:name => 'BOB'}).first, and also Model.first({:name => 'BOB'}). Mongoid has only first option.
In MM, to update multiple objects: Model.set({conditions},attr_to_update). In Mongoid: Model.where(conditions).update_all(attr_to_update).

Rails ActiveRecord, INSERT default field value in Postgres

This should be an easy one, but I've googled a fair bit.
I am trying to create a database log for my rails app. The log is a postgres table which has a timestamp field, which I have given a DEFAULT of current_timestamp. This works perfectly in raw SQL, if I leave the timestamp field out of my INSERT query, it gets the current timestamp correctly.
In rails I have;
entry = LogTable.new :fieldA => 'valA', :fieldB => 'valB'
entry.save
(LogTable extends ActiveRecord::Base)
Which results in an INSERT query that contains all fields, including the timestamp field set to NULL, which is not allowed by the database so it errors.
I have tried setting :timestamp => 'current_timestamp' and :timestamp => 'DEFAULT' but all end up trying to set it to NULL.
Is your current_timestamp just the current date/time? If so, you can stay simple and do something like:
entry = LogTable.new :fieldA => 'valA', :fieldB => 'valB', :timestamp => Time.now
If it's something more complex and you really want to use DB-side defaults, this page might help: http://drawohara.com/post/6677354/rails-activerecord-default-values.

Getting types of the attributes in an ActiveRecord object

I would like to know if it is possible to get the types (as known by AR - eg in the migration script and database) programmatically (I know the data exists in there somewhere).
For example, I can deal with all the attribute names:
ar.attribute_names.each { |name| puts name }
.attributes just returns a mapping of the names to their current values (eg no type info if the field isn't set).
Some places I have seen it with the type information:
in script/console, type the name of an AR entity:
>> Driver
=> Driver(id: integer, name: string, created_at: datetime, updated_at: datetime)
So clearly it knows the types. Also, there is .column_for_attribute, which takes an attr name and returns a column object - which has the type buried in the underlying database column object, but it doesn't appear to be a clean way to get it.
I would also be interested in if there is a way that is friendly for the new "ActiveModel" that is coming (rails3) and is decoupled from database specifics (but perhaps type info will not be part of it, I can't seem to find out if it is).
Thanks.
In Rails 3, for your model "Driver", you want Driver.columns_hash.
Driver.columns_hash["name"].type #returns :string
If you want to iterate through them, you'd do something like this:
Driver.columns_hash.each {|k,v| puts "#{k} => #{v.type}"}
which will output the following:
id => integer
name => string
created_at => datetime
updated_at => datetime
In Rails 5, you can do this independently of the Database. That's important if you use the new Attributes API to define (additional) attributes.
Getting all attributes from a model class:
pry> User.attribute_names
=> ["id",
"firstname",
"lastname",
"created_at",
"updated_at",
"email",...
Getting the type:
pry> User.type_for_attribute('email')
=> #<ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString:0x007ffbab107698
#limit=255,
#precision=nil,
#scale=nil>
That's sometimes more information than needed. There's a convenience function that maps all these types down to a core set (:integer, :string etc.)
> User.type_for_attribute('email').type
=> :string
You can also get all that data in one call with attribute_types which returns a 'name': type hash.
You can access the types of the columns by doing this:
#script/console
Driver.columns.each {|c| puts c.type}
If you want to get a list of all column types in a particular Model, you could do:
Driver.columns.map(&:type) #gets them all
Driver.columns.map(&:type).uniq #gets the unique ones
In rails 5 this will give you a list of all field names along with their data type:
Model_Name.attribute_names.each do |k| puts "#{k} = #{Model_Name.type_for_attribute(k).type}" end
Rails 5+ (works with virtual attributes as well):
Model.attribute_types['some_attribute'].type
This snippet will give you all the attributes of a model with the associated database data types in a hash. Just replace Post with your Active Record Model.
Post.attribute_names.map {|n| [n.to_sym,Post.type_for_attribute(n).type]}.to_h
Will return a hash like this.
=> {:id=>:integer, :title=>:string, :body=>:text, :created_at=>:datetime, :updated_at=>:datetime, :topic_id=>:integer, :user_id=>:integer}
Assuming Foobar is your Active Record model. You can also do:
attributes = Foobar.attribute_names.each_with_object({}) do |attribute_name, hash|
hash[attribute_name.to_sym] = Foobar.type_for_attribute(attribute_name).type
end
Works on Rails 4 too
In Rails 4 You would use Model.column_types.

Resources