I have a model called List which has many records:
class List
has_many :records
end
class Record
end
The table Record has 2 permanent fields: name, email.
Besides these 2 fields, for each List a Record can have 'n' custom fields.
For example: for list1 I add address(text), dob(date) as custom fields.
Then while adding records to list one, each record can have values for address and dob.
Is there any ActiveRecord plugin which provides this type of functionality?
Or else could you share your thoughts on how to model this?
Thanks in advance,
Pankaj
You should take a look in schemaless database solutions. One that i think is great is mongoDB.
There is a railscast explaining how to use it with rails. Take a look, it works great.
If your custom fields don't need to be real database columns, you could use serialize: http://railsapi.com/doc/rails-v2.3.5/classes/ActiveRecord/Base.html#M000924 You would use it like:
class Record < ActiveRecord::Base
serialize :custom_fields, Hash
end
r = Record.create :custom_fields => {:name => 'John Doe', :birth_date => Date.new(1970,1,1)}
r.custom_fields[:name]
# => 'John Doe'
r.custom_fields[:birth_date]
# => #<Date: 4881175/2,0,2299161>
Pro: easy to use
Con: since the custom fields are not db columns, you can't find records in the db based on their values (e.g. Record.find_by_name('John Doe') does not work)
Maybe this? has_magic_columns. I havn't tested it myself but seems like it can do what you need.
Related
I have a model called Page. The users are able to specify attributes whenever they create a Page. Ideally, they will choose from a list of attributes and assign them to the Page.
My first though was to create as many columns as attributes the user is able to select, and basically marking them as true/false if it has been selected.
However, I have a feeling that I might be missing a better approach. I have also used at HSTORE, but at the end of the day, I would also need to do something like:
attributes: { 'attribute1' => 'true', 'attribute2' => 'false' } and I am not sure if that is appropriate.
I will use those attributes in order to select pages, so I should be able to say something like:
Page.where(attribute1 is true).where(attribute2 is false).where(...)
How should I store those attributes in my Page model?
The acts_as_taggable_on gem might be of help to you.
You would declare your model as taggable on your attributes as follows:
class Page < ActiveRecord::Base
acts_as_ordered_taggable_on :attributes
end
And your example query would be something like this:
Page.tagged_with(["attribute1"]).tagged_with(["attribute2"], :exclude => true)
The Gem is very well documented here.
I'm a bit confused about STI in rails.
My situation:
I have a Contact model that has description and data string fields, to store some contact like phone, fax, email, etc.
Now when I have some specific contact type like phone number of email address I want to walidate the data format in different way and I want to make some different formating on output.
I decided to use STI as all the models have the same data with just different behaviour. And I have some questions regarding forms and CRUD operations as I don't want to go against Rails conventions.
How do I make a dropdown list in form with model type? Should I hardcode it or is there some more automated way?
How do I create a record? Should I use switch statement and according to received type create new model of according instance?
How should I update it if I'm going to change the model type? Cast the object to new class? Or create a new object and destroy the previous one?
I'll be very thankfull for your help!
Yes, should do a hardcore as there no default store for your STI models.
Generally, yes. But With Rails you could just use camelize.constantize to get class from string. Another way is just use parent model, and set type field manually. As with STI all records are in the same table and then all are of the parent class.
If you wish to update, just update type field. Then you could re-query to force Rails to get new object of different type.
You could create a model like this :
Type < ActiveRecord::Base
has_many :contacts
end
You could use this command rails g model Type name:string, add a type_id column in your contact and migrate the database.
end change your contact's model like this :
Contact < ActiveRecord::Base
belongs_to :type
end
Now, in your form, you could use this :
select("type", "type_id", Type.all.collect {|t| [ t.name, t.id ] }, { :include_blank => true })
It should resolve your problem.
Now you can do something like this :
#emails = Type.find_by_name('email').contacts
Or use scopes.
Please help a newbie to choose the best way to implement inheritance in RoR3. I have:
-Person (address fields, birthdate, etc.)
-Player, inherits from Person (position, shoe_size, etc.)
-Goalkeeper, inherits from Player (other specific fields related to this role)
I think that Single Table Inheritance is a bad solution, because there will be a lot of null fields in the table created. What is the best way to do this? Use polymorphic associations (with has_one?)? Use belongs_to/has_one (but then how to show in the Player views the fields of Person too?)? Don't implement inheritance? Other solutions?
While I think STI is probably the approach I would use for this, one other possibility, if you want to avoid a lot of NULL attributes, is to add a column other_attributes to your Person model that will store a Hash of attributes. To do this, add a text column to the people table:
def self.up
add_column :people, :other_attributes, :text
end
Then make sure the attribute is serialized in the model. And you may want to write a wrapper to make sure it's initialized as an empty Hash when you use it:
class Person < ActiveRecord::Base
serialize :other_attributes
...
def other_attributes
write_attribute(:other_attributes, {}) unless read_attribute(:other_attributes)
read_attribute(:other_attributes)
end
end
Then you can use the attribute as follows:
p = Person.new(...)
p.other_attributes #=> {}
pl = Player.new(...)
pl.other_attributes["position"] = "forward"
pl.other_attributes #=> {"position" => "forward"}
One caveat with this approach is that you should use strings as keys when retrieving data from other_attributes, as the keys will always be strings when the Hash is retrieved from the database.
I suggest STI. An alternative solution is to use a document store like mongodb, or use the activerecord store http://api.rubyonrails.org/classes/ActiveRecord/Store.html. If you have a postgress database look at his HStore column http://rubygems.org/gems/activerecord-postgres-hstore.
Another option is PostgreSQL table inheritance. http://www.postgresql.org/docs/8.1/static/ddl-inherit.html
is there any way how to execute validation directives saved in database ?
See, i don't have staticaly defined attributes by database columns but are defined as row-type.
Examle:
Have groups and attributes. Every group should have different attributes.
Groups: id, name
Attributes: id, name
GroupAttributes: id, group_id, attribute_id, validation(serialize hash as string, same as rails validations)
Articles: id, group_id, attribute_id, value
Form is dynamicaly created based on which group is defined. Group 1 may have different validation for Attribute 1 than Group 1 for same Attribute.
But big problem are validations. I can't implicit define it in Model. Because i dont know which attributes are want to valide.
I have an idea evaluate string saved in database. But it is critical when there be injected code (which is very unespected).
Do you know any way how to resolve this ?
EDIT:
Principle is: I will write about CMS, it could be more clearer.
CMS have Articles/Posts and Post belongs to Group(Category).
Article should have many Attributes (saved in Attributes db table) like title, content, excerpt, thumbnail image, etc. But these attributes are not fixed in everyone article.
This is because Article belongs to Group. For example: news, posts, properties, reviews, etc.
And there is what i want. When i write some review, it is not neccessary put all fields(attributes) for all groups, only these which are needs.
So my idea is, that is easier to define attributes as "row type" instead "column type". Any time i'm able to add new, remove old for any Group. There could be tens of attributes (and different) for any group.
After neccessary joins all Models i want to define validation for each one attribute (column "validation" on top) and be used only for these attributes which are associated with group (GroupAttributes).
Give these attribute validations into the Model, like standard Rails "validates". Without complicated "if" and "unless" conditional statements.
Hope is more detailed than first one.
You can try make custom validator and then loop attributes for group and evaluate their validation parameters on the article. But you will need a proper hash for rails validations.
GroupAttributes.first.validation => {:presence => true,
:length => {:minimum => 3, :maximum => 254},
:uniqueness => true}
#app/models/article.rb
class Article << ActiveRecord::Base
vlidates_with ArticleValidator
end
#lib/article_validator.rb
class ArticleValidator << ActiveModel::Validator
def validate(record)
record.group.group_attributes.each do |group_attribute|
record.validates group_attribute.attribute.name, group_attribute.validation
end
end
end
I am not sure if does this works but hope it will be helpfull.
UPDATE: I was wrong it didn't work because validates is ActiveModel method not ActiveRecord like I thought. The right answer is you have to add before_validation callback and then setup the right validators.
class Article << ActiveRecord::Base
before_validation :set_validators
validates :group_id, :presence => true
privat
def set_validators
self.group.group_attributes.each do |group_attribute|
Article.validates group_attribute.attribute.name, group_attribute.validation
end
end
end
This is a Rails/ActiveRecord question.
I have a model which basically has to represent events or performances. Each event has many attributions: an attribution is basically something like "In this event, Person X had Role Y".
I concluded that the best way to allow a user to edit this data is by providing a free text field which expects a structured format, which I'll call a role string:
singer: Elvis Costello, songwriter: Paul McCartney, ...
where I use autocompletion to complete on both the names of roles (singer, songwriter...) and the names of people. Both roles and people are stored in the database.
To implement this, I created a virtual attribute in the Event model:
def role_string
# assemble a role string from the associations in the model
end
def role_string=(s)
# parse a string in the above role string format,
# look up the People and Events mentioned, and update
# what's in the database
end
This is all fine. The whole thing works quite well, when the role string is well-formed and the associations given by the role string all check out.
But what if the role string is malformed? Okay, I figure, I can just use a regex together with standard validation to check the format:
validates_format_of :role_string, :with => /(\w+:\s*\w+)(,\s*\w+:\s*\w+)*/
But what if the associations implied by the role string are invalid? For example, what happens if I give the above role string, and Elvis Costello doesn't reference a valid person?
I thought, well, I could use validates_each on the attribute :role_string to look up the associations and throw an error if one of the names given doesn't match anything, for example.
My questions are two: first, I don't like this approach, since to validate the associations I would have to parse the string and look them up, which duplicates what I'd be doing in role_string= itself, except for actually saving the associations to the database.
Second, ... how would I indicate that an error's occurred in assigning to this virtual attribute?
First of all, you're attributing the Person to the Event incorrectly. You should instead pass a Person's ID to the event, rather than a string of the person's name. For instance, what if a Person with an ID of 230404 and a name of "Elvis Costello" changes his name to "Britney Spears?" Well, if that were to happen, the ID would remain the same, but the name would change. However, you would not be able to reference that person any longer.
You should set up your associations so that the foreign key references people in multiple cases:
has_one :singer, :class_name => "Person", :foreign_key => "singer_id"
has_one :songwriter, :class_name => "Person", :foreign_key => "songwriter_id"
This way, you can have multiple people associated with an Event, under different roles, and you can reference multiple attributes that Person may have. For example:
Event.first.singer.name # => "Elvis Costello"
Event.first.songwriter.name # => "Britney Spears"
You can research the available validations for associations (validates_associated), as well as whether or not an ID is present in a form (validates_presence_of). I would recommend creating your own custom validation to ensure that a Person is valid before_save. Something like (untested):
def before_save
unless Person.exists?(self.songwriter_id)
self.errors.add_to_base("Invalid songwriter. Please try again!")
return false
end
end
Also, I noticed that you're looking for a way for users to select a user which should be used for the roles in your Event. Here is what you can do in your form partial:
select(:event, :singer_id, Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => 'None' })
Hope this helps!