ActiveModelSerializers :camel_lower configuration not working - ruby-on-rails

I want to leverage AMS to create json data to pass as GraphQL variables in my test suite. Apparently, it support :camel_lower which would convert hash keys like :some_field to :someField but I can't seem to get it to work. Here's the relevant code:
/config/initializers/active_model_serializers.rb:
ActiveModelSerializers.config.key_transform = :camel_lower
/app/serializers/service_serializer.rb:
class ServiceSerializer < ApplicationSerializer
attributes :id, :name, :time_increment
end
rails console:
ActiveModelSerializers.config.key_transform
=> :camel_lower
s = Service.new(name: 'Test', time_increment: 10)
=> #<Service id: nil, name: "Test", time_increment: 10, created_at: nil, updated_at: nil>
ss = ServiceSerializer.new(s)
=> #<ServiceSerializer:0x00007f3771dd9dc0 #object=#<Service id: nil, name: "Test", time_increment: 10, created_at: nil, updated_at: nil>, #instance_options={}, #root=nil, #scope=nil>
ss.as_json
=> {:id=>nil, :name=>"Test", :time_increment=>10}
The result I was expecting was:
=> {:id=>nil, :name=>"Test", :timeIncrement=>10}

ActiveModelSerializers has been in some sort of maintainance state for a long time and doesn't seem to be receiving any updates.
My personal choice has been either the blueprinter gem or jsonapi-serializers. The blueprinter one is closer to AMS.
It is very easy to work with
# Gemfile
gem 'blueprinter'
and the usual
bundle install
Creating a serializer is very straightforward
# e.g. app/blueprinter/service_blueprint.rb
class ServiceBlueprint < Blueprinter::Base
# identifier :id
fields :id, :name, :time_increment
end
Add a class LowerCamelTransformer
# e.g. app/blueprinter/lower_camel_transformer.rb
class LowerCamelTransformer < Blueprinter::Transformer
def transform(hash, _object, _options)
hash.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
end
end
And in config/initializers/blueprinter.rb
Blueprinter.configure do |config|
config.default_transformers = [LowerCamelTransformer]
end
Test it
s = Service.find(1)
puts ServiceBlueprint.render(s)
# Will give you a nice output with lower camel case

Use
ActiveModelSerializers.config.adapter = :json
it worked for me

Related

Rails: List Required Attributes For Create

I am manually creating objects in the rails console using Model.new(<attributes here>). Is there an easy way to list out which attributes a model will require me to include in order for the .save call to succeed?
I am running rails 4.2.3
You can get an array of validators using Model.validators. You'll have to parse this in some way to extract those validations for presence, something like:
presence_validated_attributes = Model.validators.map do |validator|
validator.attributes if validator.is_a?(ActiveRecord::Validations::PresenceValidator)
end.compact.flatten
I found a simpler way to accomplish the same thing:
When you do a failed create you can check the error message on the object.
# app/models/price.rb
class Price < ActiveRecord::Base
validates_presence_of :value
end
# in console
p = Price.new()
=> #<Price id: nil, created_at: nil, updated_at: nil, value: nil>
p.save
=> false
p.errors.messages
=> {:value=>["can't be blank"]}
In case you the mandatory attributes with error messages
book = Book.new
book.valid?
book.errors.messages
In case you just want the name of attributes without an error message
book = Book.new
book.valid?
book.errors.messages.keys

Building associations in a before-filter in rails4

I am migrating an app from rails3.2.13 to rails4.0.0-rc1. I am having the following code:
class Foo < ActiveRecord::Base
has_many :bars
before_create :build_bars
private
def build_bars
self.bars.build({name: 'Bar 1'})
self.bars.build({name: 'Bar 2'})
end
end
The code above worked in rails3, but creates empty records in rails4. Some try & error in the console revealed that, indeed, attributes are not assigned.
f = Foo.new
f.bars.build({name: 'Bar'})
=> #<Bar id: nil, name: nil>
What's the proper way to build associations and have them being saved together with its parent record?
i think that #Mischa is right. i've been migrating my app over to rails4 and it works:
user.authorizations.build provider: "bla"
=> #<Authorization id: nil, provider: "bla", uid: nil, user_id: 1, created_at: nil, updated_at: nil>
you can have a look at the changes i did: https://github.com/phoet/on_ruby/pull/83/files#L23L59
most probably it's removing:
# Mass assignment settings
config.active_record.whitelist_attributes = true

virtual field in a model part of the model but not in the DB

I have a standard model with a few fields that are saved to a DB, and I need 1 field that doesn't have to be saved.
I tried attr_accessor but that doesn't cover it. Using Attr_accessor I can set and get the field, but it is not part of the model. If I add the models to an array and then see what is in the virtual field is not part of it. I also tried to add the field :headerfield to attr_accessible but that didn't change anything.
How can I get a field that is part of the model but not saved to the database?
The model
class Mapping < ActiveRecord::Base
attr_accessible :internalfield, :sourcefield
attr_accessor :headerfield
end
console output:
1.9.3-p194 :001 > m = Mapping.new
=> #<Mapping id: nil, internalfield: nil, sourcefield: nil, created_at: nil, updated_at: nil, data_set_id: nil>
1.9.3-p194 :002 > m.headerfield = "asef"
=> "asef"
1.9.3-p194 :003 > m
=> #<Mapping id: nil, internalfield: nil, sourcefield: nil, created_at: nil, updated_at: nil, data_set_id: nil>
Because ActiveRecord::Base has custom implementations for the standard serializiation methods (including to_s and as_json), you will never see your model attributes that do not have backing database columns unless you intervene in some way.
You can render it to JSON using the following:
render json: my_object, methods: [:virtual_attr1, :virtual_attr2]
Or you can use the as_json serializer directly:
my_object.as_json(methods: [:virtual_attr1, :virtual_attr2])
The return you see in the console is nothing else but the value of to_s. For this case, code should be better than natural language, take a look in the following code and see if you understand
class A
end
=> nil
A.new
=> #<A:0xb73d1528>
A.new.to_s
=> "#<A:0xb73d1528>"
class A
def to_s
"foobar"
end
end
=> nil
A.new
=> ble
A.new.to_s
=> "ble"
You can see this output because ActiveRecord::Base defines a method to_s that take into account only the attributes that are defined in the database, not the attr_accessor methods, maybe using the attributes call.

What is the best practice to get the same behavior from multiple AR objects that are referred to polymorphically?

This is a Rails 3 application.
I have images that can be tied to either a Product or a Brand. A product has an identifier and a Brand has a name.
The polymorphic relationship from Image is called "linkable".
If I want to list the items that a particular image is linked to, I want to avoid doing a conditional in the view like this:
<% for image in Image.all %>
<% if image.linkable.class.name=="Product" %>
<%= image.linkable.identifier %>
<% elsif image.linkable.class.name=="Brand" %>
<%= image.linkable.name %>
<% end %>
<% end %>
Of course I could just put a method inside Brand called "identifier" and use it to call "name". But that's not extensible if i want to add more objects that an image can be linked to. 8 years ago in Java programming I could mandate that a class implemented an Interface, but I don't know if I can do anything like that in Ruby. I appreciate any guidance anybody can offer.
While I like the answer you accepted, let me rewrite your example to be a bit more readable using more idiomatic ruby:
<% Image.each do |image| %>
<%= case image.linkable.class.name
when "Product"
image.linkable.identifier
when "Brand"
image.linkable.name
end %>
<% end %>
You could also easily extract that case statement into a helper function, which might be a good in-between solution if you don't want to create the module and extend it.
in a helper file:
def link_name(image)
case image.linkable.class.name
when "Product"
image.linkable.identifier
when "Brand"
image.linkable.name
end
end
and then your views become:
<% Image.each do |image| %>
<%= link_name(image) %>
<% end %>
You could create a module called Linkable and create the behavior methods in that. Then you extend the module in the classes where you want to add those behaviors. This way you don't have to worry about inheriting from anything you can just mix-in the behavior.
This is the standard Ruby way of adding common functionality to multiple classes without inheriting. You would also, by convention, name your module using a verb based adjective instead of a verb; Linkable vs. Link.
For instance:
module Linkable
def link
puts "Look, I'm linked!"
end
end
class Product < ActiveRecord
extend Linkable
end
class Brand < ActiveRecord
extend Linkable
end
Of course your classes and the module will have actual functionality.
I did it with plain sql
Image.find_by_sql("SELECT * from images INNER JOIN products on (images.linkable_id = products.id AND images.linkable_type = "product");")
Adding the method inside Brand (or Product) is a good way. Since this identifier method represents the contract to the object that an image can be linked to. You can unify it for all, say image_identifier and add this method to all the classes that image links to.
Of course, adding the method to Brand does not only mean defining it inside the class. It can be (rather should) done through a module that is extended by the linkables.
Here is how I tried it:
class Brand < ActiveRecord::Base
extend Linkable
linkable 'identifier'
end
class Product < ActiveRecord::Base
extend Linkable
linkable 'name'
end
class Image < ActiveRecord::Base
belongs_to :linkable, :polymorphic => true
end
module Linkable
def linkable(identifier_name = 'name')
has_many :images, :as => :linkable
instance_eval do
define_method :link_identifier do
send identifier_name.to_sym
end
end
end
end
>> Product
=> Product(id: integer, name: string, created_at: datetime, updated_at: datetime)
>> Brand
=> Brand(id: integer, identifier: string, created_at: datetime, updated_at: datetime)
>> Brand.create :identifier => 'Foo'
=> #<Brand id: 1, identifier: "Foo", created_at: "2010-12-08 16:00:11", updated_at: "2010-12-08 16:00:11">
>> Product.create :name => 'Bar'
=> #<Product id: 1, name: "Bar", created_at: "2010-12-08 16:00:23", updated_at: "2010-12-08 16:00:23">
>> i = Image.new
=> #<Image id: nil, linkable_type: nil, linkable_id: nil, title: nil, created_at: nil, updated_at: nil>
>> i.linkable = Product.first
=> #<Product id: 1, name: "Bar", created_at: "2010-12-08 16:00:23", updated_at: "2010-12-08 16:00:23">
>> i.save
>> i = Image.new
=> #<Image id: nil, linkable_type: nil, linkable_id: nil, title: nil, created_at: nil, updated_at: nil>
>> i.linkable = Brand.first
=> #<Brand id: 1, identifier: "Foo", created_at: "2010-12-08 16:00:11", updated_at: "2010-12-08 16:00:11">
>> i.save
=> true
>> Image.first.link_identifier
=> "Bar"
>> Image.last.link_identifier
=> "Foo"

Issue with setter override on ActiveRecord

This is not exactly a question, it's rather a report on how I solved an issue with write_attribute when the attribute is an object, on Rails' Active Record. I hope this can be useful to others facing the same problem.
Let me explain with an example. Suppose you have two classes, Book and Author:
class Book < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :books
end
Very simple. But, for whatever reason, you need to override the author= method on Book. As I'm new to Rails, I've followed the Sam Ruby's suggestion on Agile Web Development with Rails: use attribute_writer private method. So, my first try was:
class Book < ActiveRecord::Base
belongs_to :author
def author=(author)
author = Author.find_or_initialize_by_name(author) if author.is_a? String
self.write_attribute(:author, author)
end
end
Unfortunately, this does not work. That's what I get from console:
>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author = "Lewis Carroll"
=> "Lewis Carroll"
>> book
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author
=> nil
It seems that Rails does not recognize it is an object and makes nothing: after the attribuition, author is still nil! Of course, I could try write_attribute(:author_id, author.id), but it does not help when the author is not saved yet (it still has no id!) and I need the objects be saved together (author must be saved only if book is valid).
After search a lot for a solution (and try many other things in vain), I found this message: http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/4fe057494c6e23e8, so finally I could had some working code:
class Book < ActiveRecord::Base
belongs_to :author
def author_with_lookup=(author)
author = Author.find_or_initialize_by_name(author) if author.is_a? String
self.author_without_lookup = author
end
alias_method_chain :author=, :lookup
end
This time, the console was nice to me:
>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author = "Lewis Carroll"=> "Lewis Carroll"
>> book
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author
=> #<Author id: nil, name: "Lewis Carroll", created_at: nil, updated_at: nil>
The trick here is the alias_method_chain, that creates an interceptor (in this case author_with_lookup) and an alternative name to the old setter (author_without_lookup). I confess it took some time to understand this arrangement and I'd be glad if someone care to explain it in detail, but what surprised me was the lack of information about this kind of problem. I have to google a lot to find just one post, that by the title seemed initially unrelated to the problem. I'm new to Rails, so what do you think guys: is this a bad practice?
I recommend creating a virtual attribute instead of overriding the author= method.
class Book < ActiveRecord::Base
belongs_to :author
def author_name=(author_name)
self.author = Author.find_or_initialize_by_name(author_name)
end
def author_name
author.name if author
end
end
Then you could do cool things like apply it to a form field.
<%= f.text_field :author_name %>
Would this work for your situation?
When you override the accessor, you have to set an actual DB attribute for write_attribute and self[:the_attribute]=, and not the name of the association-generated attribute you're overriding. This works for me.
require 'rubygems'
require 'active_record'
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
ActiveRecord::Schema.define do
create_table(:books) {|t| t.string :title }
create_table(:authors) {|t| t.string :name }
end
class Book < ActiveRecord::Base
belongs_to :author
def author=(author_name)
found_author = Author.find_by_name(author_name)
if found_author
self[:author_id] = found_author.id
else
build_author(:name => author_name)
end
end
end
class Author < ActiveRecord::Base
end
Author.create!(:name => "John Doe")
Author.create!(:name => "Tolkien")
b1 = Book.new(:author => "John Doe")
p b1.author
# => #<Author id: 1, name: "John Doe">
b2 = Book.new(:author => "Noone")
p b2.author
# => #<Author id: nil, name: "Noone">
b2.save
p b2.author
# => #<Author id: 3, name: "Noone">
I strongly recommend doing what Ryan Bates suggests, though; create a new author_name attribute and leave the association generated methods as they are. Less fuzz, less confusion.
I solved this problem using alias_method
class Book < ActiveRecord::Base
belongs_to :author
alias_method :set_author, :author=
def author=(author)
author = Author.find_or_initialize_by_name(author) if author.is_a? String
set_author(author)
end
end

Resources