Correct way to define virtual attributes on a Model for the keys in a JSON column in rails - ruby

In my rails model I have a JSON column which stores some meta information.
This is to be entered bu the user from a form.
Since the keys of the JSON column are not attributes of the model I cannot use them directly in form_for instead I need to define a virtual attribute.
Since this number of virtual attributes could grow to be arbitrarily lengthy I would like to use meta programming to define the attributes.
I did try the answer in this question however when I use the constant in my model I get an error saying that the constant is undefined. So I added the symbols for the keys in an array directly and iterate over them in the module. When I do this I get an error that says stack level too deep.
Please can someone help me out here?

If you are using PostgreSQL specific columns like hstore or json simply use store_accessor instead to generate the accessor methods. Be aware that these columns use a string keyed hash and do not allow access using a symbol.
class Model < ActiveRecord::Base
store_accessor :my_json_column, [ :key_1, :key_2, key_3 ]
end
What it doing under the hood? It has define write\read helper methods:
def store_accessor(store_attribute, *keys)
keys = keys.flatten
_store_accessors_module.module_eval do
keys.each do |key|
define_method("#{key}=") do |value|
write_store_attribute(store_attribute, key, value)
end
define_method(key) do
read_store_attribute(store_attribute, key)
end
end
end
# .....
store

I figured it out. I return the attribute as a key of the JSON column and it works fine now.
# lib/virtuals.rb
module Virtuals
%W(key_1 key_2 key_3).each do |attr|
define_method(attr) do
self.my_json_column[attr]
end
define_method("#{attr}=") do |val|
self.my_json_column[attr] = val
end
end
end
In my Model i just need to include that above module and it works fine in the form_for and updates correctly as well.

Related

Getting Sequel associations through Sinatra

I'm trying to return json-formatted data from my Sinatra REST API. I currently have a bunch of associations set up, but I'm having trouble getting the views I want from my API despite getting them easily in Ruby.
For example, from my tables:
DB.create_table?(:calendars) do
primary_key :id
end
DB.create_table?(:schedules) do
primary_key :id
foreign_key :resource_id, :resources
foreign_key :task_id, :tasks
foreign_key :calendar_id, :calendars
end
In Ruby, I'm able to run a block like this and display all the info I need through my associations:
Calendar.each do |c|
c.schedules.each do |s|
puts "RESOURCE ##{s.resource_id}"
s.tasks.each do |t|
p t
end
puts
end
end
the c.schedules call works because my calendar model contains a one_to_many :schedules association.
Now, I'm wondering how this translates to my Sinatra API. In my simple GET route, I've tried many variations trying to get the schedules associated with a calendar, and convert it to JSON:
get '/calendars' do
c = DB[:calendar].first
c.schedules.to_json
content_type :json
end
... but I'll end up with an error like undefined method 'schedules' for {:id=>1}:Hash
So it looks like it's returning a hash here, but I've tried a bunch of stuff and haven't figured out how I'm supposed to work with my associations in Sinatra. How can I do this?
Thanks!
The reason your first block works but the second doesn't is because in the first case, you're using a Sequel model instance of class Calendar, whereas in the second case you're using a Sequel dataset.
When you iterate over Calendar.each do |c|, the c variable gets populated with an instance of a Calendar class Sequel model object. This object has relationship methods defined (one_to_many) and you're able to query schedules and run other model methods on it.
However, c = DB[:calendar].first gets you a Sequel dataset. This object is different than a model instance, it returns a standard Ruby hash (or an array of hashes).
You can change your 2nd block to use a model instead and it will get the result you want:
get '/calendars' do
c = Calendar.first # <=== CHANGE FROM DATASET TO MODEL
c.schedules.to_json
content_type :json
end

Can i convert an attribute from active record from Big Decimal to Integer?

I have an attribute coming back from Active Record that is a Big Decimal. The database column's data is numeric(18,0) and I'm not authorized to change it. i would like to be able to convert the attribute into an Integer with no decimal precision, but i'm not having any luck. I know that I can convert the value into something else by using big_decimal.to_i, however I am hoping there is a way to handle this within the Active Record Model possibly after_initialize so that I don't have to worry about conversion anywhere else in my code. Any help would be appreciated, thanks.
If you're stuck with a legacy schema you can always work around that by adding a wrapper method:
class MyModel < ActiveRecord::Base
def int_column
value = read_attribute(:big_decimal)
# Preserve `nil` values and avoid converting to zero.
value and value.to_i
end
def int_column=(value)
write_attribute(:big_decimal, value)
end
end
That gives you a method for reading/writing from that column using an alternate name.
You can always alias the current method and then override it.
Let's say the attribute is charge
class Transaction < ActiveRecord::Base
# right now #charge returns #<BigDecimal:7fb6c285fe28,'0.0',9(18)>
# now we can alias the current method #charge
alias_method :big_decimal_charge, :charge
# now they both return #<BigDecimal:7fb6c285fe28,'0.0',9(18)>
# and we can redefine #charge, based on the alias
def charge
big_decimal_charge.to_i
end
end
I wouldn't imagine there would be a need to change the setter.

Ruby on Rails to_xml nil="True"

I need your help on to_xml function. How can i make all nil="True" value to a default value '' (blank) when exporting to xml from active record.
The #to_xml method Rails adds to ActiveRecord, Array, and Hash uses the builder gem by default. The XML is also passed through ActiveSupport::XmlMini where the addition of the nil="true" attribute is hard coded to always be added for nil attributes.
You should probably look at using builder directly to build your XML if these values are problematic.
Builder::XmlMarkup.new.object{|xml| xml.value "" }
#=> "<object><value></value></object>"
You could also use other XML libraries. I only recommend builder because it is the rails default and likely already installed.
Another option is to convert the object into a Hash first (object.attributes works if object is an ActiveRecord instance). You can then convert any nils into blank strings.
data = object.attributes
data.each_pair{|col, val| data[col] = "" if val.nil? }
data.to_xml
You can add a method to set special default values for XML generation. This method can then be called from an overridden to_xml method which duplicates the record in memory, sets default values and finally generates the xml. Example code:
class Post < ActiveRecord::Base
def set_xml_defaults
blanks = self.attributes.find_all{|k,v| v.nil? }.map{|k,v| [k,''] }
self.attributes = Hash[blanks]
end
alias_method :to_xml_no_defaults, :to_xml
def to_xml(options = {}, &block)
dup = self.dup
dup.set_xml_defaults
dup.to_xml_no_defaults
end
end

Specify column for model attributes in Rails3?

Is it possible to specify the column for an attribute? I have something like:
NAME, COUNTRY
The database is quite large and I have over a thousand columns which are capitalized like this. I want to refer to them as so:
attr_accessible :name, :country
Where :name = column NAME. I'd prefer Model.name rather than Model.NAME. It isn't possible to downcase every column name in the structure file.
Here is an idea to do the way you preferred.
Command to generate migration: (In my example, im applying this on Posts table. Change according to your table name)
rails g migrate RenameColumnsOfPosts
Below is the migration up method. Here taking all the column names and for each one I'm applying rename_column to make it downcase.
class RenameColumnsOfPosts < ActiveRecord::Migration
def up
Post.columns.map(&:name).each do |column_name|
rename_column(:posts, column_name, column_name.downcase)
end
end
def down
#You can do the opposite operation here. Leaving on you
end
end
As i didnt run it personally, it might need some changes. So start with it and let me know if facing any problem.
Please write code inside of model ,
it's just for demostration code,after you get it and update as per logic :
This Code inside of model :
...
attr_accessor :name,:country
before_save :fill_save
..
#assign variable like ...
def fill_save
self.NAME= self.name
self.COUNTRY= self.country
end
....

Can't display more than one table model inheriting from the same class on different tables in QtRuby

I've been following this article to display ActiveRecord data in QtRuby. I've copied the BoatTableModel class from there(used my own code for the rest). In the article, BoatTableModel is defined to only support the Boat model, but except for the column definitions the code is quite generic. So, I've changed it so instead of having the columns defined there, I've made it take the columns from a column_names method, and define that methods in subclasses for each model.
Here is my code:
class QtArModel<Qt::AbstractTableModel
def initialize(items)
super()
#items=items
end
def rowCount(parent=nil)
#items.size
end
def columnCount(parent=nil)
column_names.length
end
def data(index,role=Qt::DisplayRole)
invalid=Qt::Variant.new
return invalid unless role==Qt::DisplayRole or role==Qt::EditRole
item=#items[index.row]
return invalid if item.nil?
v=item[column_names[index.column]]||""
return Qt::Variant.new(v)
end
def headerData(section,orientation,role=Qt::DisplayRole)
invalid=Qt::Variant.new
return invalid unless role==Qt::DisplayRole
v=case orientation
when Qt::Horizontal
column_names[section]
else
""
end
return Qt::Variant.new(v.to_s)
end
def flags(index)
return Qt::ItemIsEditable|super(index)
end
def setData(index,variant,role=Qt::EditRole)
if index.valid? and role==Qt::EditRole
s=variant.toString
item=#items[index.row]
if index.column.between?(0,column_names.length-1)
item[column_names[index.column]]=s
else
raise "invalid column #{index.column}"
end
item.save
emit dataChanged(index,index)
else
return false
end
end
end
class QtCoursesTableModel<QtArModel
def column_names
return [
:number,
:name,
:tutor_name,
:site,
:active,
]
end
end
class QtTasksTableModel<QtArModel
def column_names
return [
:course,
:ex_number,
:received,
:due,
:description,
:link,
:completed,
:file,
]
end
end
Now, when I display one model(doesn't matter which) - everything works just fine. However, when I display both models, each in it's own Qt::TableView - only the first one is displayed, and the other table view is blank.
I've tried different ordering, and the table that gets to display it's data is always the one which it's Qt::TableView is created first - the order of the creating the Qt models does not matter. Also, when I create the model object for the first table, but don't actually set it's model property to it, the second table displays it's data.
I've also tried to display the same model twice in two different table views - and it worked - for a split second, and then the second view's data disappeared.
I've also tried to copy-paste the QtArModel, change it's name, and make one of the models inherit from the copy. That did work - but it's obviously a huge code duplication, so I would really like to avoid that.
Now, my guess is that something in QtArModel is defined as a class member instead of instance member, making both model instances share something they shouldn't share. It has to be in QtArModel - because if it was higher in the inheritance tree, the problem would have remained when I've duplicated QtArModel. However, I can't find anything in my QtArModel that's class-scoped instead of instance-scoped.
What am I missing?
OK, I've managed to work this out. Apparently, the problem was not the inheritance, but the GC. Since the only connection to the models was from TableView's model property - which is just a wrapper for C++ getter and setter - ruby thought it lost the reference to my models, and GC'd them.
Solved by keeping the models in ruby variables.

Resources