I'm making an App in Rails to show anime, these animes has and belongs to many languages, so I made a HABTM association:
class Anime < ActiveRecord::Base
has_and_belongs_to_many :languages
end
class Language < ActiveRecord::Base
has_and_belongs_to_many :animes
end
Now I don't know how can I make associations between them, I've created many Languages' records to use them, for example, Language with ID 1 is English, Language with ID 2 is Spanish, etc... And I want to just make the associations between an anime and a language, ie, if I want to say that the Anime with ID 1 it's available in Spanish only, then in the table animes_languages I want to create the record with values anime_id: 1 and language_id: 2 and nothing more, but I belive that if I execute the command Anime.find(1).languages.create it will not use an already existing language, it will create a new language, but the only thing I want is to make associations between already existing animes with already existing languages, so, How can I do this? Should I make a model for the table animes_language?
It's confusing for me cause when I created that table as specified here enter link description here, I created the table without ID, it only have the fields anime_id and language_id.
Just to be safe I will back it up.
First you migrate your tables to remove already existing association to one or the other reference (i.e. if language already have many animes, etc).
Then you need to create a migration to create the associative table.
rails g migration CreateJoinTableAnimeLanguage anime language
Then the association pointers in your models should work properly.
class Anime < ActiveRecord::Base
has_and_belongs_to_many :languages
end
class Language < ActiveRecord::Base
has_and_belongs_to_many :animes
end
At which point whenever you want to associate one to the other already existing:
Anime.find(1).languages << Language.find(1)
Experience would recommend against trying to do this in seperate steps.
I'd say find what gets created the most, I'd guess Anime, then find a way to choose or create a language using:
class AnimeController < ApplicationController
def create
#anime = Anime.new(anime_params)
#success = #anime.save
end
private
def anime_params
params.require(:anime).permit(:stuff, :languages => [:id, :or_stuff])
end
end
Should be as simple as
anime = Anime.find(1)
language = Language.find(1)
anime.languages << language
And that will create the join record in between the two
Related
I was recently working on a project where I faced a dilemma of choosing between two ways of getting same results. Here is the class structure:
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
end
An author has first name, last name. I want to get the full name of the author for a given book as an instance method.
In simple active record terms, since book is associated with author, we can get the author name for a book as follows:
For example in Book class, we have:
class Book < ApplicationRecord
belongs_to :author
def author_name
"#{author.first_name} #{author.last_name}"
end
end
And we get the result!
But, according to the target of minimizing dependencies (POODR Book), future ease of change and better object oriented design, the book should not know properties of an author. It should interact with an author object by interfaces.
So Book should not be the one responsible for getting the Author name. The author class should.
class Book < ApplicationRecord
belongs_to :author
def author_name
get_author_name(self.author_id)
end
private
#minimizing class dependecies by providing private methods as external interfaces
def get_author_name(author_id)
Author.get_author_name_from_id(author_id)
end
end
class Author < ApplicationRecord
has_many :books
#class methods which provides a gate-way for other classes to communicate through interfaces, thus reducing coupling.
def self.get_author_name_from_id(id)
author = self.find_by_id(id)
author == nil ? "Author Record Not Found" : "#{author.first_name.titleize} #{author.last_name.titleize}"
end
end
Now, book is just interacting with the public interface provided by Author and Author is handling the responsibility of getting full name from its properties which is a better design for sure.
I tried running the queries as two separate methods in my console:
class Book < ApplicationRecord
def author_name
get_author_name(self.author_id)
end
def author_name2
"#{author.last_name} + #{author.first_name}"
end
end
The results are shown below:
Looks like both run the same queries.
My questions are
Does rails convert author.last_name called inside the Book class to
the same SQL query as Author.find_by_id(author_id).last_name called inside
Author class (through message passing from Book class) in case of bigger data size?
Which one is more performant in case of bigger data size?
Doesn't calling author.last_name from Book class violates design
principles ?
It's actually much more common and simplier to use delegation.
class Book < ApplicationRecord
belongs_to :author
delegate :name, to: :author, prefix: true, allow_nil: true
end
class Author < ApplicationRecord
has_many :books
def name
"#{first_name.titleize} #(last_name.titleize}"
end
end
As to performance, if you join the authors at the time of the book query you end up doing a single query.
#books = Book.joins(:author)
Now when you iterate through #books and you call individually book.author_name no SQL query needs to be made to the authors table.
1) Obviously not, it performs JOIN of books & authors tables. What you've made requires 2 queries, instead of 1 join you'll have book.find(id) and author.find(book.author_id).
2) JOIN should be faster.
3) Since last_name is a public interface, it absolutely doesn't violate design principles. It would violate principles if you were accessing author's last name from outside like that: Book.find(1).author.last_name - that's a bad thing. Correct is: Book.find(1).authors_last_name - and accessing author's name inside Model class.
Your provided example seems to be overcomplicated to me.
According to the example you shared, you only want to get full name of the book's author. So, the idea of splitting responsibility is correct, but in Author class should be simple instance method full_name, like:
class Author < ApplicationRecord
has_many :books
def full_name
"#{author.first_name.titleize} #{author.last_name.titleize}"
end
end
class Book < ActiveRecord::Base
belongs_to :author
def author_name
author.full_name
end
end
Note, there're no direct queries in this code. Once you'll need the author's name somewhere (in a view, in api response, etc), Rails will make the most optimized query possible (depends on your use case though, it may be ineffective for example, if you call iterate over books and call author in a loop)
I prefer the second approach because the full_name is property of author not a book. If the book wants to access that information, it can using book.author&.full_name (& is for handling cases of books with no authors).
but I would suggest a refactoring as below:
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
def full_name
"#{firstname} #{lastname}"
end
end
Does rails convert author.last_name called inside the Book class to the same SQL query as Author.find_by_id(author_id).last_name called inside Author class (through message passing from Book class) in case of bigger data size?
Depend upon the calling factor, like in your example both will generate the same query. But if you have a include\join clause while getting the Book/Author, both will generate different queries.
As per the rails convention, Author.find_by_id(author_id).last_name is not recommended as it will always fire a query on database whenever the method is called. One should use the rails' association interface to call the method on related object which is smart to identify the object from memory or fetch it from database if not in memory.
Which one is more performant in case of bigger data size?
author.last_name is better because it will take care of joins, include, and memoization clauses if used and avoid the N+1 query problem.
Doesn't calling author.last_name from Book class violates design principles?
No, you can even use delegate like #Steve Suggested.
In my experience, it's a balancing act between minimizing code complexity and minimizing scalability issues.
However, in this case, I think the simplest solution that would separate class concerns and minimize code would be to simply use: #book.author.full_name
And in your Author.rb define full_name in Author.rb:
def full_name
"#{self.first_name} #{self.last_name}"
end
This will simplify your code a lot. For example, if in the future you had another model called Magazine that has an Author, you don't have to go define author_name in the Magazine model as well. You simply use #magazine.author.full_name. This will DRY up your code nicely.
I have tables already created from a different project. Their names are formatted like aaa_bbb_ccc_ddd (all non plural and some parts aren't a convention word). I have successfully created a schema from the database by reading this. But now I have to make the actual models. I've looked at RMRE, but they enforce the ActiveRecord convention on my tables and change their names, which I don't want to do because other apps depend on those tables.
What is the best way to automatically create models and a schema from existing tables?
just a theory, not sure how this would work in real app:
create models named as ActiveRecord convention requires, for example for table aaa_bbb_ccc_ddd you'll create a model AaaBbb and map this model to your table:
class AaaBbb < ActiveRecord::Base
self.table_name = "aaa_bbb_ccc_ddd"
end
or a more human example:
class AdminUser < ActiveRecord::Base
self.table_name = "my_wonderfull_admin_users"
end
Now you'll have AaaBbb as resource in routes meaning you'll have a url like:
.../aaa_bbb/...
and if you want to use the table name name in url I guess you could rewrite the route:
get 'aaa_bbb_ccc_ddd/:id', "aaa_bbb#show", as: "aaa_bbb"
again, just a theory that might help you out. I haven't worked with such cases yet but would've start from this.
edit
to automate model creation from database:
https://github.com/bosko/rmre
but I think this will create models by rails convention with wierd names that you'll have to use as resource in your app.
A good template that I found on SO in case you want to use a model name different from table name:
class YourIdealModelName < ActiveRecord::Base
self.table_name = 'actual_table_name'
self.primary_key = 'ID'
belongs_to :other_ideal_model,
:foreign_key => 'foreign_key_on_other_table'
has_many :some_other_ideal_models,
:foreign_key => 'foreign_key_on_this_table',
:primary_key => 'primary_key_on_other_table'
end
Just switch from Rails to Django and make
your life happier and also make your work normal:
$ python manage.py inspectdb my_table_without_existing_model > some_new_model.py
That's enough. Two seconds of work :)
New to Rails and Ruby and trying to do things correctly.
Here are my models. Everything works fine, but I want to do things the "right" way so to speak.
I have an import process that takes a CSV and tries to either create a new record or update an existing one.
So the process is 1.) parse csv row 2.) find or create record 3.) save record
I have this working perfectly, but the code seems like it could be improved. If ParcelType wasn't involved it would be fine, since I'm creating/retrieving a parcel FROM the Manufacturer, that foreign key is pre-populated for me. But the ParcelType isn't. Anyway to have both Type and Manufacturer pre-populated since I'm using them both in the search?
CSV row can have multiple manufacturers per row (results in 2 almost identical rows, just with diff mfr_id) so that's what the .each is about
manufacturer_id.split(";").each do |mfr_string|
mfr = Manufacturer.find_by_name(mfr_string)
# If it's a mfr we don't care about, don't put it in the db
next if mfr.nil?
# Unique parcel is defined by it's manufacturer, it's type, it's model number, and it's reference_number
parcel = mfr.parcels.of_type('FR').find_or_initialize_by_model_number_and_reference_number(attributes[:model_number], attributes[:reference_number])
parcel.assign_attributes(attributes)
# this line in particular is a bummer. if it finds a parcel and I'm updating, this line is superfulous, only necessary when it's a new parcel
parcel.parcel_type = ParcelType.find_by_code('FR')
parcel.save!
end
class Parcel < ActiveRecord::Base
belongs_to :parcel_type
belongs_to :manufacturer
def self.of_type(type)
joins(:parcel_type).where(:parcel_types => {:code => type.upcase}).readonly(false) unless type.nil?
end
end
class Manufacturer < ActiveRecord::Base
has_many :parcels
end
class ParcelType < ActiveRecord::Base
has_many :parcels
end
It sounds like the new_record? method is what you're looking for.
new_record?() public
Returns true if this object hasn’t been saved yet — that is, a record
for the object doesn’t exist yet; otherwise, returns false.
The following will only execute if the parcel object is indeed a new record:
parcel.parcel_type = ParcelType.find_by_code('FR') if parcel.new_record?
What about 'find_or_create'?
I have wanted to use this from a long time, check these links.
Usage:
http://rubyquicktips.com/post/344181578/find-or-create-an-object-in-one-command
Several attributes:
Rails find_or_create by more than one attribute?
Extra:
How can I pass multiple attributes to find_or_create_by in Rails 3?
You have a set of related models created through a scaffold e.g. a house, which has many rooms, which each have many windows, which each has a selection of locks.
These resources are already full of data i.e. someone has entered all the information, such as: a room called 'kitchen' has various windows associated with it and these windows each have five different locks associated with them.
Someone comes along and says:
Can you create a form that lets someone create a new project where they can select the different rooms, windows and then specify the locks that they would like for that project? (these are already in the system, nothing new to add, just the associations to a new project)
This sounds like a nested form but I have wasted a lot of time trying to solve this - there are many levels of nesting, which make this tricky. Any suggestions?
session based solution
With such deeply nested models select box on the front end wouldn't be enough...
Assuming this, you may want to create a current_house who's id live in a session (just like current_user works).
Once you have your current_house add different items by navigating to your list of items view and clicking on the add_to link :
# house_controller.rb
def add_to
current_house.polymorphic_items << Kitchen.find(params[:id])
redirect_to :back
end
But there are many approaches to this session based solution which sort of implements a cart/order system. You may want to add a current_item to add stuff in each leaf of your tree aka room of your house.
E.G after clicking on the kitchen you just added :
before_filter :set_current_item
def add_to
current_item.windows << Window.find(id)
end
current_item beeing polymorphic : a living room, a bathroom etc.
But how you implement that precisely depends on your Domain Model....
As a rule of thumb regarding nested forms I'd follow rails guidance for routes : don't go deeper than one level or you'll end up in a mess.
Yes this is a nested form. Railscasts nested forms is a great place to start.
If everything is already in the system you probably just want select boxes so they can select what they want. Also check out the .build method. If you have multiple levels of nesting you can also manually set the association by passing in the foreign key yourself.
I think you can model this with a single level of nested attributes, given the models below (based on Windows/Locks pre-existing and a room just needing to mix and match them into a set of windows with given locks):
class House < ActiveRecord::Base
has_many :rooms
end
class Room < ActiveRecord::Base
belongs_to :house
has_many :window_configs
end
class WindowConfig < ActiveRecord::Base
belongs_to :room
belongs_to :window
belongs_to :lock
end
class Lock < ActiveRecord::Base
has_many :window_configs
end
class Window < ActiveRecord::Base
has_many :window_configs
end
... based on that model setup, you could have a single house form that you dynamically add child 'room' definitions to that each have a name and a collection of window_configs which have two select boxes for each one (choose a window definition and then a lock definition). Because you're dynamically adding multiple rooms with multiple windows, you'd need some JS to populate new form elements, but it could all live in a single nested form.
form_for :house do |form|
# Dynamically add a Room form for each room you want with js
form.fields_for :room do |room_attributes|
room_attributes.text_field :name
# Dynamically add window_config forms on Room w/ JS
room_attributes.fields_for :window_config do |window_attributes|
window_attributes.select :window_id, Window.all
window_attributes.select :lock_id, Lock.all
Short version: Where should I store environment-specific IDs? ENV['some-variable']? Somewhere else?
Long version:
Let's say I have a model called Books and a book has a Category. (For the sake of this question, let's say a book only has one category.)
class Book < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :books
end
Now let's say one category is called 'erotica.' And I want to suppress erotica books in my type ahead. That seems straight forward. But in production and in development 'erotica' has a different ID. I don't want my code to be ID dependent. I don't want it to be string dependent (in case 'erotica' is renamed pr0n or whatever).
I think I should have something like
def suppress_method
suppress_category_id = look_up_suppression_id
...
end
but where should 'look up' look?
Thanks!
Taking this approach will be brittle, what if you want to suppress multiple categories? Erotica and Politics? The best design here is for you to actually add 'suppressed' as a boolean to category in a migration, and maintain that in your application's administration interface. After you've done that you can add a named scope like:
class Category < ActiveRecord::Base
named_scope :not_suppressed, :conditions=>{:suppressed=>false}
# or for rails 3
scope :not_suppressed, where(:suppressed=>false)
end
Then just update your type ahead code to do:
Category.not_suppressed.find ...
Rather than
Category.find