Associations: one to many with existing table - ruby-on-rails

Rails beginner here:
I already have a database and table, so the naming convention is giving me some headaches
class Item < ActiveRecord::Base
belongs_to :categorie, :foreign_key => "catid"
end
class Categorie < ActiveRecord ...
has_many :item
end
i = Item.first # Ok
c = i.Categorie # Ok, finds proper Categorie based on "catid" of i
c.Item # fails with Categorie_id column not found ! how can i map Categorie_id to "catid"?

You're a rails beginner but you might not be a programmer beginner so I'll dive in and explain classes a little bit.
A class is simply a data object that holds methods. Nothing more. Here's a simple one that holds one method:
class Cow
def talk
"moo"
end
end
Cow is the class, talk is the method. Now, if we have the above classes in memory, we cannot do this in the console:
talk
Because that method isn't available at the global scope. This is a good thing, because this could cause bugs and is inefficient. Imagine if we have a few animals:
class Cat
def talk
"meow"
end
end
class Dog
def talk
"woof"
end
end
Running talk, how would the computer know which talk to run? Instead, we call the method that's inside the class like this:
Cow.talk #=> "moo"
Cat.talk #=> "meow"
Dog.talk #=> "woof"
Hopefully now, this code:
Item.first
is less cryptic. Item is a class, and first is a method available inside that class.
Now I know Item is a model, but in rails, models are simply classes that inherit a bunch of useful methods from ActiveRecord. At the top of the Item model you should see this:
class Item < ActiveRecord::Base
That's what pulls in all of the useful methods, such as the first method we're using. Because of this inheritance, we can imagine your Item class looks a bit like this:
class Item < ActiveRecord::Base
def first
# code is in here that queries the table in your database that has
# the downcased and pluralized name of Item (so items) and returns the first
# row of that table
end
# down here is all of your methods you've probably created. Validations and the like.
end
first, rather than return a string like in my example does something far more useful; it queries the table in your database that has the downcased and pluralized name of its class. So Item.first queries the items table, and returns the first row.
Now, I have to be honest, despite what you say, I find it highly doubtful that i.Categorie finds the proper Categorie based on the "catid" of i. If it truly does I feel you've done some crazy workaround to get that working. This is what should happen:
i.Categorie
NoMethodError: undefined method `Categorie' for #<Item:0x00000005905830>
In plain English, this means
NoMethodError: there is no 'Categorie' method inside that instance of the 'Item' class.
And this makes sense because I see no 'Categorie' method in here:
class Item < ActiveRecord::Base
def first
# code is in here that queries the table in your database that has
# the downcased and pluralized name of Item (so items) and returns the first
# row of that table
end
# down here is all of your methods you've probably created. Validations and the like.
end
Now the reason c.Item doesn't work is because c is set to nil because nil was returned by i.Categorie due to the non-method error, and nil certainly doesn't have the method Item inside it.
c = i.Categorie # c is set to nil due to noMethodError
c.Item
NoMethodError: undefined method `Item' for nil:NilClass
Hopefully you understand a bit more what's going on now. If you want your code to work you should be doing this. Look closely, there are a few nuances:
i = Item.first # i is set to the first instance of Item
c = i.categorie # c is set to the instance of Categorie that i belongs to
is = c.items # returns an array consisting of all the Item instances that belong to the Categorie instance in c
We could also do this:
is.first # returns i
So where do all these handy methods come from? The categorie method inside i (i.category), the items method inside c (c.items)? The answer is they're created dynamically by Rails based on your inheritance and pulled into the relevant model by < ActiveRecord::Base.
By "based on your inheritance" I mean, how you've used the inheritance methods, belongs_to and has_many:
class Item < ActiveRecord::Base
belongs_to :categorie, :foreign_key => "catid" # creates categorie method that returns the instance of Categorie this instance of Item belongs to
end
class Categorie < ActiveRecord ...
has_many :item # creates items method that returns an array of all the instances of Item that belong to this instance of Categorie
end
I would also point out that Categorie is a pretty terrible Model name, purely because it's spelt wrongly. Maybe Type would be better?

You can do
Item.create (:catid => #categorie.id)
#categorie = Categorie.find(params[:id]) or with Categorie.all
place the each loop & find the id .

First you should have used Category for model because rails intelligently understands the plural categories or tables.
Secondly, you should have something like this;
class Item < ActiveRecord::Base
belongs_to :Category, :foreign_key => "catid"
end
class Categorie < ActiveRecord ...
has_many :items
end
i = Item.first
c = i.Category
c.items #to find all items that belong to the category c

Related

Defining attributes at runtime based on data from related object

I'm building an application where users are part of an Organisation. An organisation has many Lists, which in turn have many ListItems.
Now, I would like for admin users to be able to specify which attributes are available on list items, based on the organisation they belong to (or rather, on the organisation their list belongs to), without having to touch any code.
So far, when defining attributes that are not bound to a specific column in the database, I have used document_serializable, a nifty little gem (based on virtus) which serializes virtual attributes to a JSONB column in the db. I like this approach, because I get all of virtus' goodies (types, coercion, validations, etc.), and because data ends up sitting in a JSONB column, meaning it can be loaded quickly, indexed, and searched through with relative ease.
I would like to keep using this approach when adding these user-defined attributes on the fly. So I'd like to do something like:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
organisation.list_attributes.each do |a, t|
attribute a, t
end
end
Where Organisation#list_attributes returns the user-defined hash of attribute names and their associated types, which, for example, might look like:
{
name: String,
age: Integer
}
As you might have guessed, this does not work, because organisation.list_attributes.each actually runs in the context of ListItem, which is an instance of Class, and Class doesn't have an #organisation method. I hope that's worded in a way that makes sense1.
I've tried using after_initialize, but at that point in the object's lifecycle, #attribute is owned by ActiveRecord::AttributeMethods::Read and not DocumentSerializable::ClassMethods, so it's an entirely different method and I can't figure out wether I can still access the one I need, and wether that would even work.
Another alternative would be to find the organisation in question in some explicit way, Organisation#find-style, but I honestly don't know where I should store the information necessary to do so.
So, my question: at the moment of instantiating (initializing or loading2) a record, is there a way I can retrieve a hash stored in a database column of one of its relations? Or am I trying to build this in a completely misguided way, and if so, how else should I go about it?
1 To clarify, if I were to use the hash directly like so:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
{
name: String,
age: Integer
}.each do |a, t|
attribute a, t
end
end
it would work, my issue is solely with getting a record's relation at this earlier point in time.
2 My understanding is that Rails runs a model's code whenever a record of that type is created or loaded from the database, meaning the virtual attributes are defined anew every time this happens, which is why I'm asking how to do this in both cases.
at the moment of instantiating (initializing or loading) a record, is
there a way I can retrieve a hash stored in a database column of one
of its relations?
Yes. This is fairly trivial as long as your relations are setup correctly / simply. Lets say we have these three models:
class ListItem < ApplicationRecord
belongs_to :list
end
class List < ApplicationRecord
belongs_to :organisation
has_many :list_items
end
class Organisation < ApplicationRecord
has_many :lists
end
We can instantiate a ListItem and then retrieve data from anyone of its parents.
#list_item = ListItem.find(5) # assume that the proper inherited
foreign_keys exist for this and
its parent
#list = #list_item.list
#hash = #list.organisation.special_hash_of_org
And if we wanted to do this at every instance of a ListItem, we can use Active Record Callbacks like this:
class ListItem < ApplicationRecord
belongs_to :list
# this is called on ListItem.new and whenever we pull from our DB
after_initialize do |list_item|
puts "You have initialized a ListItem!"
list = list_item.list
hash = list.organisation.special_hash_of_org
end
end
But after_initialize feels like a strange usage for this kind of thing. Maybe a helper method would be a better option!

Rails - Create instance of a model from within another model

I have an application I'm building where I need one model to create instances of another model. I want every Car to have 4 tires.
Car model
class Car < ActiveRecord::Base
has_many :tires
after_create :make_tires
def make_tires
4.times { Tire.create(car: self.id) }
end
end
Tire model
class Tire < ActiveRecord::Base
belongs_to :car
end
However, inside of make_tires there is an error that there is no activerecord method for create or new if I try it for Tire. When I inspect Tire it doesn't have those methods.
How can I remedy this?
The error is this: undefined method 'create' for ActiveRecord::AttributeMethods::Serialization::Tire::Module
I have tested two environments: Testing and Development and they both fail for the same error.
It is a name conflict. Sit down and relax while I explain.
Solution with explanation:
In Ruby classes are just instances of class Class (which is a subclass of class Module). Instances of Module (including instances of Class) are quite weird objects, especially weird is their connection with ruby constants. You can create a new class at any point using standard ruby notation:
my_class = Class.new { attr_accessor :a }
instance = my_class.new
instance.a = 3
insatnce.a #=>
instance.class.name #=> nil
Well, our class has no name. It is just an anonymous class. How do classes get their name? By assigning it to a constant (for the first time):
MyClass = my_class
my_class.name #=> 'MyClass'
When you define class using a class keyword:
class MyClass
...
end
You just create a new instance of Class and assign it to a constant. Because of that, Ruby compiler seeing a constant has no idea whether it is a class or a number under it - it has to make a full search for that constant.
The logic behind finding a constant is quite complex and depends on the current nesting. Your case is quite simple (as there is no nesting), so ruby will try to find Tire class inside your class first and when failed it's subclasses and included modules.
Your problem is that your class inherits from ActiveRecord::Base (which is correct), which includes ActiveRecord::AttributeMethods::Serialization module, which defines Tire constant already. Hence, ruby will use this constant instead, as this is the best match for that name in given context.
To fix it, you must tell the compiler not to look within the current class but directly in the "top namespace" (which in ruby is Object. Seriously, try Object.constants) - you can do that using :: in front of your constant, like ::Tire.
Note: even though it works, this issue is a first warning for you that your code starts to smell. You should look after this ActiveRecord::AttributeMethods::Serialization::Tire::Module thingy as it seems you will encounter it more than once in the future.
Other stuff:
You can simplify your method slightly:
def make_tires
4.times { tires.create }
end
At that point you might encounter some error you had initially. If you do, then please find what is going on with that Tire::Module thing. If you don't care about the smell:
has_many :tires, class_name: '::Tire'
I'm not sure what's causing the exception you are seeing but you have a number of issues. First, you need to pass in a car instance instead of the id in make_tires. Like this:
def make_tires
4.times { Tire.create(car: self) }
end
You also need to have attr_accessible :car in the Tire model. Like this:
class Tire < ActiveRecord::Base
belongs_to :car
attr_accessible :car
end

Find all parent's parents of a set of deeply nested child objects in Rails 4

This maybe sounds confusing but it's easy to explain. Let's say I have those 3 deeply nested models:
//boo.rb
class Boo < ActiveRecord::Base
has_many :foos
end
//foo.rb
class Foo < ActiveRecord::Base
belongs_to :boo
has_many :goos
end
//goo.rb
class Foo < ActiveRecord::Base
belongs_to :foo
end
Now if I got a set of Goos in #goos, is it possible to get all connected Foos in a lean way? I am using this atm but it's not very lean because I only get the ids in the first step and not the object themselves:
#goos.pluck(:foo_id)
And if there is a better way to do that, is there also a way to get all parents of the connected Foos? So that I have the one set of all Boos connected to the objects in #goos?
Hope this question is not too weird but I am not sure if there are technical terms for this!
Works like this:
#Get all Foos
#foos = #goos.map(&:foo).compact.uniq
#Get all Boos
#boos = #goos.map(&:foo).map(&:boo).compact.uniq
Although it's not really great cause it return an array instead of an active record relation meaning I can't call where and order on it.
Edit:
If it's important for some reason to preserve the active record relation class, this is the better way:
#Get all Foos
#foos = Foo.where(id: #goos.map(&:foo.id).uniq)
#Get all Boos
#boos = Boo.where(id: #goos.map(&:foo).map(&:boo_id).uniq)

ActiveRecord Subclassing dilemma

So here we go. I've got an Activerecord::Base model, let it be called a human.
class human < ActiveRecord::Base
has_one :Animal
end
Animal is an abstract class -
class animal < ActiveRecord::Base
self.abstract_class = true;
end
And I have a subclass of animal, let it be dog
class dog < Animal
in case I don't use abstract class, I can't add instance variables to 'Dog' (because it stores in 'Animal' table). In case I use abstract class, I can't add an 'Animal' to 'Human' - because rails doesn't know, how to store, for example, 'Dog'(ActiveRecord error: couldn't find table ''). This situation drives me crazy, and I just can't get over it.
Am I missing something or just doin' it completely wrong?
By convention in Ruby, Animal would refer to a class (actually, it's a bit more involved - this link has some more detail). In your original post, "class dog" should be "class Dog" b/c the class name is a constant, and if you had a has_one association between human and animal, you could say human.animal = (some instance of animal), but human.Animal is likely to have strange effects if it doesn't just immediately crash. The STI approach that others are recommending will do exactly what you want, though you would set the 'type' value, not 'Animal' (please don't actually do this directly).
You should read up on the meaning of capitalization in Ruby and RoR, STI, active record associations, and polymorphic associations. Something like this should work (not tested, and it's bad normalization - you can use has_one associations and a pattern called delegation to set up a situation where generic animal traits are in one table, and 'human specific' traits are in another to avoid a bunch of NULL columns in your database):
# remember to set up your migrations to add a 'type' column to your Animal table
# if animals can own other animals who own other animals, you may want to look at
# acts_as_tree, which does trees in relational databases efficiently
class Animal < ActiveRecord::Base
self.abstract_class = true
end
class Dog < Animal
# this is bad normalization - but you can keep this simple by adding
# a human_id field in your animal table (don't forget to index)
# look into the 'belongs_to' / 'references' type available for DB migrations
belongs_to :human
end
class Human < Animal
has_one :dog, :autosave => true # or you could use 'has_many :dogs'
end
human = Human.new # => adds record to Animal table, with type = 'human'
dog = Dog.new
human.dog = dog
human.save
ActiveRecord has built-in support for polymorphic associations, so you could do that:
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
ActiveRecord by default gets the table name from the name of a model. You can override that, however. If you want Dogs in one table, Cats in other, etc. then you can do (in Rails 3.2):
class Dog < Animal
self.table_name = 'dogs'
end
class Cat < Animal
self.table_name = 'cats'
end
(You'll have to add migrations to create those tables.)
However, if you want all animals to exist in one table, you should look at Single-Table-Inheritance. See the ActiveRecord docs for more on that.

At what level in a model object does ActiveRecord not load associated objects

I have a couple of models that are composites of multiple objects. I basically manage them manually for saves and updates. However, when I select items out, I don't have access to the associated properties of said item. For example:
class ObjectConnection < ActiveRecord::Base
def self.get_three_by_location_id location_id
l=ObjectConnection.find_all_by_location_id(location_id).first(3)
r=[]
l.each_with_index do |value, key|
value[:engine_item]=Item.find(value.engine_id)
value[:chassis_item]=Item.find(value.chassis_id)
r << value
end
return r
end
end
and each item:
class Item < ActiveRecord::Base
has_many :assets, :as => :assetable, :dependent => :destroy
When I use the ObjectLocation.find_three_by_location_id, I don't have access to assets whereas if use Item.find(id) in most other situations, I do.
I tried using includes but that didn't seem to do it.
thx
Sounds like the simplest solution would be to add methods to your ObjectConnection model for easy access like so:
class ObjectConnection < ActiveRecord::Base
def engine
Engine.find(engine_id)
end
def chassis
Chassis.find(chassis_id)
end
# rest of class omitted...
I'm not exactly sure what you're asking... If this doesn't answer what you're asking, then can you try to be a little bit more clear with what exactly you are trying to accomplish? Are the Chassis and Engine mdoels supposed to be polymorphic associations with your Item model?
Also, the code you're using above won't work due to the fact that you are trying to dynamically set properties on a model. It's not your calls to Item.find that are failing, it's your calls to value[:engine_item]= and value[:chassis_item] that are failing. You would need to modify it to be something like this if you wanted to keep that flow:
def self.get_three_by_location_id location_id
l=ObjectConnection.find_all_by_location_id(location_id).first(3)
r=[]
l.each_with_index do |obj_conn, key|
# at this point, obj_conn is an ActiveRecord object class, you can't dynamically set attributes on it at this point
value = obj_conn.attributes # returns the attributes of the ObjectConnection as a hash where you can then add additional key/value pairs like on the next 2 lines
value[:engine_item]=Item.find(value.engine_id)
value[:chassis_item]=Item.find(value.chassis_id)
r << value
end
r
end
But I still think that this whole method seems unnecessary due to the fact that if you setup proper associations on your ObjectConnection model to begin with, then you don't need to go and try to handle the associations manually like you're attempting to do here.

Resources