Getting Sequel associations through Sinatra - ruby

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

Related

Turn a Ruby Array built from a mongoid query into an actual mongoid object, or re write method to return a mongoid object?

I have a mongoid model called Department and a separate model called User, and there is no native relationship between the two models. Because of how the relationships in my application work, I manually store document ID's on the User model.
I am using the Grape framework for Ruby, and it has a filter system that sits on top of Mongoid objects called Entities, and it rejects anything that isnt a mongoid query response object, because this method returns a ruby Array instead of a Mongoid object, my framework gives me errors.
Is there any way to re write my function to return a Mongoid object? or is there any way I can convert an array of Mongoid Objects into one Mongoid object?
## inside Department Model
def self.user_can_access(user = nil)
if user != nil
departments = []
## department_access_keys are embedded documents belonging to a user
user.department_access_keys.each do |key|
departments << BACKBONE::Department.find(key.key)
end
departments ## => returns an array of Department Documents that a user has been granted access to
else
raise 'user was not defined'
end
end
I believe, “Mongoid Object” should be just a hash, so this should work (also note Enumerable.map instead of phpish each { << }):
## inside Department Model
def self.user_can_access(user = nil)
raise 'user was not defined' if user.nil?
{
departments: # return hash here
user.department_access_keys.map do |key|
BACKBONE::Department.find(key.key)
end
}
end
Can't you just use find like this?
departments = BACKBONE::Department.find(*user.department_access_keys.map(&:key))
I am not very familiar with mongoid but the Documentation seems to suggest that this is exactly how to achieve what you want.
Criteria#find
Find a document or multiple documents by their ids. Will raise an error by default if any of the ids do not match.
Examples:
Band.find("4baa56f1230048567300485c")
Band.find(
"4baa56f1230048567300485c",
"4baa56f1230048567300485d"
)
Band.where(name: "Photek").find(
"4baa56f1230048567300485c"
)

Is there a rails way to titleize and humanize strings before storing into database

The problem about humanize (and probably titleize too i'm not sure) is that it breaks the view if the string is nil.
and then you'll have to .try(:humanize) instead, which is a big headache if you realized that you made that mistake way too late and you have to recheck every line of code at your views.
What i'm looking for is a way i could humanize and titleize strings before storing them into the database. That way, even if i'm looking straight into my database i will see the strings humanized and titleized.
You can create a method in your model which will take the attributes of this, and will transform as you want and/or you need, you can "fire" this every time you save a record using it as a before_save callback:
For instance, having a model User which attributes name and lastname I'm using the name and using titleize on it:
class User < ApplicationRecord
before_save :titlelize_names
def titlelize_names
self.name = self.name.titleize
end
end
The params will contain the attributes as the user has typed them, but they will be saved according to what the model says:
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"...", "user"=>{"name"=>"name", "lastname"=>"lastname"}, "commit"=>"Create User"}
(0.1ms) begin transaction
SQL (0.8ms) INSERT INTO "users" ("name", "lastname", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Name"], ["lastname", "lastname"]...]
Is the only one interaction you need to do before save the record, the view is untouched and the controller just receive it and makes what model says.
If by any change it occurs some problem at the moment of saving the record you can use throw(:abort) and "to force to halt" the callback chain.
def titlelize_names
self.name = self.name.titleize
throw(:abort)
end
In addition and depending on which method you're using with the before_save callback, you can add custom errors which will be available to use within your controllers, as for instance, an error identified by the key :titleize_names could be then handled in the controller.
def titlelize_names
errors.add(:titleize_names, 'An error has ocurred with the name attribute.')
self.name = self.name.titleize
throw(:abort)
end
If humanize and titlerize return an exception if value is nil, create an if statement, and you can make this process in one service or in one function model using before_save or before_create callbacks:
value.titleize unless value.nil?
You can use model callbacks to transform the data before saving it to the DB: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html - you probably want before_save.
Also, if you want to ensure the record is never nil, add a validation as well.
Example:
class Example < ActiveRecord::Base
validates :name, presence: true
before_save :fixname
# Force titleize on :name attribute
def fixname
self.name = self.name.titleize
end
end
Now your model will have a method called fixname that you can call anywhere on your model instance as well as having it called before saving it to the DB.

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

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.

Missing ActiveRecord methods (find_by) for object

I am trying to do an assignment which requires me to create and save an ActiveRecord within my Model class, and then return it. The rspec is expecting to use the find_by method to verify this. Here's my Model:
-----------------------
class User < ActiveRecord::Base
attr_accessor :id, :username, :password_digest, :created_at, :updated_at
after_initialize :add_user
def initialize(attributes={})
#username = attributes[:username]
#password_digest = attributes[:password_digest]
super
end
def add_user
self[:username] = #username
self[:password_digest] = #password_digest
self.save
self[:id] = self.id
end
end
----------------
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object. So, rspec fails. I have looked everywhere but can't seem to find the solution. I am a noob, so sorry if the answer is obvious and I can't see it.
You say
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object
This is expected behavior. Hopefully you understand by now the difference between class and instance methods. The main important point is that query methods such as find_by are not made available to model instances. If you do something like user = User.find_by(id: params[:id]), you're calling the find_by class method on the User model.
There are a number of methods like where, order, limit, etc. that are defined in ActiveRecord::QueryMethods - these are made available to ActiveRecord::Relation object and your model class. Most of these methods will return ActiveRecord::Relation objects, which is why they're chainable, e.g.
User.where(params).order(created_at: :desc).limit(5)
However find_by is an exception - it returns a model instance so you can't continue to query on the results. In summary User.new(params) returns an instance of the model which doesn't have find_by available

Serialize a class into an activerecord field

I've tried a few different searches, but I'm not really sure how to word the question correctly. Imagine I have a db table create migration that looks like this:
def self.up
create_table :users do |t|
t.string :name, :null => false,
t.text :statistics
end
end
Example users class
class User
serialize :statistics, JSON
def statistics
self.statistics
end
end
But I want self.statistics to be an instance of class Statistics
class Statistics
#stats = {}
def set(statistic, value)
#stats[statistic] = value
end
def to_json(options)
self.to_json(:only => #stats)
end
end
(or something like that)
Ultimately, what I want to happen is that I want to be able to add Statistics-specific methods to manage the data in that field, but when I save the User object, I want it to convert that instance of the Statistics class into a JSON string, which gets saved in the DB.
I'm coming from a PHP background, where this kind of thing is pretty easy, but I'm having a hard time figuring out how to make this work with ActiveRecord (something I don't have much experience with).
Ideas?
Note: This is NOT Rails... just straight-up Ruby
Do you need it to be JSON? If not, serializing it to YAML will allow you to easily marshal the object back to its original class.
ActiveRecord::Base#serialize takes two arguments: attribute and class. If class is defined, the data must deserialize into an object of the type specified. Here's a cool example of it in action.
By default, your attribute will be serialized as YAML. Let’s say you
define MyField like this:
class MyField
end
It will be serialized like this:
--- !ruby/object:MyField {}
It will deserialize back to a MyField instance.

Resources