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.
Related
I am new to rails. I have been trying to so a follow along project. Basically trying out CRUD API.
My routes.rb
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
get '/', to: 'status#index'
jsonapi_resource :authors
end
ApplicationController.rb
class ApplicationController < ActionController::API
include JSONAPI::ActsAsResourceController
end
AuthorResource
class AuthorResource < JSONAPI::Resource
attributes :first, :last
end
Schema.rb
ActiveRecord::Schema.define(version: 2023_02_02_083747) do
create_table "authors", force: :cascade do |t|
t.string "first"
t.string "last"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
When I try to add authors via POST call, data is getting added to DB. But GET call returns a error response
POST CALL REQ/RES
{
"data":{
"type":"authors",
"attributes":{
"first":"Yuval",
"last":"Harari"
}
}
}
{
"data": {
"id": "4",
"type": "authors",
"links": {
"self": "http://localhost:3000/authors/4"
},
"attributes": {
"first": "Yuval",
"last": "Harari"
}
}
}
Response for GET call
{
"errors": [
{
"title": "Record not found",
"detail": "The record identified by could not be found.",
"code": "404",
"status": "404"
}
]
}
ERROR IN CONSOLE
ActionController::RoutingError (No route matches [GET] "/authors/4"):
Can anybody help me understand the problem please ? I am using rails v 6.1.7
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.
This question already has answers here:
Why can't show restaurant list?
(3 answers)
Closed 6 years ago.
I'm trying RoR Active Records with Association.
And trying to connect two tables, which is restaurants and restaurant_translations. These are split for multi-language support.
Here's the definition of those two tables.
create_table "restaurant_translations", id: false, force: :cascade do |t|
t.integer "id", limit: 4, default: 0, null: false
t.integer "restaurant_id", limit: 4
t.string "restaurantname", limit: 255
t.string "address", limit: 255
t.string "tel", limit: 255
t.text "description", limit: 65535
t.string "lang", limit: 255, default: "", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "restaurants", force: :cascade do |t|
t.string "restaurant_type", limit: 255
t.string "genre", limit: 255
t.string "url", limit: 255
t.string "fb", limit: 255
t.string "mailaddr", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
And the Models.
class Restaurant < ActiveRecord::Base
has_many :restaurant_translations
end
class RestaurantTranslation < ActiveRecord::Base
self.table_name = 'restaurant_translations'
belongs_to :restaurant
end
And then here's the controller which creates my headache.
class RestaurantController < ApplicationController
def list
#restaurants = Restaurant.includes(:restaurant_translations).where('restaurant_translations.lang = ?', "en").references(:restaurant_translations)
logger.debug #restaurants
end
end
View file(.slim) is like this.
h1 = t :restraunt_list_title
table
thead
tr
th = t :restraunt_list_type
th = t :restraunt_list_name
th = t :restraunt_list_url
th = t :restraunt_list_genre
th = t :restraunt_list_addr
tbody
- #restaurants.each do |restaurant|
tr
td = restaurant.restaurant_type
td = restaurant.restaurant_translations.first.restaurantname
td = link_to 'here', restaurant.url
td = restaurant.genre
td = restaurant.restaurant_translations.first.address
br
So, an error occurs 'No Method Error'. Tell me how to express association table parameters. Thanks in advance.
ps. After fixing the view as followed, the result is like this.
cf. restaurant_translation is like this.
I'm guessing you're trying to call the name method defined on the restaurant_translations, in that case you should be calling:
tr
td = restaurant.restaurant_type
td = restaurant.restaurant_translations.first.name
td = link_to 'here', restaurant.url
td = restaurant.genre
td = restaurant.restaurant_translations.first.address
However, a few corrections to your code,
You wouldn't need the restaurant_id column on restaurant, because that is already defined as id unless you want to also tie a restaurant to a restaurant_translation via a belongs_to association, in which case you'd need a restaurant_translation_id column.
I see that you're excluding the id column in restaurant_translation and yet adding it again, that seems a bit redundant, moreover if you want to take advantage of some advanced ActiveRecord features, you'd need an id column
You don't need to specify the table_name on restaurant_translation model as that is inferred by Rails
In your restaurants_controller, you're assigning #restaurants and reassigning it immediately to restaurant_translations. I don't know what you intended to do their, but I don't think that's right
Try to maintain a consistent name in your application, so that your future self can understand it. An example is the usage of restraunt_list_type, I guess you wanted to say restaurant_list_type
There could be others, but these are the ones my eyes caught immediately.
UPDATE
You should check your database to ensure that all your restaurants have at least a restaurant_translation. The error: ...for NilClass means your restaurant_translation is an empty array. If you want to fetch all restaurants that have at least a restaurant_translation, then you should be using joins vs includes in your controller, as such:
Restaurant.joins(:restaurant_translations).where(restaurant_translations: { lang: "en"}).references(:restaurant_translations)
However, if you want to fetch all restaurants, with/without restaurant_translations, then I'd say you should go with the approach of the previous response to your question, using the Object#try method:
tbody
- #restaurants.each do |restaurant|
tr
td = restaurant.restaurant_type
td = restaurant.restaurant_translations.first.try(:restaurantname)
td = link_to 'here', restaurant.url
td = restaurant.genre
td = restaurant.restaurant_translations.first.try(:address)
I would like to import an XML file from a URL using Nokogiri and save it to my PostgreSQL database.
In my schema.rb I have the following table:
create_table "centres", force: :cascade do |t|
t.string "name"
t.string "c_type"
t.text "description"
t.float "lat"
t.float "long"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Below is a sample from the file I am importing:
<facility>
<id>CG432</id>
<facility_name>Cairncry Community Centre</facility_name>
<expiration>2099-12-31T23:59:59Z</expiration>
<type>Community Centre</type>
<brief_description/>
<lat>57.1601027</lat>
<long>-2.1441739</long>
</facility>
I created the following import.rake task in lib/tasks:
require 'rake'
require 'open-uri'
require 'Nokogiri'
namespace :db do
task :xml_parser => :environment do
doc = Nokogiri::XML(open("http://sample.xml"))
doc.css('centre').each do |node|
facility_name = node.xpath("centre").text,
type = node.xpath("centre").text,
brief_description = node.xpath("centre").text,
lat = node.xpath("centre").text,
long = node.xpath("centre").text,
Centre.create(:facility_name => name, :type => c_type, :brief_description => description, :lat => lat, :long => long)
end
end
end
I tried rake db:migrate and also rake -T | grep import.
Your XML does not contain a <centre> element. Also there is no need to create a bunch of variables if you only intend to use them once.
doc.css('facility').each do |f|
centre = Centre.create do |c|
c.facility_name = node.css("facility_name").first.text
c.type = node.css("type").first.text
c.brief_description = node.css("brief_description").first.text
c.lat = node.css("lat").first.text
c.long = node.css("long").first.text
end
end
A more elegant way to do this if the selectors match up with your attributes is:
KEYS = ["facility_name", "type", "brief_description", "lat", "long"]
doc.css('facility').each do |f|
values = KEYS.map { |k| node.css(k).first.text }
Centre.create(Hash[*KEYS.zip(values).flatten])
end
An explaination on how this works can be found at: http://andywenk.github.io/programming/2014/06/27/ruby-create-a-hash-from-arrays/
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