Internal messaging in Rails - ruby-on-rails

I'm using this tutorial to get internal messages working on my site: http://www.novawave.net/public/rails_messaging_tutorial.html
But, since my latest upgrade to Rails 3, I'm getting this error:
NoMethodError in MsgController#sendmsg
undefined method `each' for #<String:0xcc8acc0>
Application trace:
app/models/message.rb:16:in `prepare_copies'
app/controllers/msg_controller.rb:140:in `sendmsg'
The Message model:
class Message < ActiveRecord::Base
belongs_to :author, :class_name => "User"
has_many :message_copies
has_many :recipients, :through => :message_copies
before_create :prepare_copies
attr_accessor :to # array of people to send to
attr_accessible :subject, :body, :to
def prepare_copies
return if to.blank?
to.each do |recipient|
recipient = User.find(recipient)
message_copies.build(:recipient_id => recipient.id, :folder_id => recipient.inbox.id)
end
end
end

That tutorial seems a bit dated. It uses Rails 2.0 (and probably some equally old Ruby version).
Are you sure that to holds an Array (as indicated in your attr_accessor comment)? The error message seems to indicate that it is a String.
Did you previously have this code running under Ruby 1.8 (and, presumably, a version of Rails 2.3)?
In Ruby 1.8 you could send each to String instances and it would (by default) iterate on the lines of the string (actually it would split on $/ and iterate on the result).
In Ruby 1.9 you need to use each_line to iterate over the lines of a string.
to.each_line do |recipient|
…
end

Seems you to is no anymore an Array but a String now. You problem semmes becomes from your controller and how you define your to accessor inside it.

Related

How to disregards the object while querying using ActiveRecord::Base methods whose associated object has been deleted from database in rails

I have Person<ActiveRecord::Base model
class Person < ActiveRecord::Base
has_many :sent_messages, :class_name => 'Message', :inverse_of => :sender
has_many :received_messages, :class_name => 'Message', :inverse_of => :receiver
#.....
end
and another modeActiveRecord::Base model
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'Person'
belongs_to :receiver, :class_name => 'Person'
# ......
end
; and ActiveRecord::Migration class supporting these models. Now my problem is that I need to output the messages received by current user with the Person name of sender by querying database, but while querying it seems that some of the person(user) who sent the messages has been deleted from database(but messages once exchanged b/w two persons is saved permanently in database and no body have the permission to delete it, So each message will be there with all the details like receiver_id and sender_id .). So when I query like this.
#messages = Message.where(:receiver_id => current_user.id)
.includes(:sender).order("updated_at DESC")------------(1)
it works fine if person who has sent the message to currently logged in user has it's delete_at attribute null(meaning not deleted from database and can be used without any error in views for sender name) but for those which has been deleted it gives this error in browser console
in application controller asset is undefined method or variable ----------(2)
where a method in application controller to handle no record found exception
def respond_to_not_found(*types)
flash[:warning] = t(:msg_asset_not_available, asset)
respond_to do |format|
format.html { redirect_to(redirection_url) }
# ...
end
end
please tell What is the t and asset is in answer ???
So as a workaround of problem (1) -------- (3).
I queried this and
def show_list
#messages = Array.new
#person = Array.new
messages_temp = Message.where(:receiver_id => current_user.id).
includes(:sender).order("updated_at DESC")
i = 0
messages_temp.each do |msg|
if(!(msg.sender.nil?))
#messages[i] = msg
#person[i] = msg.sender
i = i+1
end
end
end
My questions:-
Q.1) I haven't much worked with database so this is valid question for me(as the project I am working on explicitly destroys the requested user):- Why the user info has not been deleted from database but rather it's deleted_at attribute has become not null whereas for others which are intact have same attribute null. And when I find(query by method find) an object of Person from database then while querying it's SQL conversion query for deleted_at attribute should be null. So why is happening am I missing something in my project code or it is general behaviour. If it general then how to completely expunge the data.
Even if such behaviour is general or not how to recover it, without manually changing each deleted Person deleted_at attribute to null. And how to access some of it's attribute without fully restoring the Person(or if such thing is possible).
Q.2) What might be other reasons for getting error #(2) even after workaround. As I tested the workaround and it worked fine for some of deleted sender's. But even after at some places I am getting error #(2) so in general what does this error stand for and what might be it's other cause then the one I mentioned???
Q.3)Is there better solution then the workaround given in eq #(3) because you see in my workaround msg.sender.nil? is true for the deleted object so I think there might be some. I tried the net but with no success. So, how to filter out those messages whose associated object is deleted how to query them all at once then doing it one by one as in #(2).
As workaround is giving me hard time to paginate the output because all the available pagination gem (for example 'will_paginate', 'kaminari', 'pagination') works on ActiveRelation but in my workaround I have Array object i.e #messageson which I can not use these methods. So it would be great help if one can answer a way to paginate my workaround i.e object #messages which is an array or a way to filter out those messages whose sender is deleted from database then I can paginate in this way :-
#messages = Message.<ActiveRecord::Base method to get the desired output>.paginate[params]
PS:- I know the question is tedious but I believe it is essential for this question I am seeking answer to. Any help will be appreciated even the partial answer to the question.
Thanks a lot!!!
Ok...
Well it took me couple of days but with some help from my vicinity I did figure out answer to most of the confusion and ambiguity:
Ans:-
1:) There is a way to soft delete the data meaning hidden from normal query... Most of ambiguities of question one can be answered from this [link][1]. This gem(acts_as_paranoid) helps soft deleting the data. yes, attributes of soft deleted object can be accessed without restoring the date; and data can be completely expunged as well(everything in the link). The changes I needed to make in my code base to avail these benefits are these:-
In Person<ActiveRecord::Base model
class Person < ActiveRecord::Base
# has_many :sent_messages, :class_name => 'Message', :inverse_of => :sender
# has_many :received_messages, :class_name => 'Message', :inverse_of => :receiver
has_many :sent_messages, :class_name => 'Message',
:inverse_of => [:sender, :sender_including_deleted]
has_many :received_messages, :class_name => 'Message',
:inverse_of => [:receiver, :receiver_including_deleted]
#.....
end
and in the model ActiveRecord::Base
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'Person'
belongs_to :receiver, :class_name => 'Person'
# Add these two lines:-
belongs_to :sender_including_deleted, :class_name => 'Person',
:foreign_key => 'sender_id', :with_deleted => true
belongs_to :receiver_including_deleted, :class_name => 'Person',
:foreign_key => 'receiver_id', :with_deleted => true
# ......
end
So, adding these two lines worked for me but most importantly the gem helped me a lot.
2:) One of prominent reason for getting error in (2) is that one may be trying to directly access the attribute of object which may be deleted. Suppose a Person is deleted(soft or hard doesn't matter whose id was 15) so first_name = Person.find(15).first_name will give error where as person = Person.find(15) if(!person.nil?) return first_name will not as we are checking if returned person is nil or not. and if Person is deleted with gem acts_as_paranoid then a soft deleted(meaning deleted_at attribute is not null. The deleted_at(this attribute has to be added in migration to use) attribute is updated when object is deleted for clear picture see the embedded link here) object accessed first_name = Person.with_delete.find(15) like this won't generate an error
Note:- I don't what might be other reason. please answer this part if you can think of any.
3:) Although I do not have direct answer to question asked in 3 but since now I can access the message sender info even though they are deleted(in active relation form) so ordering and pagination works but I surely like to know how to paginate if object in not active relation.
So, this one I best answered like this:-
def show_list
# #messages = Array.new
# #person = Array.new
# messages_temp = Message.where(:receiver_id => current_user.id).
# includes(:sender).order("updated_at DESC")
# i = 0
# messages_temp.each do |msg|
# if(!(msg.sender.nil?))
# #messages[i] = msg
# #person[i] = msg.sender
# i = i+1
# end
# end
# REPLACE ABOVE CODE BY THIS. THe following code will
# include deleted messages as well.
#person = Array.new
#messages = Message.where(:receiver_id =>current_user.id).
includes(:sender_including_deleted).
order("created_at DESC").paginate(params).readonly
i = 0
#messages.each do |msg|
#person[i] = msg.sender_including_deleted(:with_deleted => true)
i = i + 1
end
end
I hope the people who stumbled upon this question get what they are looking. If they didn't get any part of my answer or question then they are welcome to comment here and I will be happy to answer to best of my abilities.

Issue with collection `build` method record instantiation in Rails 4.1.1

I'm having a problem with the Rails collection.build(attrs) method, specifically with how the framework instantiates a new record. For example, here is a much simplified view of my models:
class Document < ActiveRecord::Base
belongs_to :account
has_many :descriptions, before_add: :id_test
validates :account, presence: true
def id_test
puts self.account_id
end
end
When I do something like:
current_account.documents.build(:descriptions => [desc])
then id_test prints nothing. That is, the account_id is not set in the before_add callback (and yes, I've tried the after_add callback as well; account_id is not set in that case either).
If I do:
d = current_account.documents.build
d.assign_attributes(:descriptions => [desc])
Then everything works as expected. However, I would prefer a better alternative, since this would be a pain to implement in the controllers...
Is there a way to get Rails to add the foreign_key first, or is there some better way to set this up? I haven't gone back to check for sure, but this seems different than the way Rails 3 evaluated collection.build statements.
EDIT
Looking through the Rails code a bit, I see I can do:
current_account.documents.build do |record|
record.assign_attributes(:descriptions => [desc])
end
and everything works as expected. Although a bit more verbose, I guess this is technically more accurate anyway.
I would suggest using
Document.build(:account_id => current_account.id, :descriptions => [desc])

Rails model returning nil ID

I need to get the id of a certain object. It's probably something really simple that I am missing. The object I need comes out from the database, and I'm able to retrieve its attributes but not the id.
Part.where("code ='p8z68vprogen3'").first
# => <Part id: 486, code: "p8z68vprogen3", etc...>
Part.where("code ='p8z68vprogen3'").first.id
# => nil
Part.where("code ='p8z68vprogen3'").first.code
# => "p8z68vprogen3"
This is the model:
class Part < ActiveRecord::Base
has_many :build_parts
has_many :builds, :through => :build_parts
belongs_to :category
attr_accessible :code,:link,:description,:category_id
end
I suppose it's something related to attr_accessible or attr_accessor, I tried to fiddle with them but nothing so I ask for help.
EDIT:
asking for a reload returns an error in any way i try i get the object(where or find_by_)
part_needed = Part.where("code ='p8z68vprogen3'").first
# => <Part id: 486, code: "p8z68vprogen3", etc..>
part_needed.reload
# ActiveRecord::RecordNotFound: Couldn't find Part without an ID
Also, the parts table is already populated, but here is the code that creates an entry:
part = Part.new(
:description => objs[1],
:code => objs[2],
:category_id => tips[objs[3].to_i]
)
part.save!
This code is executed in another part of the code is not just before where I try to get the ID.
Restart your machine. I know it doesn't sound logical. I encountered a problem where I had two objects of the same class and the first/last method returned the last object. Restarting worked for me. (rails 3.1.1, ruby 1.9.2p320 (2012-04-20 revision 35421) [i686-linux])
)

ActiveAdmin, polymorphic associations, and custom filters

Rails 3.1, ActiveAdmin 0.3.4.
My question is somewhat similar to this one but different enough in terms of data modeling that I think it warrants its own response. Models:
class CheckoutRequest < ActiveRecord::Base
has_one :request_common_data, :as => :requestable, :dependent => :destroy
end
class RequestCommonData < ActiveRecord::Base
belongs_to :requestable, :polymorphic => true
end
The RequestCommonData model has a completed field (boolean) that I'd like to be able to filter in ActiveAdmin's CheckoutRequest index page. I've tried a few different approaches to no avail, including the following:
filter :completed, :collection => proc { CheckoutRequest.all.map { |cr| cr.request_common_data.completed }.uniq }
which results in no filter being displayed. Adding :as => :select to the line, as follows:
filter :completed, :as => :select, :collection => proc { CheckoutRequest.all.map { |cr| cr.request_common_data.completed }.uniq }
results in the following MetaSearch error message:
undefined method `completed_eq' for #<MetaSearch::Searches::CheckoutRequest:0x007fa4d8faa558>
That same proc returns [true, false] in the console.
Any suggestions would be quite welcome. Thanks!
From the meta_search gem page you can see that for boolean values the 'Wheres' are:
is_true - Is true. Useful for a checkbox like “only show admin users”.
is_false - The complement of is_true.
so what you need is to change the generate input name from 'completed_eq' to be 'completed_is_true' or 'completed_is_false'.
The only way I have found this possible to do is with Javascript, since by looking at the Active Admin code, the 'Wheres' are hardcoded for each data type.
I would usually have a line like this in my activeadmin.js file (using jQuery)
$('#q_completed_eq').attr('name', 'q[completed_is_true]');
or
$('#q_completed_eq').attr('name', 'q[completed_is_false]');
Terrible and ugly hack but have found no other solution myself.
Be careful to enable this only in the pages you want.
--- NEW FOR VERSION 0.4.2 and newer ---
Now Active Admin uses separate modules for each :as => ... option in the filters.
So for example you can place the code below inside an initializer file
module ActiveAdmin
module Inputs
class FilterCustomBooleanInput < ::Formtastic::Inputs::SelectInput
include FilterBase
def input_name
"#{#method}_is_true"
end
def input_options
super.merge(:include_blank => I18n.t('active_admin.any'))
end
def method
super.to_s.sub(/_id$/,'').to_sym
end
def extra_input_html_options
{}
end
end
end
end
and the use
:as => :custom_boolean
where you specify your filter.

ActiveRecord serialize using JSON instead of YAML

I have a model that uses a serialized column:
class Form < ActiveRecord::Base
serialize :options, Hash
end
Is there a way to make this serialization use JSON instead of YAML?
In Rails 3.1 you can just
class Form < ActiveRecord::Base
serialize :column, JSON
end
In Rails 3.1 you can use custom coders with serialize.
class ColorCoder
# Called to deserialize data to ruby object.
def load(data)
end
# Called to convert from ruby object to serialized data.
def dump(obj)
end
end
class Fruits < ActiveRecord::Base
serialize :color, ColorCoder.new
end
Hope this helps.
References:
Definition of serialize:
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/base.rb#L556
The default YAML coder that ships with rails:
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/coders/yaml_column.rb
And this is where the call to the load happens:
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_methods/read.rb#L132
Update
See mid's high rated answer below for a much more appropriate Rails >= 3.1 answer. This is a great answer for Rails < 3.1.
Probably this is what you're looking for.
Form.find(:first).to_json
Update
1) Install 'json' gem:
gem install json
2) Create JsonWrapper class
# lib/json_wrapper.rb
require 'json'
class JsonWrapper
def initialize(attribute)
#attribute = attribute.to_s
end
def before_save(record)
record.send("#{#attribute}=", JsonWrapper.encrypt(record.send("#{#attribute}")))
end
def after_save(record)
record.send("#{#attribute}=", JsonWrapper.decrypt(record.send("#{#attribute}")))
end
def self.encrypt(value)
value.to_json
end
def self.decrypt(value)
JSON.parse(value) rescue value
end
end
3) Add model callbacks:
#app/models/user.rb
class User < ActiveRecord::Base
before_save JsonWrapper.new( :name )
after_save JsonWrapper.new( :name )
def after_find
self.name = JsonWrapper.decrypt self.name
end
end
4) Test it!
User.create :name => {"a"=>"b", "c"=>["d", "e"]}
PS:
It's not quite DRY, but I did my best. If anyone can fix after_find in User model, it'll be great.
My requirements didn't need a lot of code re-use at this stage, so my distilled code is a variation on the above answer:
require "json/ext"
before_save :json_serialize
after_save :json_deserialize
def json_serialize
self.options = self.options.to_json
end
def json_deserialize
self.options = JSON.parse(options)
end
def after_find
json_deserialize
end
Cheers, quite easy in the end!
The serialize :attr, JSON using composed_of method works like this:
composed_of :auth,
:class_name => 'ActiveSupport::JSON',
:mapping => %w(url to_json),
:constructor => Proc.new { |url| ActiveSupport::JSON.decode(url) }
where url is the attribute to be serialized using json
and auth is the new method available on your model that saves its value in json format to the url attribute. (not fully tested yet but seems to be working)
I wrote my own YAML coder, that takes a default. Here is the class:
class JSONColumn
def initialize(default={})
#default = default
end
# this might be the database default and we should plan for empty strings or nils
def load(s)
s.present? ? JSON.load(s) : #default.clone
end
# this should only be nil or an object that serializes to JSON (like a hash or array)
def dump(o)
JSON.dump(o || #default)
end
end
Since load and dump are instance methods it requires an instance to be passed as the second argument to serialize in the model definition. Here's an example of it:
class Person < ActiveRecord::Base
validate :name, :pets, :presence => true
serialize :pets, JSONColumn.new([])
end
I tried creating a new instance, loading an instance, and dumping an instance in IRB, and it all seemed to work properly. I wrote a blog post about it, too.
A simpler solution is to use composed_of as described in this blog post by Michael Rykov. I like this solution because it requires the use of fewer callbacks.
Here is the gist of it:
composed_of :settings, :class_name => 'Settings', :mapping => %w(settings to_json),
:constructor => Settings.method(:from_json),
:converter => Settings.method(:from_json)
after_validation do |u|
u.settings = u.settings if u.settings.dirty? # Force to serialize
end
Aleran, have you used this method with Rails 3? I've somewhat got the same issue and I was heading towards serialized when I ran into this post by Michael Rykov, but commenting on his blog is not possible, or at least on that post. To my understanding he is saying that you do not need to define Settings class, however when I try this it keeps telling me that Setting is not defined. So I was just wondering if you have used it and what more should have been described? Thanks.

Resources