Conditional statement for a Serializer Rails 4 - ruby-on-rails

This is a really simple question, but I'm having trouble with the syntax.
Rails 4, Ruby 2.
Have the following code working to serialize some records and dump them into geo json:
module Serializers
class GeoUser < ActiveModel::Serializer
attributes :type, :geometry, :properties
def type
'Feature'
end
def geometry
{
type: 'Point',
coordinates: [object.longitude, object.latitude]
}
end
def properties
{
name: object.full_name,
address: object.full_address,
:'marker-color' => object.marker_color,
:'marker-symbol' => object.marker_symbol,
:'marker-size' => object.marker_size
}
end
end
end
My problem is sometimes object.longitude, object.latitude are null/nil and I would like to set them to 0. Simple conditional statement, something like || 0, but I can't seem to get the syntax correct.
Suggestions, please :)
Thank you!

object.longitude || 0 will work, as will object.longitude.to_f (nil.to_f returns 0.0).

Related

ActiveAdmin Filter on postgres Array field

I added the following filter in ActiveAdmin.
filter :roles, as: :select, collection Model::ROLES, multiple: true
but when i choose the filter value to search the roles. it gives me following error
PG::InvalidTextRepresentation: ERROR: malformed array literal: "teacher"LINE 1: ...ted" = $1 AND roles" IN('teacher
DETAIL: Array value must start with "{" or dimension information. ^
Any idea ? How we can search/Filter ARRAY field using AA filters? I'm using Rails 4.2.4,
ruby 2.2.2p95
I came up to a solution slightly different (and inspired by) this one over here: https://stackoverflow.com/a/45728004/1170086
Mine involves some changes (and prevent breaking contains operator in other cases). So, you're going to basically create two initializer files:
This one is for Arel, in order to support #> operator (array's contain operator in PG) for a given table column.
# config/initializers/arel.rb
module Arel
class Nodes::ContainsArray < Arel::Nodes::Binary
def operator
:"#>"
end
end
class Visitors::PostgreSQL
private
def visit_Arel_Nodes_ContainsArray(o, collector)
infix_value o, collector, ' #> '
end
end
module Predications
def contains(other)
Nodes::ContainsArray.new self, Nodes.build_quoted(other, self)
end
end
end
The other file aims to create a new Ransack predicate but I also decided to support the :array type (that's not natively supported in Ransack in terms of predicates).
# config/initializers/ransack.rb
module Ransack
module Nodes
class Value < Node
alias_method :original_cast, :cast
def cast(type)
return Array(value) if type == :array
original_cast(type)
end
end
end
end
Ransack.configure do |config|
config.add_predicate 'contains_array',
arel_predicate: 'contains',
formatter: proc { |v| "{#{v.join(',')}}" },
validator: proc { |v| v.present? },
type: :array
end
And in other to use it. All you need to do is:
User.ransack(roles_contains_array: %i[admin manager])
Or as a filter in ActiveAdmin (which is my case):
ActiveAdmin.register User do
# ...
filter :roles_contains_array, as: :select, collection: User.roles_for_select
# ...
end
I hope it works for you as it worked for me. ;)
You can set up a custom ransacker method to first collect the ids you want returned using a regular postgres search, and then return the results based on those ids:
class User < ApplicationRecord
ransacker :roles,
formatter: proc { |str|
data = where("? = ANY (roles)", str).map(&:id)
data.present? ? data : nil
} do |parent|
parent.table[:id]
end
end
If your filter is a select drop-down, then this should work fine. If you have a free-form text box, then make sure to use the "in" predicate:
filter :roles_in, as: :string
leandroico solutions works well.
But if you add the predicate with this formatter
formatter: proc { |v| "{#{v.join(', ')}}" }, (note the space after the comma)
Then you could use the multiple: true keyword in the filter input and filter by more than one value:
filter :roles_contains_array, as: :select, multiple: true, collection: User.roles_for_select
I used the answer from #leandroico to come up with the below wiki-type approach to doing this.
How to Create Custom SQL Searches for ActiveAdmin (using Arel and Ransack)
In ActiveAdmin, filters are declared in app/admin/model.rb like:
ActiveAdmin.register Model do
filter 'column_name', label: 'column_name', as: :string
end
That will make a searchbox available on the front-end with options to choose between
contains
equals
starts with
ends with
You can even do something like...
filter 'column_name_contains', label: 'column_name', as: :string
...to only have a contains type search available on the front-end.
You can also (after defining some custom methods elsewhere) specify other, non-built-in search methods, like:
filter 'column_name_custom_contains', label: 'column_name', as: :string
The rest of this doc will be about how to define this custom search method, custom_contains
Within config/initializers/arel.rb, define the following:
module Arel
# this example of custom_contains will cast the SQL column as ::text and then do a wildcard-wrapped ILIKE
class Nodes::CustomContains < Arel::Nodes::Binary
def operator
'::text ILIKE'.to_sym
end
end
class Visitors::PostgreSQL
private
def visit_Arel_Nodes_CustomContains(o, collector)
infix_value o, collector, '::text ILIKE '
end
end
module Predications
def custom_contains(column_value)
column_value = self.relation.engine.column_types[self.name.to_s].type_cast_for_database(column_value)
column_value = "%#{self.relation.engine.send(:sanitize_sql_like, column_value)}%" # wrap escaped value with % wildcard
column_value = Nodes.build_quoted(column_value, self)
Nodes::CustomContains.new(self, column_value)
end
end
end
module ActiveRecord::QueryMethods
def custom_contains(predicates)
return none if predicates.length == 0
predicates.map{ |column_name, column_value|
column_value = table.engine.column_types[column_name.to_s].type_cast_for_database(column_value)
column_value = "%#{table.engine.send(:sanitize_sql_like, column_value)}%" # wrap escaped value with % wildcard
column_value = Arel::Nodes.build_quoted(column_value)
where Arel::Nodes::CustomContains.new(table[column_name], column_value)
}.inject(:merge)
end
end
module ActiveRecord::Querying
delegate :custom_contains, :to => :all
end
Within config/initializers/ransack.rb, define the following:
Ransack.configure do |config|
config.add_predicate(
'custom_contains',
arel_predicate: 'custom_contains',
formatter: proc { |v| v.to_s },
validator: proc { |v| v.present? },
type: :string
)
end
The above has accomplished a couple of things:
1) You can use the custom_contains method that was delegate'd to all ActiveRecord models:
puts Model.custom_contains(column_name: 'search for me').to_sql
2) You can use Ransack to search against the Arel predicates that were defined:
puts Model.ransack(column_name_custom_contains: 'search for me').result.to_sql
However, in order to do the below in ActiveAdmin...
filter 'column_name_custom_contains', label: 'column_name', as: :string
...we must add a scope to Model so that there is a method, column_name_custom_contains, on Model
scope_name = "#{column_name}_custom_contains".to_sym
unless Model.methods.include?(scope_name)
Model.scope(
scope_name,
->(value) {
Model.custom_contains({column_name.to_sym => value})
}
)
end
Voila!

Rails 4 hstore defining datatypes

So I know that hstore only stores in string either key and especially value. I'm looking for a way to set their datatypes. Is there a way in rails or hstore to do this?
So far what I did was to override the getters and depending on the datatype I want. This is what I have so far:
class ModelWithHstore < ActiveRecord::Base
store_accessor :properties, :some_boolean_field, :some_integer_field, :some_datetime_field
validate :validate_range
def some_boolean_field
return if self[:properties].nil? || self[:properties][__method__.to_s].nil?
if [true, 'true', '1'].include? self[:properties][__method__.to_s]
return true
elsif [false, 'false', '0'].include? self[:properties][__method__.to_s]
return false
end
self[:properties][__method__.to_s]
end
def some_integer_field
return if self[:properties].nil? || self[:properties][__method__.to_s].nil?
self[:properties][__method__.to_s].to_i
end
def some_datetime_field
return if self[:properties].nil? || self[:properties][__method__.to_s].nil?
DateTime.strptime(self[:properties][__method__.to_s].to_s, '%F %T')
end
private
def validate_range
errors.add(:some_integer_field, "value out of range") if !some_integer_field.between?(10, 90)
end
end
And since they are getters. I think they are being used too in validators and some other places. But I really am not sure if something like this already exists or is there a better way to implement this.
Cheers!
You can try hstore_accessor gem

Methods not generated for Opinions with Mongoid

I'm trying to build an application on RoR that uses MongoDB via Mongoid for the main objects but has a like and dislike process using Redis via Opinions https://github.com/leehambley/opinions/.
It sort of works but when I run the methods on my objects I just get an error "undefined method `like_by'" where I think the methods are supposed to be autogenerated.
My model looks like:
class Punchline
include Mongoid::Document
include Opinions::Pollable
opinions :like, :dislike
field :key, type: String
field :text, type: String
field :won, type: Boolean
field :created, type: Time, default: ->{ Time.now }
field :score, type: Integer
index({ key: 1 }, { unique: true, name: "key_index" })
belongs_to :user
embedded_in :joke
end
and I run:
user = User.find(session[:userid])
#joke.punchlines.sample.like_by(user);
But it fails with the undefined method error :(
Do I need to initialize Opinions somewhere beyond
/config/initializers/opinions.rb
Opinions.backend = Opinions::RedisBackend.new
Redis.new(:host => 'localhost', :port => 6379)
So, it turns out that Opinions doesn't really work. Why is a bit beyond my two weeks with Rails :)
Anyway, it turns out that this is really easy to do by hand anyway especially as I only had a like and dislike to handle.
I used a Redis sorted set which allows a unique key - value pair with a score. I used a score of +1 or -1 to denote like or dislike and then encoded the key to represent the liked object and the value to be the user id.
This looked like:
def like(user)
$redis.zadd('joke:'+self.key+':likes', 1, user._id)
end
def dislike(user)
$redis.zadd('joke:'+self.key+':likes', -1, user._id)
end
def numLikes
res = $redis.zrangebyscore('joke:'+self.key+':likes',1,1);
return res.count
end
def numDislikes
res = $redis.zrangebyscore('joke:'+self.key+':likes',-1,-1);
return res.count
end
def likedBy(user)
res = $redis.zscore('joke:'+self.key+':likes',user._id)
return (res == 1)
end
def dislikedBy(user)
res = $redis.zscore('joke:'+self.key+':likes',user._id)
return (res == -1)
end

Validation Hash fields using mongoid

I am working on mongoDB with Rails. So using gem mongoid, Anyone know how to validate Hash fields in model?
We have to write custom validation methods
Here explained how we are writing custom validation methods
Looking for a solution, I came to a custom validator that appears good to me and it can be used generically.
private
def fix_content(input_hash, valid_fields)
temphash = {}
input_hash.each do |k,v|
k=k.to_sym
if valid_fields.has_key? k
case valid_fields[k]
when 'integer'
v=v.to_i
when 'boolean'
v=(v=='true' || v==true)
when 'float'
v=v.to_f
when 'array'
v = "#{v.class}"=="Array" ? v : []
else
v=v.to_s
end
temphash[k]=v
end
end
temphash
end
Let's suppose we have this field:
field :fieldname, type: Hash, default: {hfield1: 0, hfield2: [], hfield3: false}
Actually, it's not a validator, it's a callback. It works like this:
before_save :fieldname_fix_content
Under private:
def fieldname_fix_content
# we show the callback what fields will be processed. All others will be disposed of
self.fieldname = fix_content(self.fieldname, {:hfield1=> 'integer', :hfield2=>'array', :hfield3=>'boolean'})
end

How to get the maximum length configured in an ActiveRecord validation?

Given a model:
class Person
validates_lenght_of :name, :maximum => 50
end
I have some view code that shows a countdown and enforces this maximum. However I hard coded the number 50 into that view code. Is there a way to extract this number from the model?
Something like:
Person.maximum_length_of_name
I tried this:
Person.validators_on(:name)
=> [#<ActiveRecord::Validations::UniquenessValidator:0x000001067a9840 #attributes=[:name], #options={:case_sensitive=>true}, #klass=Trigger(id: integer, name: string, created_at: datetime, updated_at: datetime, user_id: integer, slug: string, last_update_by: integer)>, #<ActiveModel::Validations::PresenceValidator:0x000001067a6c30 #attributes=[:name], #options={}>, #<ActiveModel::Validations::LengthValidator:0x000001067a3f08 #attributes=[:name], #options={:tokenizer=>#<Proc:0x00000101343f08#/Users/sjors/.rvm/gems/ruby-1.9.2-p0/gems/activemodel-3.0.6/lib/active_model/validations/length.rb:9 (lambda)>, :maximum=>50}>]
The information is in there, but I don't know how to extract it:
Use validators_on method
irb(main):028:0> p Person.validators_on(:name)[0].options[:maximum]
50
=> 50
As #Max Williams mentioned it works only on Rails 3
The problem with #nash answer is that validators do not own a certain order. I figured out how to do the same thing with just some more code but in some kind of safer mode ('cause you can add more validators later and break the order you get it):
(Person.validators_on(:name).select { |v| v.class == ActiveModel::Validations::LengthValidator }).first.options[:maximum]
I think it does only work for Rails 3 too.
[Edit 2017-01-17] Carefull my answer is old (2012) and was for Rails 3. It may not work / be ideal for newer Rails versions.
Just to bring a little more DRY spirit, you could create a generic class method to get maximum "length_validator" value on any attribute, like so:
Create a module in your lib directory and make it extend ActiveSupport::Concern:
module ActiveRecord::CustomMethods
extend ActiveSupport::Concern
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecord::CustomMethods)
Add the "module ClassMethods" in it and create the "get_maximum" method:
module ActiveRecord::CustomMethods
extend ActiveSupport::Concern
module ClassMethods
def get_maximum(attribute)
validators_on(attribute).select{|v| v.class == ActiveModel::Validations::LengthValidator}.first.options[:maximum]
end
end
end
EDIT 1: Configuration
You'll also have to add a require in one of your initializers.
For instance, here are my configurations:
I've put my file in lib/modules/active_record/extensions.
I've added this in my autoload_paths: config.autoload_paths +=
%W(#{config.root}/lib/modules) Note: this is not required, but best practice if you want to put there some of your custom classes and modules that you share between your apps.
In one of my initializers (config/initializers/custom.rb) I've added this line: require "active_record/extensions"
And that should do it! Restart your server and then...
END EDIT 1
And then you should be able to do something like this:
<%= form_for #comment do |f| %>
<%= f.text_area(:body, :maxlength => f.object.class.get_maximum(:body)) #Or just use Comment.get_maximum(:body) %>
<% end %>
I hope it will help others! :) Of course you can customize the method the way you want and add options and do fancy stuff. ;-)
More concise:
Person.validators_on(:name).detect { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }.options[:maximum]
Uses detect{} instead of select{}.first and is_a? instead of class ==.
That works with Rails 4.1 as well.
Even more dynamic than Kulgar's answer would be to use something like this:
Person.validators_on(attribute)
.select { |v| v.class == ActiveRecord::Validations::LengthValidator }
.select { |v| v.options[:maximum].present? }.first.options[:maximum]
That way you can order the validators inside the model the way you want.
Use it as a Rails helper
You then could use this code to write a helper:
# Returns the maximum length for a given attribute of a model
def get_maxlength(model, attribute)
model.validators_on(attribute)
.select { |v| v.class == ActiveRecord::Validations::LengthValidator }
.select { |v| v.options[:maximum].present? }.first.options[:maximum]
end
And utilize the helper like this:
= f.text_field :name, maxlength: get_maxlength(f.object.class, :name) # Or use get_maxlength(Person, :name)
This is an indirect answer, but is an alternative solution, just in case it may prove useful to anyone.
Alternative Solution 1
class Person
MAX_LENGTHS = {
name: 50,
# email: 30, etc...
}.freeze
validates :name, length: { maximum: MAX_LENGTHS.fetch(:name) }
end
# usage example in view file
<%= Person.MAX_LENGTHS.fetch(:name) %>
Alternative Solution 2
... or if you prefer one-liners, or to not use a Hash constant
class Person
validates :name, length: { maximum: (MAX_NAME_LENGTH = 50) }
# validates :password, length: {
# minimum: (MIN_PASSWORD_LENGTH = 8),
# maximum: (MAX_PASSWORD_LENGTH = 70)
# }
# ... etc
end
# usage example in view file
<%= Person.MAX_NAME_LENGTH %>
If you're in rails 2, it's not easy. I remember trying this before and not getting far. You can get at the validation objects but they don't list which field they are for which seemed very odd to me. People have done plugins for this (ie to inspect or reflect on an AR object's validations), such as https://github.com/redinger/validation_reflection
Kulgar's answer is good and possibly ideal. Here is an easier way to do this for Rails 4 that does not require modifying any configuration, with the disadvantage that you have to add an include line to every model that wants to use it.
models/concerns/introspection.rb:
module Introspection
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def max_length(property)
Resource.validators_on(property.to_sym).
select{ |v| v.kind_of?(ActiveModel::Validations::LengthValidator) }.
first.options[:maximum]
end
end
end
Then in your model:
class Comment < ActiveRecord::Base
include Introspection
..
end
Then you can do something like this:
<%= form_for #comment do |f| %>
<%= f.text_area(:body, :maxlength => f.object.class.max_length(:body)) %> # Or just use Comment.max_length(:body) %>
<% end %>

Resources