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
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.
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
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.
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
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.