How do I cause ActiveModelSerializers to serialize with :attributes and respect my key_transform? - json

I have a very simple model that I wish to serialize in a Rails (5) API. I want to produce the resulting JSON keys as CamelCase (because that's what my client expects). Because I expect the model to increase in complexity in future, I figured I should use ActiveModelSerializers. Because the consumer of the API expects a trivial JSON object, I want to use the :attributes adapter. But, I cannot seem to get AMS to respect my setting of :key_transform, regardless of whether I set ActiveModelSerializers.config.key_transform = :camel in my configuration file or create the resource via s = ActiveModelSerializers::SerializableResource.new(t, {key_transform: :camel}) (where t represents the ActiveModel object to be serialized) in the controller. In either case, I call render json: s.as_json.
Is this a configuration problem? Am I incorrectly expecting the default :attributes adapter to respect the setting of :key_transform (this seems unlikely, based on my reading of the code in the class, but I'm often wrong)? Cruft in my code? Something else?
If additional information would be helpful, please ask, and I'll edit my question.
Controller(s):
class ApplicationController < ActionController::API
before_action :force_json
private
def force_json
request.format = :json
end
end
require 'active_support'
require 'active_support/core_ext/hash/keys'
class AvailableTrucksController < ApplicationController
def show
t = AvailableTruck.find_by(truck_reference_id: params[:id])
s = ActiveModelSerializers::SerializableResource.new(t, {key_transform: :camel})
render json: s.as_json
end
end
config/application.rb
require_relative 'boot'
require 'rails/all'
Bundler.require(*Rails.groups)
module AvailableTrucks
class Application < Rails::Application
config.api_only = true
ActiveModelSerializers.config.key_transform = :camel
# ActiveModelSerializers.config.adapter = :json_api
# ActiveModelSerializers.config.jsonapi_include_toplevel_object = false
end
end
class AvailableTruckSerializer < ActiveModel::Serializer
attributes :truck_reference_id, :dot_number, :trailer_type, :trailer_length, :destination_states,
:empty_date_time, :notes, :destination_anywhere, :destination_zones
end

FWIW, I ended up taking an end-around to an answer. From previous attempts to resolve this problem, I knew that I could get the correct answer if I had a single instance of my model to return. What the work with ActiveModel::Serialization was intended to resolve was how to achieve that result with both the #index and #get methods of the controller.
Since I had this previous result, I instead extended it to solve my problem. Previously, I knew that the correct response would be generated if I did:
def show
t = AvailableTruck.find_by(truck_reference_id: params[:id])
render json: t.as_json.deep_transform_keys(&:camelize) unless t.nil?
end
What had frustrated me was that the naive extension of that to the array returned by AvailableTruck.all was failing in that the keys were left with snake_case.
It turned out that the "correct" (if unsatisfying) answer was:
def index
trucks = []
AvailableTruck.all.inject(trucks) do |a,t|
a << t.as_json.deep_transform_keys(&:camelize)
end
render json: trucks
end

Related

Use ERB to access images NOT asset pipeline

I want to display an dynamically chosen image, thus within the html I call upon the variable #background_img, which contains the url to a specific picture. However, doing
<body style='background-image: url(<%=#background_img%>);'>
simply refuses to display the image for the background. Am I misinterpreting how ERB works, because wouldn't Rails simply precompile the CSS and end up with a working HTML image fetch? Using the Chrome Developer Tools when previewing my app reveals url(), and obviously an empty parameter can't fetch the image.
EDIT:
Just wanted to add that I would rather not have to download the images, but keep the urls I already have prepared.
This is the WeatherMan class:
require 'rest-client'
class WeatherMan
#images within accessible data structures, designed to be expandable
def initialize
#hot = ['https://farm2.staticflickr.com/1515/23959664094_9c59962bb0_b.jpg']
#rain = ['https://farm8.staticflickr.com/7062/6845995798_37c20b1b55_h.jpg']
end
def getWeather(cityID)
response = JSON.parse RestClient.get "http://api.openweathermap.org/data/2.5/weather?id=#{cityID}&APPID=bd43836512d5650838d83c93c4412774&units=Imperial"
return {
temp: response['main']['temp'].to_f.round,
cloudiness: response['clouds']['all'].to_f.round,
humidity: response['main']['humidity'].to_f.round,
windiness: response['wind']['speed'],
condition_id: response['weather'][0]['id'].to_f,
condition_name: response['weather'][0]['main'],
condition_description: response['weather'][0]['description'],
condition_img: response['weather'][0]['icon']
}
end
def getImg(temp)
if temp <= 100 #CHANGE!!!
return #rain[rand(#rain.length)]
elsif temp <= 32
return nil
elsif temp <= 50
return nil
elsif temp <= 75
return nil
elsif temp <= 105
return nil
end
end
end
So sorry about the formatting, on mobile right now.
Now, the controller class:
load File.expand_path("../../data_reader.rb", __FILE__)
load File.expand_path("../../weatherstation.rb", __FILE__)
class PagesController < ApplicationController
def home
# `sudo python /home/pi/Documents/coding/raspberryPI/weatherStation/app/led_blink.py`
server = WeatherMan.new
#outside_data = server.getWeather(4219934)
#sensor_temp = DRead.read_data(File.expand_path('../../data.txt', __FILE__), 'temperature')
#sensor_temp = (#sensor_temp.to_f * (9.0/5) + 32).round(2)
#background_img = server.getImg(#outside_data[:temp])
end
end
The problem seems to be that #background_img is not populated.
The reason for this seems to be your Weatherman class. I will attempt to rectify the issue...
Controller
If you're calling #background_img on your body tag, it means it's accessible at every controller action. Thus, instead of declaring it in a solitary home action, you need to make it available each time you load your views:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_background
private
def set_background
server = WeatherMan.new
#outside_data = server.getWeather(4219934)
#sensor_temp = DRead.read_data(File.expand_path('../../data.txt', __FILE__), 'temperature')
#sensor_temp = (#sensor_temp.to_f * (9.0/5) + 32).round(2)
#background_img = server.getImg(#outside_data[:temp])
end
end
--
Class
The main issue I see is that your class is not giving you a value. I'll attempt to refactor your class, although I can't promise anything:
require 'rest-client'
class WeatherMan
##static = {
hot: 'https://farm2.staticflickr.com/1515/23959664094_9c59962bb0_b.jpg',
rain: 'https://farm8.staticflickr.com/7062/6845995798_37c20b1b55_h.jpg'
}
def getWeather(cityID)
response = JSON.parse RestClient.get weather_url(cityID)
return {
temp: response['main']['temp'].to_f.round,
cloudiness: response['clouds']['all'].to_f.round,
humidity: response['main']['humidity'].to_f.round,
windiness: response['wind']['speed'],
condition_id: response['weather'][0]['id'].to_f,
condition_name: response['weather'][0]['main'],
condition_description: response['weather'][0]['description'],
condition_img: response['weather'][0]['icon']
}
end
def getImg(temp)
#### This should return the image ####
#### Below is a test ####
##static[:hot]
end
private
def weather_url city
"http://api.openweathermap.org/data/2.5/weather?id=#{city}&APPID=bd43836512d5650838d83c93c4412774&units=Imperial"
end
end
--
View
You need to make sure you're getting returned data from your controller in order to populate it in your view.
Because your getImg method is returning nil, you're getting a nil response. I have amended this for now with one of the flickr links you have included in the class.
If you always have a returned image, the following should work:
#app/views/layouts/application.html.erb
<body style='background-image: url(<%= #background_img %>);'>
Because your #background_img is an external URL, the above should work. If you were using a file from your asset_pipeline, you'd want to use image_url etc

If not passed a variable, use DB default (Ruby on Rails)

I have a function that is inserting a record into my DB (MySQL). It has many columns, many of which have default values in the DB. Passing in values for these variables is therefore optional.
def assign_X_to_Y( options = {} )
. . .
#bar.var1 = options[:foo]
. . .
end
I would like to do the following:
-If a variable exists (ex: options[:foo]), add it to the record I'm making.
#bar.var1 = options[:foo]
-If it doesn't, I don't want to add it--I want to use the DB default.
I know I can simply do an if:
if options[:foo]
#bar.var1 = options[:foo]
end
But I have a lot of these variables and so I think there must be a nicer way that having loads of if-statements. Something like the "if doesn't exist set to null" expression:
#bar.var1 = options[:foo] || nil
Is there anything like what I am saying? I can't use the above expression because I don't want to set it to null (which I think it would do), I want to use the default value…
Thanks in advance!
If #bar is an model you can simply pass a hash:
Bar.create(hash) # creates a Bar with the defaults from your schema
#bar.assign_attributes(hash)
#bar.update(hash) # same as object but commits the changes to the db
If bar is a Plain Old Ruby class you can give it the same functionality by:
class Bar
attr_accessor :foo
attr_accessor :baz
attr_accessor :woggle
def initialize(hash)
assign_values(hash)
end
def assign_attributes(hash)
assign_values(hash)
end
private
def assign_values(hash)
hash.each do |k,v|
send "#{k}=", v
end
end
end
Then I can simply create an object with:
Bar.new(foo: 1, baz: 3)
Note that this will respect object encapsulation - if I try to do:
Bar.new(haxxored: true)
It will raise a NoMethodError. Just like #bar.haxxored = true.
If I'm understanding your question correctly, the best way to handle this would be to use the public_send method in Ruby:
def set_new_property(obj, prop_name, prop_value)
obj.class.__send__(attr_accessor: "#{prop_name}")
obj.public_send("#{prop_name}=", prop_value)
end
Bear in mind that you'll have to set explicit attribute accessors for each potential property being assigned.

Is there a way to make a DataMapper Enum type be zero based?

Using DataMapper Enum type for the first time, and I noticed the first value in the enum translates to a 1. I need this to be zero based to be backward compatiable with another ORM layer in a different application also reading this database.
You should be able to monkey-patch enum.rb in dm-types to support this. You will need to replace the initialize method with a slightly modified copy where #flag_map[i+1] is replaced with #flag_map[i]:
module DataMapper
class Property
class Enum < Object
def initialize(model, name, options = {})
#flag_map = {}
flags = options.fetch(:flags, self.class.flags)
flags.each_with_index do |flag, i|
#flag_map[i] = flag
end
if self.class.accepted_options.include?(:set) && !options.include?(:set)
options[:set] = #flag_map.values_at(*#flag_map.keys.sort)
end
super
end # end initialize
end # end class Enum
end # end class Property
end # end module DataMapper

How can I store a hash for the lifetime of a 'jekyll build'?

I am coding a custom Liquid tag as Jekyll plugin for which I need to preserve some values until the next invocation of the tag within the current run of the jekyll build command.
Is there some global location/namespace that I could use to store and retrieve values (preferably key-value pairs / a hash)?
You could add a module with class variables for storing the persistent values, then include the module in your tag class. You would need the proper accessors depending on the type of the variables and the assignments you might want to make. Here's a trivial example implementing a simple counter that keeps track of the number of times the tag was called in DataToKeep::my_val:
module DataToKeep
##my_val = 0
def my_val
##my_val
end
def my_val= val
##my_val = val
end
end
module Jekyll
class TagWithKeptData < Liquid::Tag
include DataToKeep
def render(context)
self.my_val = self.my_val + 1
return "<p>Times called: #{self.my_val}</p>"
end
end
end
Liquid::Template.register_tag('counter', Jekyll::TagWithKeptData)

Rails: Force empty string to NULL in the database

Is there an easy way (i.e. a configuration) to force ActiveRecord to save empty strings as NULL in the DB (if the column allows)?
The reason for this is that if you have a NULLable string column in the DB without a default value, new records that do not set this value will contain NULL, whereas new records that set this value to the empty string will not be NULL, leading to inconsistencies in the database that I'd like to avoid.
Right now I'm doing stuff like this in my models:
before_save :set_nil
def set_nil
[:foo, :bar].each do |att|
self[att] = nil if self[att].blank?
end
end
which works but isn't very efficient or DRY. I could factor this out into a method and mix it into ActiveRecord, but before I go down that route, I'd like to know if there's a way to do this already.
Yes, the only option at the moment is to use a callback.
before_save :normalize_blank_values
def normalize_blank_values
attributes.each do |column, value|
self[column].present? || self[column] = nil
end
end
You can convert the code into a mixin to easily include it in several models.
module NormalizeBlankValues
extend ActiveSupport::Concern
included do
before_save :normalize_blank_values
end
def normalize_blank_values
attributes.each do |column, value|
self[column].present? || self[column] = nil
end
end
end
class User
include NormalizeBlankValues
end
Or you can define it in ActiveRecord::Base to have it in all your models.
Finally, you can also include it in ActiveRecord::Base but enable it when required.
module NormalizeBlankValues
extend ActiveSupport::Concern
def normalize_blank_values
attributes.each do |column, value|
self[column].present? || self[column] = nil
end
end
module ClassMethods
def normalize_blank_values
before_save :normalize_blank_values
end
end
end
ActiveRecord::Base.send(:include, NormalizeBlankValues)
class User
end
class Post
normalize_blank_values
# ...
end
Try if this gem works:
https://github.com/rubiety/nilify_blanks
Provides a framework for saving incoming blank values as nil in the database in instances where you'd rather use DB NULL than simply a blank string...
In Rails when saving a model from a form and values are not provided by the user, an empty string is recorded to the database instead of a NULL as many would prefer (mixing blanks and NULLs can become confusing). This plugin allows you to specify a list of attributes (or exceptions from all the attributes) that will be converted to nil if they are blank before a model is saved.
Only attributes responding to blank? with a value of true will be converted to nil. Therefore, this does not work with integer fields with the value of 0, for example...
Another option is to provide custom setters, instead of handling this in a hook. E.g.:
def foo=(val)
super(val == "" ? nil : val)
end
My suggestion:
# app/models/contact_message.rb
class ContactMessage < ActiveRecord::Base
include CommonValidations
include Shared::Normalizer
end
# app/models/concerns/shared/normalizer.rb
module Shared::Normalizer
extend ActiveSupport::Concern
included do
before_save :nilify_blanks
end
def nilify_blanks
attributes.each do |column, value|
# ugly but work
# self[column] = nil if !self[column].present? && self[column] != false
# best way
#
self[column] = nil if self[column].kind_of? String and self[column].empty?
end
end
end
Sorry for necroposting, but I didn't find exact thing here in answers, if you need solution to specify fields which should be nilified:
module EnforceNil
extend ActiveSupport::Concern
module ClassMethods
def enforce_nil(*args)
self.class_eval do
define_method(:enforce_nil) do
args.each do |argument|
field=self.send(argument)
self.send("#{argument}=", nil) if field.blank?
end
end
before_save :enforce_nil
end
end
end
end
ActiveRecord::Base.send(:include, EnforceNil)
This way:
class User
enforce_nil :phone #,:is_hobbit, etc
end
Enforcing certain field is handy when let's say you have field1 and field2. Field1 has unique index in SQL, but can be blank, so you need enforcement(NULL considered unique, "" - not by SQL), but for field2 you don't actually care and you have already dozens of callbacks or methods, which work when field2 is "", but will dig your app under the layer of errors if field2 is nil. Situation I faced with.
May be useful for someone.
Strip Attributes Gem
There's a handy gem that does this automatically when saving a record, whether that's in a user form or in the console or in a rake task, etc.
It's called strip_attributes and is extremely easy to use, with sane defaults right out of the box.
It does two main things by default that should almost always be done:
Strip leading and trailing white space:
" My Value " #=> "My Value"
Turn empty Strings into NULL:
"" #=> NULL
" " #=> NULL
Install
You can add it to your gem file with:
gem strip_attributes
Usage
Add it to any (or all) models that you want to strip leading/trailing whitespace from and turn empty strings into NULL:
class DrunkPokerPlayer < ActiveRecord::Base
strip_attributes
end
Advanced Usage
There are additional options that you can pass on a per-Model basis to handle exceptions, like if you want to retain leading/trailing white space or not, etc.
You can view all of the options on the GitHub repository here:
https://github.com/rmm5t/strip_attributes#examples
I use the attribute normalizer gem to normalize attributes before they into the db.