Create a method to set attributes after initializing an object - ruby-on-rails

I'm initializing a new object and setting the attributes (because there are no attributes for this particular object) before rendering a form like so:
def new
Book.new title: nil, author: nil, genre: nil, language: nil, ect...
end
This to me looks like a code smell.
I'm trying to set the attributes in a method within the model so I can increase readability by using: Book.new.set_attributes. So my set_attributes method in the Book model would look like:
def set_attributes
{posted: nil, company: nil, poster: nil, city: nil, state: nil, title: nil, body: nil, keywords: nil}
end
However this does not work (with or without the {} brackets). Is it possible to call a method after using .new?

Ruby's constructor method is initialize, not new. You shouldn't try to define a method called new. Do something like:
class Book
attr_accessor :title, :author
def initialize(title = nil, author = nil)
#title = title
#author = author
end
end

You don't need to initialize nil values. When calling Book.new, any values that are not provided in a hash (e.g., Book.new(title: 'Bozo', author: 'Clown')) will be nil automatically.

Related

Metrics/ParameterLists error in too many method params

I've got an API resource to fetch transaction list. There are few filters and params to make it sortable e.g. per_page, status etc. which may be passed on but only user_id is required. Now my method looks like this:
def list(user_id:, page: nil, per_page: nil, status: nil, payment_id: nil, start_date: nil,
end_date: nil, max_amount: nil)
filters = { start_date:, end_date:, max_amount:, status:, payment_id: }.compact_blank!
params = { filters:, page:, per_page: }.compact_blank!
begin
body = get("users/#{user_id}/transactions", params:).body
rescue Errors::NotFoundError
nil
end
resource_list(body, BaseStruct::Transaction)
end
This code produces me Rubocop error of:
Metrics/ParameterLists: Avoid parameter lists longer than 5 parameters. [8/5]. I know I could get rid of it by # rubocop:disable Metrics/ParameterLists but I don't think that's the best idea. Is it possible to pass those not required field in a different way to avoid that error?
Since you are passing 3 kinds of parameters to the list method, you can group them and pass them to the method like below:
def list(user_id, filter_params, page_params)
filters = filter_params.compact_blank!
params = { filter_params, page_params.slice(:page), page_params.slice(:per_page) }.compact_blank!
begin
body = get("users/#{user_id}/transactions", params).body
rescue Errors::NotFoundError
nil
end
resource_list(body, BaseStruct::Transaction)
end
list(user_id, { status: nil, payment_id: nil, start_date: nil, end_date: nil, max_amount: nil }, { page: nil, per_page: nil })
There is CountKeywordArgs option that I found useful to set false.
My rationale is that kwargs add less complexity than positional or optional parameters. It's more effective replacement of old-school Hash options argument.

Rails Copying attributes from User object to new object

In the User model I have an archive! method that is called when a User is destroyed. This action creates a new ArchivedUser in separate table.
The ArchivedUser is successfully created, but the way I am manually setting each value is pretty dirty; if a new column is added to the User table it must be added here as well.
I tried to select and slice the attributes, but got undefined local variable or methoduser'`
ArchivedUser.create(user.attributes.select{ |key, _| ArchivedUser.attribute_names.include? key })
ArchivedUser.create(user.attributes.slice(ArchivedUser.attribute_names))
How can I iterate through each attribute in the User table when creating an ArchivedUser with self?
def archive!
if ArchivedUser.create(
user_id: self.id,
company_id: self.company_id,
first_name: self.first_name,
last_name: self.last_name,
email: self.email,
encrypted_password: self.encrypted_password,
password_salt: self.password_salt,
session_token: self.session_token,
perishable_token: self.perishable_token,
role: self.role,
score: self.score,
created_at: self.created_at,
updated_at: self.updated_at,
api_key: self.api_key,
device_id: self.device_id,
time_zone: self.time_zone,
device_type: self.device_type,
verified_at: self.verified_at,
verification_key: self.verification_key,
uninstalled: self.uninstalled,
device_details: self.device_details,
is_archived: self.is_archived,
registered_at: self.registered_at,
logged_in_at: self.logged_in_at,
state: self.state,
creation_state: self.creation_state,
language_id: self.language_id,
offer_count: self.offer_count,
expired_device_id: self.expired_device_id,
unique_id: self.unique_id,
best_language_code: self.best_language_code,
offer_id: self.offer_id,
vetted_state: self.vetted_state,
photo_path: self.photo_path
)
self.is_archived = true
self.email = "#{self.email}.archived#{Time.now.to_i}"
self.encrypted_password = nil
self.password_salt = nil
self.session_token = nil
self.perishable_token = nil
self.device_id = nil
self.verification_key = nil
self.save!
self.update_column(:api_key, nil)
UserGroup.delete_all(:user_id => self.id)
else
# handle the ArchivedUser not being created properly
end
end
Thanks for viewing :)
Update:
We were able to figure out the reasons why ArchivedUser.create(self.attributes.slice!(ArchivedUser.attribute_names) wasn't working. The first reason is the create method requires "bang" to write the object. The second reason was that ArchivedUser has a user_id field, that User doesn't receive until after create. We have to set the user_id: manually with merge(user_id: self.id)
The final output looks like
ArchivedUser.create!(self.attributes.slice!(ArchivedUser.attribute_names).merge(user_id: self.id))
You were on the right track with the first implementation. You just have to use self instead of user.
ArchivedUser.create(self.attributes.slice(ArchivedUser.attribute_names))
If you just want to have a copy of the user that is being archived, I think an elegant way would be to do
archived_user = user_to_be_archived.dup
or you can take a look at the amoeba gem, this will do all the heavy lifting including associations if you want.
https://github.com/amoeba-rb/amoeba

Rails - how to fetch from ActiveRecord object only specific records?

I get this from an ActiveRecord call:
#<ActiveRecord::Associations::CollectionProxy [
#<CarService id: nil, car_id: nil, car_service: 1,
created_at: nil, updated_at: nil, car_type: 0>,
#<CarService id: nil, car_id: nil, car_service: 11,
created_at: nil, updated_at: nil, car_type: 1>]>
Once I get this, I need to filter only records where car_type = "0". How to do that without doing another database call (WHERE car_type = "0")?
Thank you in advance.
EDIT:
this:
car.car_services.select{|key, hash| hash['car_type'] == "1" }
does not work.
just convert your result to an array then filter it like this
result = car.car_services.to_a.select do |e|
e.car_type == "0"
end
You can use scope in CarService model:
scope :type_ones, -> { where(car_type: 1) }
and you can use it like this:
car.car_services.type_ones
If you use enum, it will be better. Because the enum creates to scopes automatically instead of you. And of course it has more features. More about the enum.

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

How do I get around a nil object being created in my controller?

So this is my controller for my Home#Index
class HomeController < ApplicationController
def index
#daily_entries = current_user.daily_entries
#weekly_entries = current_user.weekly_entries
#daily_entry = current_user.daily_entries.new
#weekly_entry = current_user.weekly_entries.new
end
end
The reason this is like this is because I am trying to render two form partials for the creation of both the DailyEntry and WeeklyEntry object types on my Home#Index.html.erb.
But, once the page loads, it automatically instantiates an object of each DailyEntry and WeeklyEntry with all nil values. So whenever I do a simple #daily_entries.each loop, it comes upon a record with lots of nil values - even though the record is not nil itself.
Like this:
#<DailyEntry id: nil, breakfast: nil, snack_1: nil, lunch: nil, snack_2: nil, dinner: nil, water_intake: nil, workout: nil, notes: nil, created_at: nil, updated_at: nil, user_id: 1>]
Aside from removing the current_user.daily_entries.new calls, how do I get around these nil objects for my each loops on this page?
Try the "confident code" approach. In this particular case, abuse the methods such as to_s and to_i. Examples work better:
#daily_entries.each do |daily_entry|
# If it's a String, nothing happens, if it's nil, empty string will be returned.
string_val = daily_entry.breakfast.to_s
# If it's a Fixnum, nothing happens, if it's nil, 0 will be returned.
int_val = daily_entry.id.to_i
end
Watch this video with Avdi Grimm, it explains this stuff about confident code really well.
Hope it helps :)
This is what I found works:
In my HomeController.rb:
#daily_entries = current_user.daily_entries.where(:id == true)
That filters out initialized, but unsaved records.

Resources