Reputation: 63
I have a User model and a Message Model. User has_many messages and Message belongs_to user.
In one method of the users_controller, I get email data from an API, initialize a new Message with that data (using thisMessage = Message.new(messageId, senderName...)). In the controller I do this multiple times, and store these messages in an Array called listOfMessages. So far, so good, I can use attributes of a message by doing listOfMessages[0].sender (sender being a Message model attribute) for example.
But, these messages are not saved when I refresh the page of course. If I try to save the messages to the database using 'thisMessage.save' after each time I initialize a new one, I get the error "undefined method '[]' for nil:NilClass".
I have also tried 'thisMessage.save!', and I get the error "undefined method 'has_key?' for nil:NilClass"
So how should I save these messages?
Message Model
class Message < ActiveRecord::Base
belongs_to :user
def initialize(inputId, inputSender, inputSenderRealName, inputRecievedAt, inputSubject, inputContent, inputIsRead, inputIsReplied, inputImportanceLevel)
@emailId = inputId
@sender = inputSender
@senderRealName = inputSenderRealName
@recievedAt = inputRecievedAt
@subject = inputSubject
@content = inputContent
@isRead = inputIsRead
@isReplied = inputIsReplied
@importanceLevel = inputImportanceLevel
end
The Method from users_controller I am using
def messageList
@user = current_user
if @user.current_message == nil
@user.current_message = 0
else
end
puts "Need Loading " + @user.needsLoading.to_s
@listOfMessages = Array.new
messageIdList = Array.new
--API stuff removed--
body1 = singleMessage['bodies'][0]['content']
senderName = singleMessage['addresses']['from'][0]['email']
senderActualName = singleMessage['addresses']['from'][0]['name']
recieveTime = singleMessage['sent_at']
subjectText = singleMessage['subject']
isRead = singleMessage['flags']['seen']
hasReplied = singleMessage['flags']['answered']
thisMessage = Message.new(messageId, senderName, senderActualName, recieveTime, subjectText, body1, isRead, hasReplied, 0)
@listOfMessages << thisMessage
thisMessage.save
}
puts @user.messages.length
@responseText = response
puts @user.needsLoading.to_s
puts @user.current_message
@user.update_column(:needsLoading, 1)
end
The end goal is to be able to do things like "current_user.messages[1].sender" somewhere else. Thanks!
Upvotes: 2
Views: 8554
Reputation: 101811
ActiveRecord::Model
.ActiveRecord models already take a hash of attributes:
User.new(name: 'Max', awesome: true)
If the attribute exists it will be set on the model instance. The initializer does a lot of work for and you should not be clobbering it lightly.
If you really really need to do something in the initializer make sure you call super
and keep the interface the same as one would expect for a model.
def initalize(hash = {}, &block)
super
# do something custom here
end
But in most cases you can use callbacks or custom attribute setters instead.
Ruby and Rails for that matter has pretty simple and strong conventions for naming:
CONSTANTS
are all caps. Ruby enforces this.ModuleName
and ClassName
are CamelCase (and also a kind of constant).variable_name
and attribute_name
are snake_case
. Ruby does not care - but the community does. Disregard this and you'll never be able to sit with the cool kids..update
and .assign_attributes
There are a few ways to change a model instance but it's important to note the difference between changing the model instance in memory and committing the changes to the database.
@order = Order.new
# change a single attribute.
@order.total = 999
# Update multiple attributes at once
@order.assign_attributes(
total: 669,
shipping_method: 'FEDEX'
)
# So far we have only updated the instance in memory.
@order.save # Commit the changes to the database
# We can also update the database straight away.
@order.update_attribute(:total, 999) # one attribute at a time
# or with a hash.
@order.update(
total: 669,
shipping_method: 'FEDEX'
)
# Using related objects works the same:
@order.update(
customer: @customer
)
# Note that this will also save the related object to the database.
@order.update(
customer: Customer.new
)
This limits the amount of contact points between the external api and your application. And they are are really easy to test.
# Import a message from SomeApi
class MessageImportService
def initialize(client = MyApi)
@client = client
end
# @return [Array]
def call
data = @client.call_some_api_method || []
data.map do |raw_msg|
Message.new(
body1 : raw_msg['bodies'][0]['content'],
sender_name : raw_msg['addresses']['from'][0]['email'],
sender_actual_aame : raw_msg['addresses']['from'][0]['name'],
)
end
end
end
From your controller you would do:
@messages = MessageImportService.new.call
https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services
Upvotes: 3