Why can't I save an object with a serialized attribute? - ruby-on-rails

I have...
/app/models/search.rb:
serialize :result
def multisearch
self.result = PgSearch.multisearch(self.term)
self.status = "closed"
self.save
return result
end
/db/schema.rb:
create_table "searches", :force => true do |t|
t.string "term"
t.string "status"
t.text "result"
end
I get the following error when I try `self.save?
ArgumentError: wrong number of arguments (2 for 1)
from /Users/steven/.rvm/gems/ruby-1.9.2-p320/gems/arel-3.0.2/lib/arel/expressions.rb:3:in `count'
I get a similar error when I test result.serialize:
ArgumentError: wrong number of arguments (0 for 1)
from /Users/steven/.rvm/gems/ruby-1.9.2-p320/gems/activerecord-3.2.11/lib/active_record/attribute_methods/serialization.rb:49:in `serialize'
How can I fix this?

Answer was to convert to an array before serialization: self.result = PgSearch.multisearch(self.term).to_a

Related

Getting undefined method `sorted_by' when trying to use filterrific gem on a rails app

I'm trying to use the filterrific gem on a rails app to filter cities by a price lower than $1000 for example. - https://github.com/jhund/filterrific
but can't seem to set it up, i've added the code to the model and controllers but I get undefined method `sorted_by' for #<City::ActiveRecord_Relation:0x00007fc191173040> Did you mean? sort_by
Model -
class City < ApplicationRecord
has_one :guide, dependent: :destroy
filterrific(
default_filter_params: { sorted_by: 'created_at_desc' },
available_filters: %i[
sorted_by
search_query
with_created_at_gte
]
)
end
Controller -
class CitiesController < ApplicationController
def index
#cities = City.all
(#filterrific = initialize_filterrific(
City,
params[:filterrific]
)) || return
#cities = #filterrific.find.page(params[:page])
respond_to do |format|
format.html
format.js
end
end
Schema -
create_table "cities", force: :cascade do |t|
t.string "name"
t.string "internet"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "weather"
t.string "image"
t.string "country"
t.string "price"
end
It looks like you have copied and pasted the example from the documentation without really understanding what you are trying to do.
The error message is coming from your default_filter_params here:
filterrific(
default_filter_params: { sorted_by: 'created_at_desc' }, <<<
...
)
For this to work you need a sorted_by scope which takes a parameter 'created_at_desc'. There are examples in the documentation here: http://filterrific.clearcove.ca/pages/active_record_scope_patterns.html
An example for the sorted_by scope would be:
scope :sorted_by, (lambda do |sort_option|
direction = (sort_option =~ /desc$/) ? 'desc' : 'asc'
case sort_option.to_s
when /^created_at_/
order("cities.created_at #{ direction }")
when /^name_/
order("cities.name #{ direction }")
else
raise(ArgumentError, "Invalid sort option: #{ sort_option.inspect }")
end
end)
to filter by price you will also need a scope like so:
scope :with_price_lte, (lambda do |price|
where('price >= ?', price)
end)
so your model filterrific clause should look like:
filterrific(
default_filter_params: { sorted_by: 'created_at_desc' },
available_filters: %i[
sorted_by
with_price_lte
]
)
There's more to it as you have to have a filterrific form in your view which returns the parameters for your scopes and an index.js.erb view which updates your list of cities, but this should help you get a little further.

How to set attributes with different names than a DB schema

I am a newbie Ruby developer. I cannot figure out how to create an ActiveRecord model with different attributes names than defined in a DB schema
Consider the following schema
create_table "sync_tasks", force: :cascade do |t|
t.string "name"
t.string "path"
t.string "task_type"
t.string "status"
t.boolean "async", default: false
t.boolean "direct_download", default: true
t.datetime "created_at", null: false
t.datetime "completed_at"
t.datetime "updated_at", null: false
end
And I have the following payload
{
"name" : "Sync /var/www/",
"path" : "/var/www",
"directDownload": true,
"async" : false,
"taskType" : "directory"
}
And trying to create my model like that
class SyncTask < ApplicationRecord
TYPE_DB='db'
TYPE_FILE='file'
TYPE_DIRECTORY='directory'
def initialize(params)
# super
#task_type = params[:taskType]
#direct_download = params[:directDownload]
#path = params[:path]
#status = params[:status]
#async = params[:async]
end
end
When I try to save it throws an error
<NoMethodError: undefined method `[]' for nil:NilClass>
Also I am not able to access field like that
new_task = SyncTask.new(allowed_task_params)
new_task.task_type
It throws the following error
#<NoMethodError: undefined method `task_type' for #<SyncTask not initialized>>
In case I uncomment the super call it gives another error
#<ActiveModel::UnknownAttributeError: unknown attribute 'taskType' for SyncTask.>
What I am doing wrong ? How can I use different attributes names and initialize the model by myself ?
Thanks
You can transform the keys , for example:
=> payload = { "name": "Sync /var/www/", "path": "/var/www", "directDownload": true, "taskType": "directory" }
=> h = payload.transform_keys { |key| key.to_s.underscore } # only since v4.0.2
=> h = Hash[payload.map { |(k, v)| [k.to_s.underscore, v] }] # before v.4.0.2
#> {"name"=>"Sync /var/www/", "path"=>"/var/www", "direct_download"=>true, "task_type"=>"directory"}
=> new_task = SyncTask.new(h)
You shouldn't use the initialize method on AR models. If you still need to use initialize, use after_initialize hook. Because with the initialize we have to declare the super, so it is best to use the callback.

How to group by individual elements in arrays

I have collections of shows with their genres attached, so that Show.first.genres returns ['horror','scifi',etc].
My goal is to calculate a users mean score by unique genres. The problem is that if I do a Show.group(:genres), I get results by the whole sets of genres:
['horror','scifi']=>[list of entries]
['horror','gore']=>[list of entries]
I would rather get a count of all elements with horror in the genres, all elements with scifi, etc. Any ideas?
Here's some relevant schema information:
create_table "animes", force: :cascade do |t|
end
create_table "animes_genres", id: false, force: :cascade do |t|
t.integer "anime_id", null: false
t.integer "genre_id", null: false
end
create_table "genres", force: :cascade do |t|
t.string "name"
end
create_table "library_entries", force: :cascade do |t|
end
These are all linked back and forth and I can generally access any relationships that exist via ActiveRecord.
Or in a more Railsish way, you should probably start from Genre and do something like:
Genre.all.map{|g| [g, g.shows] }.to_h
If the ActiveRecord Association goes both directions, then you should be able to look at the problem from the Genre Model's perspective.
Genre.find('space opera').shows
I am not sure if this is what you are looking for, but if that Show.group(:genres) returns a Hash of [array of genres] => [array of entries], you can transform that into a Hash of genre => [array of entries], by doing this:
by_genres = Show.group(:genres)
by_genre = {}
by_genre.default = []
by_genres.each{|ks,vs| ks.each{|k| by_genre[k] += vs }}
Or if you only want the count:
by_genres = Show.group(:genres)
count_genre = {}
count_genre.default = 0
by_genres.each{|ks,vs| ks.each{|k| count_genre[k] += vs.size }}

ActiveModel::MissingAttributeError (can't write unknown attribute 'Russian Federation'):

Getting a very strange error when running reduce on an activemodel relation. It seems like calling "c.name" in my reduce code causes the error. c.name returns the string "Russian Federation". Am I using reduce incorrectly?
Here is the error:
[2014-03-17T21:12:40.174655 #9240] FATAL -- :
ActiveModel::MissingAttributeError (can't write unknown attribute `Russian Federation'):
app/controllers/registrations_controller.rb:96:in `block in pluck_countries'
app/controllers/registrations_controller.rb:96:in `pluck_countries'
Larger stacktrace from the console:
#third_party_countries.reduce(#hsh) {|hsh, c| hsh[c.name] = ThirdPartyShipping.first }
ActiveModel::MissingAttributeError: can't write unknown attribute `Russian Federation'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/attribute_methods/write.rb:47:in `write_attribute'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/attribute_methods/dirty.rb:70:in `write_attribute'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/attribute_methods.rb:341:in `[]='
from (irb):3:in `block in irb_binding'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/relation/delegation.rb:63:in `each'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/relation/delegation.rb:63:in `reduce'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/relation/delegation.rb:63:in `method_missing'
from (irb):3
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/railties-4.0.2/lib/rails/commands/console.rb:90:in `start'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/railties-4.0.2/lib/rails/commands/console.rb:9:in `start'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/railties-4.0.2/lib/rails/commands.rb:62:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'
Here is the code:
#third_party_countries = Country.third_party_countries
#hsh = HashWithIndifferentAccess.new
#third_party_countries.reduce(#hsh) {|hsh, c| hsh[c.name] = c.third_party_shipping }
Country schema:
create_table "countries", force: true do |t|
t.string "name"
t.float "shipping_rate"
t.integer "third_party_shipping_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "product_id"
end
Country model:
class Country < ActiveRecord::Base
belongs_to :third_party_shipping
has_and_belongs_to_many :products
has_many :addresses
validates_presence_of :name
validates_uniqueness_of :name
before_create :init
after_initialize :init
scope :shippable, -> { where(third_party_shipping_id: nil) }
scope :third_party_countries, -> { where.not(third_party_shipping_id: nil) }
def shipping_price
self.shipping_rate * 100
end
def free_shipping
self.shipping_rate <= 0 and self.third_party_shipping_id.nil?
end
def paid_shipping
!self.free_shipping
end
def shipping_display
if self.free_shipping
"free"
elsif self.paid_shipping
self.shipping_rate
end
end
private
def init
if self.shipping_rate.blank?
self.shipping_rate = 0
end
end
end
Since you're declaring #hsh outside the loop, you have no need of the extra complexity introduced by inject/reduce. Just use each:
#hsh = HashWithIndifferentAccess.new
#third_party_countries.each{ |c| #hsh[c.name] = c.third_party_shipping }
The problem with your existing code is that, with inject/reduce, the return value of the block is passed as the first argument to the next invocation. You need to return hsh from your block, otherwise the value of c.third_party_shipping is passed as hsh to the next invocation, and you're effectively doing c.third_party_shipping[c.name].
You could make it work by returning hsh...
#third_party_countries.reduce(#hsh) do |hsh, c|
hsh[c.name] = c.third_party_shipping
hsh
end
But you shouldn't. You don't need this functionality. each_with_object is the correct method to use:
#hsh = #third_party_countries.each_with_object(HashWithIndifferentAccess.new) do |hsh, c|
hsh[c.name] = c.third_party_shipping
end
You could also just map the collection to an array, and initialize your hash with that array:
#hsh = HashWithIndifferentAccess[#third_party_countries.map { |c| [c.name, c.third_party_shipping] }]
This relies on the ability to freely convert between arrays and hashes. The array [[a, b], [c, d]] converts to the hash { a => b, c => d }.

Ruby on Rails, find by two conditions

I am trying to find all values of a given model by a combination of two fields.
I have the following model:
create_table "users", :force => true do |t|
t.string "name"
t.string "url"
t.integer "clicks_given"
t.integer "clicks_received"
t.datetime "created_at"
t.datetime "updated_at"
end
and I have defined this method for the model:
def credits
credits = self.clicks_given - self.clicks_received
end
I am trying to find all users above a given credits:
#users = User.find(:all, :conditions => { "credits >= ?", -5 })
and:
#users = User.find(:all, :conditions => { "clicks_given - clicks_received >= ?", -5 })
Fail. What should is the correct statement to to find the right users?
Pass the condition as an array instead of a hash:
#users = User.find(:all, :conditions => [ "clicks_given - clicks_received >= ?", -5 ])
The first version will not work since "credits" is not a database column.
EDIT:
Or in Rails 3 syntax, with your order:
#users = User.where("clicks_given - clicks_received >= ?", 5).order("clicks_given - clicks_received")
Why don't directly use -5 in condition like this:
#users = User.find(:all,
:conditions =>"clicks_given-clicks_received>=-5")
and if you have a variable in place of -5 then you can write something like this:
#users = User.find(:all,
:conditions => ["clicks_given-clicks_received >= ?",-5])

Resources