customising GORM basic collections - grails

As well as associations between different domain classes, GORM also supports mapping of basic collection types. . For example, the following class creates a nicknames association that is a Set of String instances
class Person {
static hasMany = [nicknames:String]
}
This will store the nicknames in a separate table person_nicknames:
---------------------------------------------
| person_id | nickname |
---------------------------------------------
| 1 | Fred |
---------------------------------------------
By default both columns are nullable and there are no indices present. I would like to make the following changes
make both columns not null
put a composite unique index on (person_id, nickname)
Obviously I could just run an SQL script to make these changes, but is it possible for me to express this in the domain model, so that GORM does it when creating and updating the schema?

No, this presently isn't possible. You can fake it by making Nickname an explicit domain class (Andre Steingress gives an example in the question comments), but otherwise you have to write a migration.

You can use joinTable and basic collection types.
http://grails.org/doc/2.4.3/ref/Database%20Mapping/joinTable.html
You can specify SQL behavior with column mapping:
http://grails.org/doc/2.4.3/ref/Database%20Mapping/column.html

Related

How do I query a list of users?

If I want to query a list of users, I want to dynamically pass in the parameters, for example, can only query according to username, or according to the combination of username and userType conditions to query, I do not know how to use typeORm to write
I guess what you are looking for is find options. Link to official documentation: TypeORM - Find Options
repository.findOne(id?: string | number | Date | ObjectID, options?: FindOneOptions<Entity>): Promise<Entity | undefined>;
findOne function takes in two parameters. First one defines logic to how you want the record to find, by id or its column value. Second parameter lets you fetch the relations if you have any with the specific entity.

Facet search with dynamic ransackers

I am trying to create a generic product catalog application with Rails and in order to have products of varying types with varying attributes I have abstracted product properties into their own table with a link table in between the product and the property that stores the value.
------------- --------------------
|products | |product_properties| ------------
|-----------| |------------------| |properties|
|name |---|value |---|----------|
|description| |product_id | |name |
|etc... | |property_id | ------------
------------- --------------------
For example a product could have a width property (which will be stored in the property table so it can be reused) whilst the value for the width will be stored in the product_properties table with a record that links the property to the product.
This works fine but I need to implement facet-able search within the products model and have chosen to use ransack. So to find all products that have a width greater than 30 I must do
Product.ransack(product_properties_property_name_eq: 'width', product_properties_value_gt: 30).result
This again works fine but I would prefer to 'ransack' using the property name
Product.ransack(width_gt: 30).result
Are there any ways to dynamically create ransackers (or alternatives) that will allow me to do this? I have tried using method_missing but this confused me to no end. I was thinking of creating scopes on the model using all the name values in the properties table but thought I would ask for some advice first.
UPDATE
I have attempted implementing a series of custom ransackers on the product model
class Product < ActiveRecord::Base
Property.pluck(:name, :id).each do |name, id|
ransacker name, formatter: -> (value) { value.to_s.downcase } do
product_properties = Arel::Table.new(:product_properties)
product_properties[:value]
end
end
end
This is getting me ever closer to the answer I can feel it. What else shoudl I be doing here?
This does the job perfectly. The gotcha here is the Arel::Nodes.build_quoted. I had originally left this out and I would get no errors/warning back but I would equally get no results either which left me rather stumped. This apparently is only necessary when usingRails 4.2+ (Arel 6.0+).
Property.pluck(:id, :name).each do |id, name|
product_properties = Arel::Table.new(:product_properties)
ransacker name.to_sym, formatter: -> (value) { value.to_s.downcase } do
Arel::Nodes::InfixOperation.new('AND',
Arel::Nodes::InfixOperation.new('=',
product_properties[:property_id], Arel::Nodes.build_quoted(id)
),
product_properties[:value]
)
end
end
To actually use this I then need to explicitly join the product_properties table to the query
Product.joins(:product_properties).ransack(width_gt: 30)
As the ransack documentation states the difficulty some people encounter with using ransackers stems not from Ransack, but from not understanding Arel. This was definitely the case here.

Rails - Associating database/models with Modules that aren't database tables

I want to create "associations" (or an equivalent concept with similar methods available from having associations). It is with this table of information, that does NOT need to be updated wahtsoever with other tables that DO involve CRUD.
This is my non-updated table of information:
Table name: Personalities
personality_type | alternate_name | CF1 | CF2 | CF3 | CF4 | CF5 | CF6 | CF7 | CF8
----------------------------------------------------------------------------------
ENTj | ENTJ | Te | Ni | Se | Fi | etc | etc | |
INTp | INTJ | (more data values)
ISFj | ISFP | (more data values)
ESFp | ESFP | (more data values)
So it seems to me that making this non-updated into a database table and performing queries on it would be a silly and pointless way of designing my code, since that would entail all of the query loading time overhead.
So I was thinking of something like making a separate Ruby module, but wasn't sure how to "associate" it with other tables that would be full-fledged database tables with models.
1) How do I associate a non-database class instance based on ActiveRecords::Base with one?
2) Which format/data type should I put my non-updated table of information in? class, module, multiple class instances, a 2 dimensional array, or 2 dimensional hash?
My goal in sorting out this decision is to be able to use the similar method notations that comes with associating database models. (e.g. two tables called "Personality" and "User" would allow Rails/Ruby code like #user.alternate_name. and #personality.user.email).
3) Does the fact that rails uses hidden :id, and timestamp columns affect this in any way?
(If this question is a bit broad, feel free to ignore answering it).
Much thanks!
-A user can have only one personality type.
-Other database models need to refer to personality type information independent of the user model.
Presumably only the User model can have a personality type. Why not create an array of these types as a constant in the User model, which you can then refer to in forms etc for selection using User::PERSONALITY_TYPES.
For example:
class User
PERSONALITY_TYPES = %w{ ENTJ INTJ ISFP ESFP }
# ... other model code
end
Then simply store the index of the personality type within the array as the user's personality_type_index.
Perhaps I'm oversimplifying your needs, but this is the approach I would start with.

Persist column order from Grails domain to database

When Grails creates a table from a domain object, is it possible to specify the column order? I'd like it to preserve the column order as specified in the domain. Instead, it seems to be mostly alphabetical. I could not find anything in the documentation. I've found this article that details specifying constraints, but that did not appear to fix the issue for database columns.
Example:
class Foo {
Long id
String zee
Integer baz
Integer bar
}
I'd like the database columns to then be ordered as:
id | zee | baz | bar
Instead I get something closer to:
id | bar | baz | zee
You can always create the DB outside of Grails and put the columns in whatever order you wish and Grails will happily use the schema you provide (assuming only the column ordering is different from what it wants to create by default)
An even better option, as #Burt pointed out, is to use the database migration plugin to create (and manage) the database. It lets you have fine-grained control over the database in a database-agnostic way and also has the massive advantage of making your DB schema and schema changes versioned along with your code, for both upgrades and rollbacks.
This is the only way to do it as I know. Use static constraints and write them with your order
class Foo {
Long id
String zee
Integer baz
Integer bar
}
static constraints = {
id()
zee()
baz()
bar()
}

Rails custom meta model?

I'd like to be able to add "meta" information to a model, basically user-defined fields. So, for instance, let's imagine a User model:
I define fields for first name, last name, age, gender.
I would like users to be able to define some "meta information", basically to go in their profile page and share other information. So one user might want to add "hobbies", "occupation", and "hometown", and another might want to add "hobbies", and "education".
So, I'd like to be able to have a standard view for this kind of stuff, so for instance in the view I might do something like (in HAML):
- for item in #meta
%li
%strong= item.key + ":"
= item.value
This way I can ensure that the information is consistently displayed, rather than just providing a user with a markdown textbox that they may format all different ways.
I'd also love to be able to click on meta and see other users who have given the same thing, so in the example above both users defined "hobbies", it would be nice to be able to say I want to see users who have shared hobbies -- or even better I want to see users whose hobbies are ___.
So, since I don't know what fields users will want to define in advance, what kind of options are there for providing that kind of functionality?
Is there a gem that handles custom meta information on a model like this, or at least sort of similarly? Has anyone had experience with this kind of problem? If so, how did you solve it?
Thanks!
The dynamic field implementation depends upon following factors:
Ability to dynamically add attributes
Ability to support new data types
Ability to retrieve the dynamic attributes without additional query
Ability to access dynamic attributes like regular attributes
Ability query the objects based on dynamic attributes. (eg: find the users with
skiing hobbies)
Typically, a solution doesn't address all the requirements. Mike's solution addresses 1, and 5 elegantly. You should use his solution if 1 & 5 are important for you.
Here is a long solution that addresses 1,2,3, 4 and 5
Update the users table
Add a text field called meta to the users table.
Update your User model
class User < ActiveRecord::Base
serialize :meta, Hash
def after_initialize
self.meta ||= {} if new_record?
end
end
Adding a new meta field
u = User.first
u.meta[:hobbies] = "skiing"
u.save
Accessing a meta field
puts "hobbies=#{u.meta[:hobbies]}"
Iterating the meta fields
u.meta.each do |k, v|
puts "#{k}=#{v}"
end
To address the 5th requirement you need to use Solr Or Sphinx full text search engines. They are efficient than relying on DB for LIKE queries.
Here is one approach if you use Solr through Sunspot gem.
class User
searchable do
integer(:user_id, :using => :id)
meta.each do |key, value|
t = solr_type(value)
send(t, key.to_sym) {value} if t
end
end
def solr_type(value)
return nil if value.nil?
return :integer if value.is_a?(Fixnum)
return :float if value.is_a?(Float)
return :string if value.is_a?(String)
return :date if value.is_a?(Date)
return :time if value.is_a?(Time)
end
def similar_users(*args)
keys = args.empty? ? meta.keys : [args].flatten.compact
User.search do
without(:user_id, id)
any_of do
keys.each do |key|
value = meta[key]
with(key, value) if value
end
and
end
end
end
Looking up similar users
u = User.first
u.similar_users # matching any one of the meta fields
u.similar_users :hobbies # with matching hobbies
u.similar_users :hobbies, :city # with matching hobbies or the same city
The performance gain here is significant.
If each user is allowed to define their own attributes, one option might be to have a table with three columns: user_id, attribute_name, attribute_value. It might look like:
| user_id | attribute_name | attribute_value |
| 2 | hobbies | skiing |
| 2 | hobbies | running |
| 2 | pets | dog |
| 3 | hobbies | skiing |
| 3 | colours | green |
This table would be used for finding other users who have the same hobbies/pets/etc.
For performance reasons (this table is going to get large) you may want to maintain multiple places that the info is stored -- different sources of info for different purposes. I don't think it's bad to store the same info in multiple tables if absolutely necessary for performance.
It all depends on what functionality you need. Maybe it will end up making sense that each user has their key/value pairs serialized into a string column on the users table (Rails provides nice support for this type of serialization), so when you display info for a particular user you don't even need to touch the huge table. Or maybe you will end up having another table that looks like this:
| user_id | keys | values |
| 2 | hobbies, pets | skiing, running, dog |
| 3 | hobbies, colours | skiing, green |
This table would be useful if you need to find all users that have hobbies (run LIKE sql against the keys column), or all users that have anything to do with a dog (run LIKE sql against the values column).
That's the best answer I can give with the requirements you gave. Maybe there is a third-party solution available, but I'm skeptical. It's not really a "pop in a gem" type of problem.
In this case, I would at least consider a documentdb like mongo or couch, which can deal with this type of scenario much easier then an rdms.
If that isn't the case, I would probably end up doing something along the lines of what Mike A. described.

Resources