Is there a rails way to titleize and humanize strings before storing into database - ruby

The problem about humanize (and probably titleize too i'm not sure) is that it breaks the view if the string is nil.
and then you'll have to .try(:humanize) instead, which is a big headache if you realized that you made that mistake way too late and you have to recheck every line of code at your views.
What i'm looking for is a way i could humanize and titleize strings before storing them into the database. That way, even if i'm looking straight into my database i will see the strings humanized and titleized.

You can create a method in your model which will take the attributes of this, and will transform as you want and/or you need, you can "fire" this every time you save a record using it as a before_save callback:
For instance, having a model User which attributes name and lastname I'm using the name and using titleize on it:
class User < ApplicationRecord
before_save :titlelize_names
def titlelize_names
self.name = self.name.titleize
end
end
The params will contain the attributes as the user has typed them, but they will be saved according to what the model says:
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"...", "user"=>{"name"=>"name", "lastname"=>"lastname"}, "commit"=>"Create User"}
(0.1ms) begin transaction
SQL (0.8ms) INSERT INTO "users" ("name", "lastname", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Name"], ["lastname", "lastname"]...]
Is the only one interaction you need to do before save the record, the view is untouched and the controller just receive it and makes what model says.
If by any change it occurs some problem at the moment of saving the record you can use throw(:abort) and "to force to halt" the callback chain.
def titlelize_names
self.name = self.name.titleize
throw(:abort)
end
In addition and depending on which method you're using with the before_save callback, you can add custom errors which will be available to use within your controllers, as for instance, an error identified by the key :titleize_names could be then handled in the controller.
def titlelize_names
errors.add(:titleize_names, 'An error has ocurred with the name attribute.')
self.name = self.name.titleize
throw(:abort)
end

If humanize and titlerize return an exception if value is nil, create an if statement, and you can make this process in one service or in one function model using before_save or before_create callbacks:
value.titleize unless value.nil?

You can use model callbacks to transform the data before saving it to the DB: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html - you probably want before_save.
Also, if you want to ensure the record is never nil, add a validation as well.
Example:
class Example < ActiveRecord::Base
validates :name, presence: true
before_save :fixname
# Force titleize on :name attribute
def fixname
self.name = self.name.titleize
end
end
Now your model will have a method called fixname that you can call anywhere on your model instance as well as having it called before saving it to the DB.

Related

Getting Sequel associations through Sinatra

I'm trying to return json-formatted data from my Sinatra REST API. I currently have a bunch of associations set up, but I'm having trouble getting the views I want from my API despite getting them easily in Ruby.
For example, from my tables:
DB.create_table?(:calendars) do
primary_key :id
end
DB.create_table?(:schedules) do
primary_key :id
foreign_key :resource_id, :resources
foreign_key :task_id, :tasks
foreign_key :calendar_id, :calendars
end
In Ruby, I'm able to run a block like this and display all the info I need through my associations:
Calendar.each do |c|
c.schedules.each do |s|
puts "RESOURCE ##{s.resource_id}"
s.tasks.each do |t|
p t
end
puts
end
end
the c.schedules call works because my calendar model contains a one_to_many :schedules association.
Now, I'm wondering how this translates to my Sinatra API. In my simple GET route, I've tried many variations trying to get the schedules associated with a calendar, and convert it to JSON:
get '/calendars' do
c = DB[:calendar].first
c.schedules.to_json
content_type :json
end
... but I'll end up with an error like undefined method 'schedules' for {:id=>1}:Hash
So it looks like it's returning a hash here, but I've tried a bunch of stuff and haven't figured out how I'm supposed to work with my associations in Sinatra. How can I do this?
Thanks!
The reason your first block works but the second doesn't is because in the first case, you're using a Sequel model instance of class Calendar, whereas in the second case you're using a Sequel dataset.
When you iterate over Calendar.each do |c|, the c variable gets populated with an instance of a Calendar class Sequel model object. This object has relationship methods defined (one_to_many) and you're able to query schedules and run other model methods on it.
However, c = DB[:calendar].first gets you a Sequel dataset. This object is different than a model instance, it returns a standard Ruby hash (or an array of hashes).
You can change your 2nd block to use a model instead and it will get the result you want:
get '/calendars' do
c = Calendar.first # <=== CHANGE FROM DATASET TO MODEL
c.schedules.to_json
content_type :json
end

Missing ActiveRecord methods (find_by) for object

I am trying to do an assignment which requires me to create and save an ActiveRecord within my Model class, and then return it. The rspec is expecting to use the find_by method to verify this. Here's my Model:
-----------------------
class User < ActiveRecord::Base
attr_accessor :id, :username, :password_digest, :created_at, :updated_at
after_initialize :add_user
def initialize(attributes={})
#username = attributes[:username]
#password_digest = attributes[:password_digest]
super
end
def add_user
self[:username] = #username
self[:password_digest] = #password_digest
self.save
self[:id] = self.id
end
end
----------------
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object. So, rspec fails. I have looked everywhere but can't seem to find the solution. I am a noob, so sorry if the answer is obvious and I can't see it.
You say
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object
This is expected behavior. Hopefully you understand by now the difference between class and instance methods. The main important point is that query methods such as find_by are not made available to model instances. If you do something like user = User.find_by(id: params[:id]), you're calling the find_by class method on the User model.
There are a number of methods like where, order, limit, etc. that are defined in ActiveRecord::QueryMethods - these are made available to ActiveRecord::Relation object and your model class. Most of these methods will return ActiveRecord::Relation objects, which is why they're chainable, e.g.
User.where(params).order(created_at: :desc).limit(5)
However find_by is an exception - it returns a model instance so you can't continue to query on the results. In summary User.new(params) returns an instance of the model which doesn't have find_by available

What methods are used to set attributes when data is pulled from a table in Ruby ActiveRecord?

I'm using ActiveRecord 4.1.8 in a Ruby (not Rails) application. I have a table and a corresponding model that looks like the following:
create_table 'people', :force => true do |t|
t.string 'name'
end
class Person < ActiveRecord::Base
def name=(name)
puts "Attribute setter for name called with #{name}"
write_attribute(:name, name)
end
end
When I create a new instance of Person, I see the Attribute setter for name called with... written to STDOUT. However, when I reload the model instance, I do not see the message written to STDOUT.
p = Person.create(name: 'foobar')
--> Attribute setter for name called with foobar
p.reload
--> <nothing>
The model is getting persisted to the database, so this makes me think name= isn't used when data is loaded into a model from the database. I need to modify certain data attributes when they're read in from the database, so does anyone know what other method I need to override?
From the active_record/persistence.rb source:
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
else
self.class.unscoped { self.class.find(id) }
end
#attributes = fresh_object.instance_variable_get('#attributes')
#new_record = false
self
end
It just replaces the attributes hash directly. Seems like the easiest way to handle this is to override reload and patch things up after its called.

Serialize a class into an activerecord field

I've tried a few different searches, but I'm not really sure how to word the question correctly. Imagine I have a db table create migration that looks like this:
def self.up
create_table :users do |t|
t.string :name, :null => false,
t.text :statistics
end
end
Example users class
class User
serialize :statistics, JSON
def statistics
self.statistics
end
end
But I want self.statistics to be an instance of class Statistics
class Statistics
#stats = {}
def set(statistic, value)
#stats[statistic] = value
end
def to_json(options)
self.to_json(:only => #stats)
end
end
(or something like that)
Ultimately, what I want to happen is that I want to be able to add Statistics-specific methods to manage the data in that field, but when I save the User object, I want it to convert that instance of the Statistics class into a JSON string, which gets saved in the DB.
I'm coming from a PHP background, where this kind of thing is pretty easy, but I'm having a hard time figuring out how to make this work with ActiveRecord (something I don't have much experience with).
Ideas?
Note: This is NOT Rails... just straight-up Ruby
Do you need it to be JSON? If not, serializing it to YAML will allow you to easily marshal the object back to its original class.
ActiveRecord::Base#serialize takes two arguments: attribute and class. If class is defined, the data must deserialize into an object of the type specified. Here's a cool example of it in action.
By default, your attribute will be serialized as YAML. Let’s say you
define MyField like this:
class MyField
end
It will be serialized like this:
--- !ruby/object:MyField {}
It will deserialize back to a MyField instance.

Can't display more than one table model inheriting from the same class on different tables in QtRuby

I've been following this article to display ActiveRecord data in QtRuby. I've copied the BoatTableModel class from there(used my own code for the rest). In the article, BoatTableModel is defined to only support the Boat model, but except for the column definitions the code is quite generic. So, I've changed it so instead of having the columns defined there, I've made it take the columns from a column_names method, and define that methods in subclasses for each model.
Here is my code:
class QtArModel<Qt::AbstractTableModel
def initialize(items)
super()
#items=items
end
def rowCount(parent=nil)
#items.size
end
def columnCount(parent=nil)
column_names.length
end
def data(index,role=Qt::DisplayRole)
invalid=Qt::Variant.new
return invalid unless role==Qt::DisplayRole or role==Qt::EditRole
item=#items[index.row]
return invalid if item.nil?
v=item[column_names[index.column]]||""
return Qt::Variant.new(v)
end
def headerData(section,orientation,role=Qt::DisplayRole)
invalid=Qt::Variant.new
return invalid unless role==Qt::DisplayRole
v=case orientation
when Qt::Horizontal
column_names[section]
else
""
end
return Qt::Variant.new(v.to_s)
end
def flags(index)
return Qt::ItemIsEditable|super(index)
end
def setData(index,variant,role=Qt::EditRole)
if index.valid? and role==Qt::EditRole
s=variant.toString
item=#items[index.row]
if index.column.between?(0,column_names.length-1)
item[column_names[index.column]]=s
else
raise "invalid column #{index.column}"
end
item.save
emit dataChanged(index,index)
else
return false
end
end
end
class QtCoursesTableModel<QtArModel
def column_names
return [
:number,
:name,
:tutor_name,
:site,
:active,
]
end
end
class QtTasksTableModel<QtArModel
def column_names
return [
:course,
:ex_number,
:received,
:due,
:description,
:link,
:completed,
:file,
]
end
end
Now, when I display one model(doesn't matter which) - everything works just fine. However, when I display both models, each in it's own Qt::TableView - only the first one is displayed, and the other table view is blank.
I've tried different ordering, and the table that gets to display it's data is always the one which it's Qt::TableView is created first - the order of the creating the Qt models does not matter. Also, when I create the model object for the first table, but don't actually set it's model property to it, the second table displays it's data.
I've also tried to display the same model twice in two different table views - and it worked - for a split second, and then the second view's data disappeared.
I've also tried to copy-paste the QtArModel, change it's name, and make one of the models inherit from the copy. That did work - but it's obviously a huge code duplication, so I would really like to avoid that.
Now, my guess is that something in QtArModel is defined as a class member instead of instance member, making both model instances share something they shouldn't share. It has to be in QtArModel - because if it was higher in the inheritance tree, the problem would have remained when I've duplicated QtArModel. However, I can't find anything in my QtArModel that's class-scoped instead of instance-scoped.
What am I missing?
OK, I've managed to work this out. Apparently, the problem was not the inheritance, but the GC. Since the only connection to the models was from TableView's model property - which is just a wrapper for C++ getter and setter - ruby thought it lost the reference to my models, and GC'd them.
Solved by keeping the models in ruby variables.

Resources