getting attributes from a hash/difference between '.first' and '[i]' - ruby-on-rails

I have the following class methods for goal
def evals
self.evaluations.order("eval_number").group_by(&:student_id)
end
def evals_for(student, i)
#evals = []
self.evals.values.each do |eval|
#evals << eval.keep_if { |e| e.student_id == student.id }
end
#evals = #evals.reject { |array| array.empty? }.first
#evals[i]
end
in the view, i'm calling the second method like this:
<% #student.student_group.eval_count.times do |i| %>
<td><%= goal.evals_for(#student, i) %></td>
<% end %>
which returns
#<Evaluation:x>, #<Evaluation:y>, #<Evaluation:z>
if i change the last line of the class method to call #evals[i].inspect, i can see the data inside each hash, like so:
#<Evaluation id: 1949, score: 3, created_at: "2013-08-28 09:44:32", updated_at: "2013-08-28 09:44:32", student_id: 32, goal_id: 63, eval_number: 29>
I want to get the score, but when I call #evals[i].score on the last line in the class method, i get an error - undefined method 'score' for nil:NilClass
I know I can't call class methods on hashes, but is there a way to pull out just that data? As a sub-question, I'm a bit confused about the difference between .first and [i] as calling #evals.first.score returns "3" - only I can't use first as I need to be able to access each instance of evaluation in turn.

This is not a simple Hash:
#<Evaluation id: 1949, score: 3, created_at: "2013-08-28 09:44:32", updated_at: "2013-08-28 09:44:32", student_id: 32, goal_id: 63, eval_number: 29>
It's an instance of Evaluation class so you should be able to call score on it. I think your issue is due to the fact that you are trying to call score on nil
You'll probably want to make sure that you have an instance object before calling the method:
#evals[i].score if #evals[i]

Related

Rails: how to add items to an instance variable

In my index action I am creating an instance variable like so:
def index
#cards = Card.where(:cardstack_id => params[:cardstack_id])
end
This creates the output for #cards:
[#<Card id: 3, header: "Crazy", body: "Lege deinen Kopf für zwei Minuten auf den Tisch un...", points: 1, cardstack_id: 1, created_at: "2017-06-09 16:41:09", updated_at: "2017-06-13 17:24:29", lifetime: 240>, #<Card id: 4, header: "Böse Zungen", body: "Sprich 20 Minuten in einem starken Dialekt, der un...", points: 3, cardstack_id: 1, created_at: "2017-06-09 16:42:11", updated_at: "2017-06-13 17:26:24", lifetime: 360> ...
What I want to do now is to add for each object a random token created with SecureRandom.uuid.
So my output should look like this:
[#<Card id: 3, header: "Crazy", token: "34985jlskd908tjkls980", body : "..." ...]
How can I achieve this? I guess I have to somehow loop through the array in my controller, create a token for each element and then add it to a new array. However, I don't know how to achieve this.
I guess I have to somehow loop through the array in my controller,
create a token for each element and then add it to a new array.
However, I don't know how to achieve this.
You won't be able to do this and keep a ActiveRecord::Relation object (which is the result of your query); but you could get an array of hashes, where each hash will contain all attributes (and values) for each record, including any new key you need to add, for example:
#cards = #cards.map { |i| i.attributes.symbolize_keys.merge({ token: SecureRandom.uuid }) }
Using your example, this will be the content for #cards:
[
{
:id=>3,
:header=>"Crazy",
:body=>"Lege deinen Kopf für zwei Minuten auf den Tisch un...",
:points=>1,
:cardstack_id=>1,
:created_at=>"2017-06-09 16:41:09",
:updated_at=>"2017-06-13 17:24:29",
:lifetime=>240,
:token=>"fa637bfa-a781-4029-8f60-2763e75d6d5c"
},
{
:id=>4,
:header=>"Böse Zungen",
:body=>"Sprich 20 Minuten in einem starken Dialekt, der un...",
:points=>3,
:cardstack_id=>1,
:created_at=>"2017-06-09 16:42:11",
:updated_at=>"2017-06-13 17:26:24",
:lifetime=>360,
:token=>"2ff962cf-2258-4f2a-8d50-d8a864fb845a"
}
]
Then, you can iterate in your view just like any array, for example:
<% #cards.each do |card| %>
<p>ID: <%= card[:id] %></p>
<p>Header: <%= card[:header] %></p>
...
<p>Token: <%= card[:token] %></p>
<% end %>
It looks like you want to generate a UUID value for each card so that you can use it as a public id value without exposing the db id. Is that correct?
In that case, you shouldn't be doing it here. This should be done in after_initialize.
class Card < ActiveRecord::Base
after_initialize do
self.token ||= SecureRandom.uuid if has_attribute?(:token)
end
end
Use of has_attribute? is necessary here in case you constrain the lookup with select and don't include token in the query.
Make sure you read up on the performance impacts of UUIDs in databases.
I ended up using this pattern for so many things I wrote a simple gem for it.
class Card < ActiveRecord::Base
safe_initialize :token, with: ->{ SecureRandom.uuid }
end

Created objects in Rspec test can be inspected but showing as blank? and !present?

I have a really strange case I'm finding when I'm running RSpec tests.
After doing a my_model = MyModel.create, I can do a my_model.inspect and I will get all the create properties of that object, but when I do a my_model.blank? or my_model.present? it shows as true and false respectively
I can further demonstrate the strange behavior by doing a
find_test = MyModel.find(my_model.id)
And I get the same results - the object can be inspected, shows properties, but registers as blank and not present.
Really weird and I can't write a reliable test with this behavior. How can something so obviously "exist" but be both "blank" and not "present"?
Here is code showing the output in comments:
def self.find_or_create(id, current_user)
cart = Cart.where(referral_guid: nil, id: id).first
if !priority_cart?(cart) && !current_user.nil?
cart = Cart.where(referral_guid: current_user.referral_guid).order("id DESC").first
end
cart.update_cart_with_referral_guid(current_user) unless current_user.nil?
cart = Cart.create unless cart
##### test 1 #####
puts("cart.inspect: #{cart.inspect}") #<Cart id: 2, created_at: "2015-06-13 17:39:57", updated_at: "2015-06-13 17:39:57", price_total: nil, tax: nil, referral_guid: nil>
puts("cart.present?: #{cart.present?}") #false
puts("cart.blank?: #{cart.blank?}") #true
##### test 2 #####
find_test = Cart.find(cart.id)
puts("find_test.inspect: #{find_test.inspect}") #<Cart id: 2, created_at: "2015-06-13 17:39:57", updated_at: "2015-06-13 17:39:57", price_total: nil, tax: nil, referral_guid: nil>
puts("find_test.present?: #{find_test.present?}") #false
puts("find_test.blank?: #{find_test.blank?}") #true
cart
end
def self.find_or_create(id, current_user)
cart = Cart.where(referral_guid: nil, id: id).first
if !priority_cart?(cart) && !current_user.nil?
cart = Cart.where(referral_guid: current_user.referral_guid).order("id DESC").first
end
cart.update_cart_with_referral_guid(current_user) unless current_user.nil?
cart = Cart.create unless cart
##### test 1 #####
puts("cart.inspect: #{cart.inspect}") #<Cart id: 2, created_at: "2015-06-13 17:39:57", updated_at: "2015-06-13 17:39:57", price_total: nil, tax: nil, referral_guid: nil>
puts("cart.present?: #{cart.present?}") #false
puts("cart.blank?: #{cart.blank?}") #true
##### test 2 #####
find_test = Cart.find(cart.id)
puts("find_test.inspect: #{find_test.inspect}") #<Cart id: 2, created_at: "2015-06-13 17:39:57", updated_at: "2015-06-13 17:39:57", price_total: nil, tax: nil, referral_guid: nil>
puts("find_test.present?: #{find_test.present?}") #false
puts("find_test.blank?: #{find_test.blank?}") #true
cart
end
blank? is a Rails method defined as follows:
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
and present? is simply the inverse.
In other words, it's very much dependent on whether/how the model responds to the empty? method.
You haven't provided the definition for the Cart class and perhaps you're making use of some framework, but assuming this is some form of "shopping cart", it would be natural for it to have defined an empty? method which returns true if an only if it "contains" items. Since you just created the cart objects and haven't placed any items in it, it's natural that it would be "empty".

Access attributes in ActiveRecord::Associations::CollectionProxy

I am trying to write a custom function that will throw an error if the amount of associated objects are >=4
I am wondering how i can access the keys/values in the contained hash and run a validation on it
if i do this
animal = FactoryGirl.create(:animal, images_count: 4)
a = animal.animal_images
ap(a)
I get this returned
#<ActiveRecord::Associations::CollectionProxy [
#<AnimalImage id: 520, animal_id: 158, image: "yp2.jpg", created_at: "2014-10-15 13:45:11", updated_at: "2014-10-15 13:45:11">,
#<AnimalImage id: 521, animal_id: 158, image: "yp2.jpg", created_at: "2014-10-15 13:45:11", updated_at: "2014-10-15 13:45:11">,
#<AnimalImage id: 522, animal_id: 158, image: "yp2.jpg", created_at: "2014-10-15 13:45:11", updated_at: "2014-10-15 13:45:11">,
#<AnimalImage id: 523, animal_id: 158, image: "yp2.jpg", created_at: "2014-10-15 13:45:11", updated_at: "2014-10-15 13:45:11">
]
So i thought of using .map
animal = FactoryGirl.create(:animal, images_count: 4)
a = animal.animal_images
map = a.each.map { |i| i.image }
if map.length >= 4
ap('MORE THAN 4 IMAGES')
end
"MORE THAN 4 IMAGES"
So that iterates through the CollectionProxy. However how can i get this formatted into a correct rspec test and perform the logic in a custom validation function.
I thought my test would look like this
it 'should display an error message when too many images are uploaded' do
animal = FactoryGirl.create(:animal, images_count: 4)
animal.max_num_of_images
expect(animal.errors[:base]).to include("Max of 3 images allowed")
end
and just to get the pass for now (add error message) with no logic i have
class AnimalImage < ActiveRecord::Base
belongs_to :animal
validate :max_num_of_images, :if => "image?"
def max_num_of_images
errors.add(:base, "Max of 3 images allowed")
end
end
but it seems as if the test doesnt get past the first line
ActiveRecord::RecordInvalid:
Validation failed: Max of 3 images allowed
the above is thrown in the console
This is my factory
FactoryGirl.define do
factory :animal, class: Animal do
ignore do
images_count 0
end
after(:create) do |animal, evaluator|
create_list(:animal_image, evaluator.images_count, animal: animal)
end
end
end
FactoryGirl.define do
factory :animal_image do
image { File.open("#{Rails.root}/spec/fixtures/yp2.jpg") }
end
end
i'm probably going about this in the most backwards way possible, does anyone have any suggestions please
thanks
I am trying to write a custom function that will throw an error if the amount of associated objects are >=4
You are overcomplicating things. If you just want to count the number of records in a collection then you can simply do animal.animal_images.size. So your model will look like this:
class Animal < ActiveRecord::Base
has_many :animal_images
validate :max_num_of_images
def max_num_of_images
errors.add(:base, "Max of 3 images allowed") if self.animal_images.size >= 4
end
end

NoMethodError: undefined method `<<' for nil:NilClass

I have a task that I did not build but is no longer working. i am new to ruby on rails and have not found a solution for this problem yet.
when i run the task i Get:
this is the task:
desc "Archive Loads/Trucks that have expired."
task :expire_old_posts => :environment do
expired_loads = []
loads = Load.where("pickup < NOW()")
loads.each {|load| expired_loads << load.attributes }
ArchivedLoad.create( expired_loads )
loads.delete_all
end
little Debugging thru console
Confirms there are 120530 loads to transfer:
loads = Load.where("pickup < NOW()").count
2014-07-23 12:55:56 DEBUG -- (149.8ms) SELECT COUNT(*) FROM "loads" WHERE (pickup < NOW())
=> 120530
create the empty array:
expired_loads = []
=> []
run the where command
loads = Load.where("pickup < NOW()")
lots and lots of records... i limited it to two for testing
1.9.3-p547 :006 > loads = Load.where("pickup < NOW()").limit(2)
2014-07-23 13:02:07 DEBUG -- Load Load (74.2ms) SELECT "loads".* FROM "loads" WHERE (pickup < NOW()) LIMIT 2
=> [#<Load id: 18398947, user_id: 11074, origin: #<RGeo::Geographic::SphericalPointImpl:0x595f056 "POINT (-80.48237609863281 37.772544860839844)">, dest: #<RGeo::Geographic::SphericalPointImpl:0x595ec64 "POINT (-78.30302429199219 40.295894622802734)">, length: 48, comments: "~PostEverywhere_20140721140916~", ltl: false, rate: nil, delivery: "2014-07-22 17:00:00", pickup: "2014-07-21 17:00:00", weight: 48, equipment_id: 8, covered: false, created_at: "2014-07-21 19:16:16", updated_at: "2014-07-21 19:16:16", owner: nil, deleted: false, origin_city: "ronceverte", origin_state: "wv", dest_city: "martinsburg", dest_state: "pa">, #<Load id: 18398948, user_id: 11074, origin: #<RGeo::Geographic::SphericalPointImpl:0x553cd2a "POINT (-81.035400390625 37.384891510009766)">, dest: #<RGeo::Geographic::SphericalPointImpl:0x553c9d8 "POINT (-79.80570983886719 40.317527770996094)">, length: 48, comments: "~PostEverywhere_20140721140916~", ltl: false, rate: nil, delivery: "2014-07-22 17:00:00", pickup: "2014-07-21 17:00:00", weight: 48, equipment_id: 8, covered: false, created_at: "2014-07-21 19:16:16", updated_at: "2014-07-21 19:16:16", owner: nil, deleted: false, origin_city: "princeton", origin_state: "wv", dest_city: "greenock", dest_state: "pa">]
Really not sure what the << is but i assume this is just distributing the attributes so that they can be saved in the other table
loads.each {|load| expired_loads << load.attributes }
Errors here with
**NoMethodError: undefined method `<<' for nil:NilClass**
Next line is: Saves them to archived_loads table and deletes from loads table
ArchivedLoad.create( expired_loads )
loads.delete_all
Why not use #map instead? expired_loads = loads.map(&:attributes) or better yet just create a method in Load to archive them e.g.
class Load
scope :expired_loads, -> {where("pickup < NOW()")}
def self.archive_now
ArchiveLoad.create(expired_loads.map(&:attributes))
expired_loads.delete_all
end
end
Then you can call Loads.archive_now I am unsure of any of your structure this is just a suggestion as to a more concise implementation.
It also might be prudent to post the create method of your ArchivedLoad in case you have utilized the << method inside of there as well. If you have not implemented some kind of a custom create method I think you need a iteration instead such as
expired_loads.map(&:attributes).each{|load| ArchiveLoad.create(load)}
Also << is similar to push it adds an object to the end of an Array

Delete a record if nil is returned

I am making requests to the Facebook API and some of the responses are empty/nil and I am wondering how I can delete these so that when I save them to my model I don't have any nil entries.
def formatted_data
for record in results['data'] do
attrs = {
message: record['message'],
picture: record['picture'],
link: record['link'],
object_id: record['object_id'],
description: record['description'],
created_time: record['created_time']
}
attrs.delete_if { |x| x.nil? }
Post.where(attrs).first_or_create! do |post|
post.attributes = attrs
end
end
As you can see I am trying to use the delete_if method but it's not working.
Here's an example of a response that I would like to delete:
id: 45
message:
picture:
link:
object_id:
large_image_url:
description:
created_time: 2014-04-12 11:38:02.000000000 Z
created_at: 2014-05-01 10:27:00.000000000 Z
updated_at: 2014-05-01 10:27:00.000000000 Z
This kind of record is no good to me as it has no message, so maybe I could make the query specify if message.nil ? then delete
Edit
Been reading the delete_if docs and after iceman's comment, I thought this would work but it doesn't, though it seems closer to what I want:
attrs = attrs.delete_if {|key, value| key = 'message', value = nil }
There are about 25 records returned, of which 5 should be deleted, but after running the above I get one result left in the model:
[#<Post id: 81, message: nil, picture: nil, link: nil, object_id: nil, large_image_url: nil, description: nil, created_time: nil, created_at: "2014-05-01 11:22:40", updated_at: "2014-05-01 11:22:40">]
Why are all the rest being deleted, maybe my syntax for accessing the key is incorrect?
Since #delete_if passes into block two arguments: the key, and value, try this usage:
attrs.delete_if { |k,v| v.nil? }
and for ruby-on-rails you can remove all blank lines, i.e. nil, and empty:
attrs.delete_if { |k,v| v.blank? }
Im adding this in that someone could provide a more efficient way of doing this, maybe before the records get written to the model..But i have managed a solution, albeit a hacky one i would say
I have added this after the creation of the posts
delete_if_nil = Post.where(message: nil)
delete_if_nil.destroy_all
Its another query on the db which isnt ideal i guess
Any other suggestions appreciated

Resources