rails 3 custom validation error messages in a join table, how? - ruby-on-rails

How can I return errors messages from a cross reference table with multiple records when I trying to create those? I'm trying this:
## activity_set.rb
class ActivitySet < ActiveRecord::Base
has_many :activity_set_lessons
has_many :lessons, :through => :activity_set_lessons
validates :name, :presence => true
def activity_set_lessons=(data)
data.each_with_index do |v, i|
activity_set_lessons.build(
:lesson_id => v[:lesson_id],
:sort_order => i,
:weight_percentage => v[:weight_percentage]
)
end
end
end
## activity_set_lesson.rb
class ActivitySetLesson < ActiveRecord::Base
belongs_to :activity_set
belongs_to :lesson
validates :lesson_id, :presence => true
validates_each :weight_percentage do |record, attr, value|
record.errors.add :base, "woot" if value.blank?
end
end
This is the request data:
## params[:activity_set]
"activity_set" => {
"name" => "hshshshs",
"keywords" => "",
"activity_set_lessons" => [
{"weight_percentage" => "", "lesson_id"=>"4"},
{"weight_percentage" => "", "lesson_id"=>"5"}
]
}
Error messages from #activity_set when I do #save:
{
"errors":{
"activity_set_lessons":["is invalid","is invalid"]
},
"full_messages":[
"Activity set lessons is invalid","Activity set lessons is invalid"
]
}
I always got the same error message even if I'm adding a custom one in the join table. How can I return a message like: "woot 1 is wrong" or something like that, per validation?.
Thanks.

make use of accepts_nested_attributes_for
## activity_set.rb
class ActivitySet < ActiveRecord::Base
has_many :activity_set_lessons
has_many :lessons, :through => :activity_set_lessons
validates :name, :presence => true
accepts_nested_attributes_for :activity_set_lessons
end
view will look like
= form_for #activity_set do |f|
[activity_set form fields ]
= f.fields_for :activity_set_lessons do |p|
= p.select :lession_id
= p.select :weight_percentage

Related

Ruby, merge two different objects

I need to merge to different object in my Ruby on Rails application. I have my Invoice object:
class Invoice < ActiveRecord::Base {
:id => :integer,
:user_id => :integer,
:description => :text,
......
:status => :string,
:price => :float
}
And my Payment object:
class Payment < ActiveRecord::Base {
:id => :integer,
:token => :string,
:invoice_id => :integer,
:created_at => :datetime,
:updated_at => :datetime,
:email => :string
}
With 1 to many relationship between them:
class Invoice < ActiveRecord::Base
has_many :payments
class Payment < ActiveRecord::Base
belongs_to :invoice
Now what I would like to do is to return the Invoice object and the :email and :created_at field of the payment object associated. Right now I return the two object with the zip function:
:invoices => (user.invoices.where(:hide => false).zip user.invoices.map{|x| x.payments.last}),
but that return an array of array:
[
[{invoice},{payment}],
[{invoice},{payment}],
...
]
What I want to return is something like:
[
{invoice, :email_payment, :created_at_payment},
{invoice_1, :email_payment, :created_at_payment},
...
]
How can I do that?
I would add email_payment and created_at_payment as methods to the invoice model, but you can achieve it with the following:
user.invoices.where(hide: false).map do |invoice|
invoice.attributes.merge({
email_payment: invoice.payments.last.email,
created_at_payment: invoice.payments.last.created_at
})
end

NoMethodError undefined method `messages'

I have sidekiq running and I am getting a NoMethodError undefined method `messages' in my log for the worker. I have everything defined so I am not sure what I am doing wrong.
class UserMessageWorker
include Sidekiq::Worker
def perform(message_id, recipient_id)
message = Message.find(message_id)
user = User.find(recipient_id)
message.read_at = Time.now
old_msg_count = user.messages.count
usermessages = user.received_messages.where("read_at IS NOT NULL").count + user.sent_messages.where("read_at IS NOT NULL").count
if message.save
msg_response_time = message.read_at - message.created_at
readmessages = []
usermessages.each do |um|
if um.read_at != nil
readmessages << um
end
end
response_rate = usermessages/(old_msg_count + 1)
response_time = ((user.average_response_time * old_msg_count)+msg_response_time)/(old_msg_count + 1)
user.update_attributes(:response_rate => response_rate, :average_response_time => average_response_time )
end
end
end
Messages controller:
def show
#reply_message = Message.new
#message = Message.find(params[:id])
if #message.recipient == current_user
UserMessageWorker.perform_async(#message.id, current_user.id)
end
#message.readingmessage if #message.recipient == current_user
end
Messages model:
attr_accessible :subject, :conversation_id, :body, :parent_id, :sender_id, :recipient_id, :read_at,:sender_deleted,:recipient_deleted
validates_presence_of :subject, :message => "Please enter message title"
has_many :notifications, as: :event
belongs_to :conversation, inverse_of: :messages
belongs_to :user
scope :unread, -> {where('read_at IS NULL')}
scope :not_deleted_by_recipient, where('messages.recipient_deleted IS NULL OR messages.recipient_deleted = ?', false)
scope :not_deleted_by_sender, where('messages.sender_deleted IS NULL OR messages.sender_deleted = ?', false)
belongs_to :sender,
:class_name => 'User',
:foreign_key => 'sender_id'
belongs_to :recipient,
:class_name => 'User',
:foreign_key => 'recipient_id'
def reply
new_message.reply_from_user_id = self.id #save the user id of original repost, to keep track of where it originally came from
end
def self.by_date
order("created_at DESC")
end
# marks a message as deleted by either the sender or the recipient, which ever the user that was passed is.
# When both sender and recipient marks it deleted, it is destroyed.
def mark_message_deleted(id,user_id)
self.sender_deleted = true if self.sender_id == user_id
self.recipient_deleted = user_id if self.recipient_id == user_id
(self.sender_deleted > 0 && self.recipient_deleted > 0) ? self.destroy : self.save!
(self.sender_deleted != 0 && self.recipient_deleted != 0)
end
# Read message and if it is read by recipient then mark it is read
def readingmessage
self.read_at ||= Time.now
save
end
# Based on if a message has been read by it's recipient returns true or false.
def read?
self.read_at.nil? ? false : true
end
def self.received_by(user)
where(:recipient_id => user.id)
end
def self.not_recipient_deleted
where("recipient_deleted = ?", false)
end
def self.sent_by(user)
Message.where(:sender_id => user.id)
end
def next(same_recipient = true)
collection = Message.where('id <> ? AND created_at > ?', self.id, self.created_at).order('created_at ASC')
collection.where(recipient_id: self.recipient_id) if same_recipient
collection.first
end
def previous(same_recipient = true)
collection = Message.where('id <> ? AND created_at < ?', self.id, self.created_at).order('created_at DESC')
collection.where(recipient_id: self.recipient_id) if same_recipient
collection.first
end
end
private
def send_notification(message)
message.notifications.create(user: message.recipient)
end
User model:
has_secure_password
attr_accessible :role, :name, :time_zone, :code, :lat, :lon, :city, :age, :age_end, :password_confirmation, :about_me, :feet, :inches, :password, :birthday, :career, :children, :education, :email, :ethnicity, :gender, :height, :name, :password_digest, :politics, :religion, :sexuality, :user_drink, :user_smoke, :username, :zip_code
# this prevented user from registering as I don't have timezone select on user reg form
# validates_inclusion_of :time_zone, in: ActiveSupport::TimeZone.zones_map(&:name)
has_many :photos
has_many :letsgos, dependent: :destroy
belongs_to :default_photo, :class_name => "Photo"
has_many :notifications
has_many :questions
belongs_to :location
belongs_to :zip
has_many :messages
belongs_to :avatar, class_name: 'Photo'
has_many :received_messages, class_name: 'Message', foreign_key: 'recipient_id'
has_many :sent_messages, class_name: 'Message', foreign_key: 'sender_id'
has_many :users, dependent: :destroy
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id", class_name: "Relationship", dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
validates_format_of :zip_code,
with: /\A\d{5}-\d{4}|\A\d{5}\z/,
message: "should be 12345 or 12345-1234"
validates_uniqueness_of :email
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
validates_uniqueness_of :username
validates_presence_of :username
validates_format_of :username, :with => /\A[a-zA-Z0-9]+\Z/, :message => "should only contain letters or numbers"
validates :password, :presence => true,
:confirmation => true,
:length => {:within => 6..40},
:on => :create
before_create { generate_token(:auth_token) }
ROLES = %w[admin user guest banned]
def received_messages
Message.received_by(self)
end
def unread_messages?
unread_message_count > 0 ? true : false
end
def unread_messages
received_messages.where('read_at IS NULL')
end
def sent_messages
Message.sent_by(self)
end
def deleted_messages
Message.where(recipient_deleted: self)
end
# Returns the number of unread messages for this user
def unread_message_count
eval 'messages.count(:conditions => ["recipient_id = ? AND read_at IS NULL", self.user_id])'
end
Log:
2014-02-05T20:46:24Z 36507 TID-107jr10 WARN: {"retry"=>true, "queue"=>"default", "class"=>"UserMessageWorker", "args"=>[152, 1], "jid"=>"68109564031c679162dda497", "enqueued_at"=>1391632954.347344, "error_message"=>"Mysql2::Error: Unknown column 'messages.user_id' in 'where clause': SELECT COUNT(*) FROM `messages` WHERE `messages`.`user_id` = 1", "error_class"=>"ActiveRecord::StatementInvalid", "failed_at"=>"2014-02-05T20:42:34Z", "retry_count"=>3, "retried_at"=>2014-02-05 20:46:24 UTC}
2014-02-05T20:46:24Z 36507 TID-107jr10 WARN: Mysql2::Error: Unknown column 'messages.user_id' in 'where clause': SELECT COUNT(*) FROM `messages` WHERE `messages`.`user_id` = 1
What does you User model looks like?
EDIT:
You might need to add
usermessages = user.received_messages + user.sent_messages
to you worker and edit the response_rate line to this
readmessages = []
usermessages.each do |um|
if um.read_at != nil
readmessages << um
end
end
response_rate = (readmessages.count)/(old_msg_count + 1)
Let me know if this help. You might need to tweak it a little to work correctly but that should fix it
EDIT:
Or even better i think you can do this
usermessages = user.received_messages.where("read_at IS NOT NULL").count + user.sent_messages.where("read_at IS NOT NULL").count
and edit this line:
response_rate = usermessages/(old_msg_count + 1)
FULL WORKING CLASS:
class UserMessageWorker
include Sidekiq::Worker
def perform(message_id, recipient_id)
message = Message.find(message_id)
user = User.find(recipient_id)
message.read_at = Time.now
old_msg_count = user.received_messages.count + user.sent_messages.count
usermessages = user.received_messages.where("read_at IS NOT NULL").count + user.sent_messages.where("read_at IS NOT NULL").count
if message.save
msg_response_time = message.read_at - message.created_at
response_rate = usermessages/(old_msg_count + 1)
response_time = ((user.average_response_time * old_msg_count)+msg_response_time)/(old_msg_count + 1)
user.update_attributes(:response_rate => response_rate, :average_response_time => average_response_time )
end
end
end

API: Update model through an other related model

I'm trying make it possible to update a a LineItem trough a CreditNote. It's for an API, so I'm trying to update that trough a JSON.
My relational model is:
class TestCreditNote < ActiveRecord::Base
self.table_name = :credit_notes
has_many :line_items, :class_name => TestLineItem, :foreign_key => :artef_id
accepts_nested_attributes_for :line_items
end
class TestLineItem < ActiveRecord::Base
self.table_name = :line_items
attr_accessible :description
belongs_to :credit_note, :class_name => TestCreditNote, :foreign_key => :artef_id
end
When executing this test:
it "should update the sales line item record" do
put "api/v1/credit_notes/#{#credit_note.id}", { :test_credit_note => { :line_items => [{ :description => 'PEPITO'}] }}, http_headers
data = JSON.parse(response.body, :symbolize_names => true)
TestCreditNote.find(#sales_credit_note.id).line_item.description.should == 'PEPITO'
end
It fails because of:
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: line_items
I've add the attr_accesible :line_items_attributes
class TestCreditNote < ActiveRecord::Base
self.table_name = :credit_notes
has_many :line_items, :class_name => TestLineItem, :foreign_key => :artef_id
accepts_nested_attributes_for :line_items
attr_accessible :line_items_attributes
end
And the same in the test
it "should update the sales line item record" do
put "api/v1/credit_notes/#{#credit_note.id}", { :test_credit_note => { :line_items_attributes => [{:id => 1, :description => 'PEPITO'}] }}, http_headers
data = JSON.parse(response.body, :symbolize_names => true)
TestCreditNote.find(#sales_credit_note.id).line_item.description.should == 'PEPITO'
end

Rails Nested forms, not add a second element if user dont clic on "add new"

Hi im using Rails Nested forms, i need something very simple, but i cant figure put why, cause im a noob in Rails.
In the nested forms, when the user select an option on the list, the script automatically insert a second select list, in blank, this blanks remains always there,
Is there a way to avoid this.. so if you select nothing happends, (just the selection ofcourse) until you hit "add new", just then the script add a new select list for user to add another option.
Thanks..
View code
<%= f.fields_for :citizens do |citizen_form| %>
<div>
<%= citizen_form.label :citizen, t('generales.citizen') %>
<%= citizen_form.select :country_id , Country.all.collect {|p| [ t("generales."+p.iso), p.id ] }, { :include_blank => true } , { :class => 'pca33' } %>
<div id="delerr"><%= citizen_form.link_to_remove t('generales.delete') %></div>
</div>
<% end %>
<%= f.link_to_add t('generales.add'), :citizens %>
Model Player.rb
class Player < ActiveRecord::Base
belongs_to :user
has_many :clubs
has_many :links
has_many :references
has_many :achievements
has_many :citizens
has_and_belongs_to_many :languages
has_and_belongs_to_many :selections
accepts_nested_attributes_for :clubs, :allow_destroy => true, :reject_if => proc { |attributes| attributes['name'].blank? }
accepts_nested_attributes_for :links, :allow_destroy => true, :reject_if => proc { |attributes| attributes['url'].blank? }
accepts_nested_attributes_for :references, :allow_destroy => true, :reject_if => proc { |attributes| attributes['name'].blank? }
accepts_nested_attributes_for :achievements, :allow_destroy => true, :reject_if => proc { |attributes| attributes['name'].blank? }
accepts_nested_attributes_for :citizens, :allow_destroy => true, :reject_if => proc { |attributes| attributes['country_id'].blank?}
attr_accessible :name,
:lastname,
:birthday,
:height,
:height_measure,
:weight,
:weight_measure,
:inches,
:city,
:birthplace,
:other_languages,
:cp,
:phone,
:cellphone,
:web_page,
:game_status,
:club,
:actual_club,
:actual_country_club,
:actual_division_club,
:actual_contract_expiration_club,
:last_club,
:last_country_club,
:last_division_club,
:last_contract_expiration_club,
:position,
:alternative_position,
:dominant_leg,
#normal player
:short_passes,
:long_passes,
:shots_half_distance,
:shots_long_distance,
:ball_habilities,
:offensive_capability,
:ball_driving,
:defense_capability,
:dribbling,
:velocity,
:vision_field,
:movements_wothout_ball,
:recovery_ball,
:head_ball,
:lidership,
:teamwork,
#goalkeeper
:air_game,
:clearance_technique, #técnica de despeje
:ball_keep, #atajes
:flexibility, #flexibilidad
:penalty_keep, #atajar penales
:achique,
:defense_communication,
:foot_game,
:velocity_reaction, #reflejos
:area_domination,
:goalkeep_teamwork,
:goalkeep_lidership,
:strenghts,
:weaknesses,
:aditional_information,
:active,
:clubs_attributes,
:links_attributes,
:references_attributes,
:achievements_attributes,
:citizens_attributes,
:avatar_file_name,
:avatar_content_type,
:avatar_file_size,
:avatar,
:language_ids,
:selection_ids
POSITIONS = %w{
goalkeeper
defense
medium
offensive
}
LEG = %w{
left
right
both
}
# altura
HEIGHT = (1..200).to_a
INCH = (1..11).to_a
# peso
WEIGHT = (1..300).to_a
HEIGHT_MEASURE = %w{
cms
pies
}
WEIGHT_MEASURE = %w{
kgs
lbs
}
Paperclip.interpolates :random_hex do |attachment, style|
attachment.instance.random_hex
end
has_attached_file :avatar, :styles => { :profile => "300x300", :thumb => "100x100#"},
:url => "/assets/people/:id/:style/:hash.:extension",
:path => ":rails_root/public/assets/people/:id/:style/:hash.:extension",
:hash_secret => "longSecretString"
validates_attachment_size :avatar, :less_than => 2.megabytes # Solo aceptar imágenes menores a 2 Mb.
validates_attachment_content_type :avatar, :content_type => ['image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png', 'image/gif']
def defeated?
t = Time.now - created_at
mm, ss = t.divmod(60)
hh, mm = mm.divmod(60)
dd, hh = hh.divmod(24)
dd > 180 ? true : false
end
end
Model citizen.rb
class Citizen < ActiveRecord::Base
attr_accessible :country_id
belongs_to :player
end

Rails: array created in one model, reach in from form in another

I've got a form view of an Order model (orders_form.html.erb) with a select option:
<%= f.select :pay_type, PaymentType.array_of_payment_types,
:prompt => 'Select a payment method' %>
PaymentType is another model and .array_of_payment_types is an array created out of the entries in the payment_type_name column, like so:
def self.array_of_payment_types
#array_of_payment_types ||= PaymentType.pluck(:pay_type_name)
end
... from models\payment_type.rb
But I get a proc 'empty?' error:
undefined method `empty?' for #
I hope my problem is clear, it seems like there is an obvious solution but I haven't found one reading other questions so far...
I will update with the relationships in the models...
My models:
payment_type.rb:
class PaymentType < ActiveRecord::Base
attr_accessible :pay_type_name
has_many :orders
validates :pay_type_name, :uniqueness
def self.names
all.collect { |pt| pt.pay_type_name }
end
def self.array_of_payment_types
PaymentType.all.map{ |p| [p.pay_type_name, p.id] }
end
end
order.rb:
class Order < ActiveRecord::Base
attr_accessible :address, :email, :name, :pay_type, :payment_type_id, :cart_id,
:product_id
has_many :line_items, :dependent => :destroy
belongs_to :payment_type
#PAYMENT_TYPES = ['Check','Purchase order','Credit card']
validates :name, :address, :email, :presence => true
validates :pay_type,
:presence => true,
:inclusion => { :in => proc { PaymentType.array_of_payment_types } }
def add_line_items_from_cart(cart)
cart.line_items.each do |item|
item.cart_id = nil
line_items << item
end
end
end
Try using the options_for_select:
# in the view:
<%= f.select :pay_type, options_for_select(PaymentType.array_of_payment_types),
:prompt => 'Select a payment method' %>
# in the PaymentType model:
def self.array_of_payment_types
PaymentType.all.map{ |p| [p.pay_type_name, p.id] }
end
You also need to update your validates statement in the Order model:
validates :pay_type,
:presence => true,
:inclusion => { :in => proc { PaymentType.pluck(:pay_type_name) } }

Resources