Should I symbolize keys? - ruby-on-rails

1) I am grabbing some records for the DB in HAML to display, and the attributes method on each row returns a hash. The hash's keys are strings. Should I turn those keys into symbols? I am not sure the call to symbolize_keys is worth it. I.e.,
%td #{app['comment']}
or
%td #{app[:comment]
2) I am trying to symbolize the array of hashes I return, but it is not working:
rows = Comment.all(:order => 'created DESC')
result = rows.each_with_object([]) do |row, comments|
comments << row.attributes.symbolize_keys
end
Is it not actually pushing the symbolized hash into the comments array? I also tried symbolize_keys!, and that did not help. What am I doing wrong?

Since you're using Rails, you have access to HashWithIndifferentAccess so you can bypass your "strings or symbols" issue quite easily by allow both:
h = HashWithIndifferentAccess.new(some_model.attributes)
puts h['id'] # Gives you some_model.id
puts h[:id] # Also gives you some_model.id
Your each_with_object approach:
result = rows.each_with_object([]) do |row, comments|
comments << row.attributes.symbolize_keys
end
should work fine so I think your problem with that lies elsewhere.

Do you have a reason for using ActiveRecord::Base#attributes[your_attribute] instead of ActiveRecord::Base#your_attribute directly? You didn't mention a reason.
ActiveRecord::Base automatically sets up accessors for your database fields:
object = Model.new
object.your_column = "foo" # Writer
object.your_column # Reader
You should be able to use the reader in your views instead of accessing the value through ActiveRecord::Base#attributes.
Update:
I'm not sure if this is what confuses you.
Comment.find(:all) already retrieves all columns values for those rows in your database and stores them in your Comment objects (which we assign to #comments below). The values are already stored in your Comment objects, so you may already use them in your views as you please.
In your controller, if you have:
def index
#comments = Commend.find(:all) # Fetch columns and rows.
end
you can do this in your HAML view:
- #comments.each do |comment| # Iterate through array of Comment objects
%tr
%td= comment.comment # Use value for "comment" column.

you can add hook, which symbolizes keys after model load:
class YourModel < ApplicationRecord
after_initialize do |rec|
attributes["some_json_field"].symbolize_keys! if attributes.key? "some_json_field"
end
end

Related

Search for an object in rails by an array and create objects if not exist

I want to call the ActiveRecord method where with an array for a column. If the each item on the array doesn't exist, create the object. The closest method I found for this is first_or_create but this seems to be called only once, not for each time the record doesn't exist. Below is my example code-
hashtag_list = params[:message][:hashtag_primary]
#hashtags = Hashtag.where({:name => hashtag_list}).first_or_create do |hashtag|
hashtag.creator = current_user.id
end
Rails version- 4.2.1
I don't know a direct method, only a workaround
existing_tags = Hashtag.where({:name => hashtag_list}).pluck(:name)
not_existing_tags = hashtag_list - existing_tags
#hashtags = Hashtag.where({:name => existing_tags}).all
not_existing_tags.each do |tag|
#hashtags << Hashtag.new name: tag
end
#hashtags.each do |hashtag|
hashtag.creator = current_user.id
end
This is expected behavior of where + first_or_create method. Basically where(field: array) produces an SQL to find all records where field matches any item in the array. Than you have first_or_create method which takes the first record from results or creates a new one with escaped array value assigned to a field (so something like field: "[\"foo\", \"bar\"]" when used as where(field: %w(foo bar)).
If you want to create records for each hashtag from your list, you should iterate over it:
if #hashtag = Hashtag.where({:name => hashtag_list}).first
# do something if found the first one
else
hashtag_list.each do |hashtag|
# create an object
end
end
If you want to create missing hashtags even if the record is found, you can extract this to a private helper method with missing tags as the argument and re-write code as:
if #hashtags = Hashtag.where({:name => hashtag_list})
# do something if found
end
create_missing_hashtags(hashtag_list - #hashtags.pluck(:name))

Whats a succinct way to express 'If an array exists then do'

Is there a more succinct way of expressing the following:
if Model.all
array = Model.all
array.each do |a|
a.info
end
end
In my case, Model.all is a helper method (get_all_of_those()).
In the view, I am displaying data in tables based on the results. a.info might be
"<div class='row'>#{a.name}</div>"
Model.all is always truthy and is always an array-like object (Strictly speaking it's ActiveRecord::Relation object in rails 4; an Array in rails 3). You can just do:
Model.all.each do |a|
a.info
end
If there are no models, the loop will not be executed even once.
(Note however, that this code doesn't do anything interesting with models, so you need to update your question with: What do you want the final result to be? There is a chance that you are looking for Model.pluck(:info))
If info is a field in the database, you could do this more efficiently with
array = Model.pluck(:info)
Try this out:
Model.all.find_each do |a|
a.info
end
Read more about find_each in the documentation.

Rails association access

I wish I described this better, but it's the best I know how. I have two classes Cars and Colors. Each can have many of each other through a association class CarColors. The association is set up correctly I'm positive of this but I can't seem to get this to work:
#carlist = Cars.includes(:Colors).all
#carlist.colors
ERROR
#carlist[0].colors
WORKS
My question is how can I iterate over the #carlist without declaring a index as in the successful example? Below is a few things I have tried which also fail:
#carlist.each do |c|
c.colors
end
#carlist.each_with_index do |c,i|
c[i].colors
end
Your first example fails because Car.includes(:colors).all returns an array of cars, not a single car, so the following will fail, because #colors is not defined for the array
#cars = Car.includes(:colors).all
#cars.colors #=> NoMethodError, color is not defined for Array
The following will work, because the iterator will have an instance of car
#cars.each do |car|
puts car.colors # => Will print an array of color objects
end
each_with_index will work as well, but it is a bit different, as the first object
is the same as the each loop car object, the second object is the index
#cars.each_with_index do |car, index|
puts car.colors # => Will print an array of color objects
puts #cars[index].colors # => Will print an array of color objects
puts car == #cars[index] # => will print true
end

Rails 3 check if attribute changed

Need to check if a block of attributes has changed before update in Rails 3.
street1, street2, city, state, zipcode
I know I could use something like
if #user.street1 != params[:user][:street1]
then do something....
end
But that piece of code will be REALLY long. Is there a cleaner way?
Check out ActiveModel::Dirty (available on all models by default). The documentation is really good, but it lets you do things such as:
#user.street1_changed? # => true/false
This is how I solved the problem of checking for changes in multiple attributes.
attrs = ["street1", "street2", "city", "state", "zipcode"]
if (#user.changed & attrs).any?
then do something....
end
The changed method returns an array of the attributes changed for that object.
Both #user.changed and attrs are arrays so I can get the intersection (see ary & other ary method). The result of the intersection is an array. By calling any? on the array, I get true if there is at least one intersection.
Also very useful, the changed_attributes method returns a hash of the attributes with their original values and the changes returns a hash of the attributes with their original and new values (in an array).
You can check APIDock for which versions supported these methods.
http://apidock.com/rails/ActiveModel/Dirty
For rails 5.1+ callbacks
As of Ruby on Rails 5.1, the attribute_changed? and attribute_was ActiveRecord methods will be deprecated
Use saved_change_to_attribute? instead of attribute_changed?
#user.saved_change_to_street1? # => true/false
More examples here
ActiveModel::Dirty didn't work for me because the #model.update_attributes() hid the changes. So this is how I detected changes it in an update method in a controller:
def update
#model = Model.find(params[:id])
detect_changes
if #model.update_attributes(params[:model])
do_stuff if attr_changed?
end
end
private
def detect_changes
#changed = []
#changed << :attr if #model.attr != params[:model][:attr]
end
def attr_changed?
#changed.include :attr
end
If you're trying to detect a lot of attribute changes it could get messy though. Probably shouldn't do this in a controller, but meh.
Above answers are better but yet for knowledge we have another approch as well,
Lets 'catagory' column value changed for an object (#design),
#design.changes.has_key?('catagory')
The .changes will return a hash with key as column's name and values as a array with two values [old_value, new_value] for each columns. For example catagory for above is changed from 'ABC' to 'XYZ' of #design,
#design.changes # => {}
#design.catagory = 'XYZ'
#design.changes # => { 'catagory' => ['ABC', 'XYZ'] }
For references change in ROR

Verifying if an object is in an array of objects in Rails

I'm doing this:
#snippets = Snippet.find :all, :conditions => { :user_id => session[:user_id] }
#snippets.each do |snippet|
snippet.tags.each do |tag|
#tags.push tag
end
end
But if a snippets has the same tag two time, it'll push the object twice.
I want to do something like if #tags.in_object(tag)[...]
Would it be possible? Thanks!
I think there are 2 ways to go about it to get a faster result.
1) Add a condition to your find statement ( in MySQL DISTINCT ). This will return only unique result. DBs in general do much better jobs than regular code at getting results.
2) Instead if testing each time with include, why don't you do uniq after you populate your array.
here is example code
ar = []
data = []
#get some radom sample data
100.times do
data << ((rand*10).to_i)
end
# populate your result array
# 3 ways to do it.
# 1) you can modify your original array with
data.uniq!
# 2) you can populate another array with your unique data
# this doesn't modify your original array
ar.flatten << data.uniq
# 3) you can run a loop if you want to do some sort of additional processing
data.each do |i|
i = i.to_s + "some text" # do whatever you need here
ar << i
end
Depending on the situation you may use either.
But running include on each item in the loop is not the fastest thing IMHO
Good luck
Another way would be to simply concat the #tags and snippet.tags arrays and then strip it of duplicates.
#snippets.each do |snippet|
#tags.concat(snippet.tags)
end
#tags.uniq!
I'm assuming #tags is an Array instance.
Array#include? tests if an object is already included in an array. This uses the == operator, which in ActiveRecord tests for the same instance or another instance of the same type having the same id.
Alternatively, you may be able to use a Set instead of an Array. This will guarantee that no duplicates get added, but is unordered.
You can probably add a group to the query:
Snippet.find :all, :conditions => { :user_id => session[:user_id] }, :group => "tag.name"
Group will depend on how your tag data works, of course.
Or use uniq:
#tags << snippet.tags.uniq

Resources