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 }}
Related
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.
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 have two tables in my SQLite database. On table called movies and a table called trailers. The movie table has a few columns,
create_table "movies", force: :cascade do |t|
t.string "title"
t.string "release_date"
t.string "image"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "movie_id"
t.string "imdb_rating"
end
I want to add trailers to my movies. I've tried that by adding a column called trailers into my movies table, and then store multiple results in 1 column. But that didn't look like the right way to go.
So I've created the trailers table.
create_table "trailers", force: :cascade do |t|
t.string "movie_id"
t.string "link"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
My idea was to save every trailer link with the movie_id value so I maybe I could merge the two tables in 1 JSON file that I could use in my Angular app.
To retrieve the trailer data I've created a service,
service.trailer = function(youtube_link){
return httpPromise(
baseUrl + youtube_link + '/videos?api_key=a8f7039633f2065942***a28d7cadad4&callback=JSON_CALLBACK'
)
};
The data returned from that service looks like this,
{"id":157336,"results":[
{"id":"53db3c790e0a26189a000d09","iso_639_1":"en","key":"ePbKGoIGAXY","name":"Trailer 3","site":"YouTube","size":1080,"type":"Trailer"},
{"id":"550df44b9251413554004d43","iso_639_1":"en","key":"KlyknsTJk0w","name":"Own it today","site":"YouTube","size":720,"type":"Trailer"},
{"id":"533ec6fcc3a3685448009ccc","iso_639_1":"en","key":"nyc6RJEEe0U","name":"Teaser","site":"YouTube","size":720,"type":"Trailer"},
{"id":"5376ab510e0a26141c0005a8","iso_639_1":"en","key":"zSWdZVtXT7E","name":"Trailer","site":"YouTube","size":720,"type":"Trailer"},
{"id":"545da247c3a3685362005187","iso_639_1":"en","key":"Lm8p5rlrSkY","name":"Trailer 2","site":"YouTube","size":1080,"type":"Trailer"}
]}
So now I'm trying to save the data into the trailer table.
var mappedData = dataYoutube.results.map(function(r) {
var obj = {}
obj["movie_id"] = dataYoutube.id;
obj["link"] = r.key
return obj;
});
console.log(mappedData);
createTrailer.create({
movie_id: mappedData.movie_id,
link: mappedData.key
})
And the createTrailer function in my service,
app.factory('createTrailer', ['$http', function($http){
return{
create: function(trailer){
return $http.post('/trailers.json', trailer);
}
};
}])
On the Rails backend I've created the routes,
resources :trailers, only: [:create, :destroy, :index, :show]
A trailers_controller.rb
class TrailersController < ApplicationController
def index
respond_with Trailer.all
end
def create
end
end
And a trailer_model.rb
class Trailer < ActiveRecord::Base
belongs_to :movie
end
Currently when I do the save action I get an error in my rails console,
Started POST "/trailers.json" for 127.0.0.1 at 2015-12-04 16:04:25 +0100
Processing by TrailersController#create as JSON
Parameters: {"movie_id"=>[{"movie_id"=>210577, "link"=>"Ym3LB0lOJ0o"}], "link"=>[{"movie_id"=>210577, "link"=>"Ym3LB0lOJ0o"}], "trailer"=>{"movie_id"=>[{"movie_id"=>210577, "link"=>"Ym3LB0lOJ0o"}], "link"=>[{"movie_id"=>210577, "link"=>"Ym3LB0lOJ0o"}]}}
User Load (10.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
Completed 500 Internal Server Error in 15ms (ActiveRecord: 17.1ms)
ActionView::MissingTemplate (Missing template trailers/create, application/create with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :haml, :jbuilder]}. Searched in:
* "/home/alucardu/sites/movieseat/app/views"
* "/home/alucardu/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/devise-3.5.2/app/views"
):
Based on your data above, you can make that an array of objects, each object having the movie_id and link column:
var mappedData = data.results.map(function(r) {
var obj = {}
obj["movie_id"] = data.id;
obj["link"] = r.key
return obj;
});
Fiddle: http://jsfiddle.net/muat8brp/
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 got this query and would like to order the results by 'delivery_time':
d = Date.today.at_midnight
orders = car.orders.where("finished_at IS NOT NULL AND delivery_time > ?", d).order(delivery_time: :desc)
The problem is, the order method doesn't work - it still gets ordered by id! Whatever I put into the order method arguments doesn't seem to work.
This is what my orders table partially looks like:
create_table "orders", force: true do |t|
t.datetime "delivery_time"
t.datetime "finished_at"
t.datetime "created_at"
t.datetime "updated_at"
end
car.rb:
has_many :orders, -> { order "delivery_time ASC" }
.explain output:
SELECT `orders`.* FROM `orders` WHERE `orders`.`car_id` = 1 AND (finished_at IS NOT NULL AND delivery_time > '2014-09-09 03:00:00') ORDER BY delivery_time ASC, `orders`.`delivery_time` DESC
In order to override ordering, try using reorder instead of order.
orders = car.orders.where(
"finished_at IS NOT NULL AND delivery_time > ?", d
).reorder(delivery_time: :desc)