I have very stupid question about design patterns: let's say we have two classes Post and Product, for each of them we have different table in the DB, and they have nothing in common with each other, so we can't create base class for them. Some Posts even contains Products. And here's what we should do with them:
Somehow store Post and Product instances in the DB, pack them in one array(using C++, if it matters) when user requests news feed from the next item, send it to the client, and receive and unpack on the client side(using Java).
Next, we have to show both Post and Product in the one list(such as news feed on the Facebook).
Also, we can share Post or Product with our friends using chat. So we can send Post or Product as an attachment of the message(consequently, we should to store id of sent Post or Product in the column attached_item of the messages table in the DB on the server side).
So, what design pattern would be best here? How should I implement the Post and Product classes?
It is a very broad question, but here is a skeleton of what you could you, just to give you some ideas:
// An interface containing methods specific to objects you can list
interface Listable {}
// An interface containing methods specific to objects you can share
interface Shareable {}
// An interface containing methods specific to objects you can send
interface Sendable {}
class Post implements Listable, Shareable, Sendable {
List<Product> products;
}
class Product implements Listable, Shareable, Sendable {
}
class ListManager {
public void addToList(Listable element) { }
}
class ShareManager {
public void share(Shareable element) { }
}
class SendManager {
public void send(Sendable element) { }
}
You could then use Post and Product interchangeably this way:
Post post = new Post();
Product product = new Product();
ListManager listManager = new ListManager();
listManager.addToList(post);
listManager.addToList(product);
ShareManager shareManager = new ShareManager();
shareManager.share(post);
shareManager.share(product);
SendManager sendManager = new SendManager();
sendManager.send(post);
sendManager.send(product);
Regarding the database representation, as suggested fusiled in his comment, just stick them in 2 separate tables. With a mapping table in between to link the products to their post.
EDIT
Regarding the issue with the MESSAGES table
You could add a new mapping table MESSAGE_ATTACHED_ITEM with columns messageId, postId, productId. Only set a value to the relevant colum when attaching an item to a message
Or an other option would be to have an ATTACHED_ITEM table with an id only.
And have Post and Product tables to have a foreign key to this table Id.
you can then stick this attachedItemId into your attached_item column
I think the solution could be simpler than you think. Why don't you ust use a common Java-like interface and hide the implementation details?
Just implement a common interface with the methods you need. Supposing this common interface is called EntityInterface:
public class Post implements EntityInterface {};
public class Product implements EntityInterface {};
Then when you want to handle these classes, you treat them as an EntityInterface object:
EntityInterface myNewPost = new Post();
EntityInterface myNewProduct = new Product();
//Now you see myNewProduct and myNewPost as EntityInterface objects
These code fragments are in Java, but use virtual functions in C++ and you get the same.
Related
I'm pretty new to laravel and have a really basic question related to relationships.
Here is an example of my question:
I have a migration called money_transfers.
The migration contains the following things:
user_id (transfer sent by)
sentTo_id (transfer sent to)
amount
sent_at
BOTH user_id and sentTo_id refer to a User ID.
Now, what I want to do is the following:
Fetch the user the money was sent TO the same way as the user the money was sent BY. Just like in the example below:
$transfer->sentTo->name
or
$transfer->sentTo->id
You get what I mean. Thanks in advance :)
If you defined your foreign keys correctly in your migration table, Then it's just a matter of defining the right relationship:
class MoneyTransfer extends Model
{
public function sentBy()
{
return $this->belongsTo(User::class,'user_id');
}
public function sentTo()
{
return $this->belongsTo(User::class,'sentTo_id');
}
}
This way you can access the receiver attribute like this:
$transfer->sentTo->name;
And the sender attribute like this:
$transfer->sentBy->name;
I have this scenario to be implemented
"Member - Album" many to many relationship is the comments. Member can post comments on any Album. Album can have comments from any number of Members. Some comments can be posted as a reply to other comments.
"reply_to" refers to the parent comment. That is, a comment can have several replies.
I'm using Eloquent and Lumen 5.5 in my project. I have gone through the documentation of both of these. I know how to implement relationship such as M:N, M:1 and etc.
I still couldn't find out how to implement this scenario using Custom Intermediate Table Models in Eloquent. Any help is much appreciated.
Note: I have removed other elements from the ERD to make it simple..
Try this
class Member
{
public function comments()
{
return $this->hasMany(Comment:class, 'Member_id'); // a member may has many comments
}
}
class Album
{
public function comments()
{
return $this->hasMany(Comment::class, 'Album_id'); // an album may has many comments
}
}
class Comment
{
public function parent()
{
return $this->belongsTo(Comment::class, 'reply_to'); // self reference
}
public function member()
{
return $this->belongsTo(Member::class, 'Member_id'); // a comment should belongs to a member
}
public function album()
{
return $this->belongsTo(Album::class, 'Album_id'); // a comment should belongs to an album
}
}
Querying:
Album::with(['comments' => function ($query) {
$query->with(['parent', 'member]);
}])->get();
As far as I could understand.
Below are the relationships required in the model.\
Comments model
Comment::belongsTo('Member'); //add namespace of member model
Comment::belongsTo('Albums'); //add namespace of member model
Member model
Member::hasMany('comments');
Album model
Album::hasMany('comments');
For not getting reply tocomments you can add where in Comment::belongsTo('Albums')->whereNull('replyTo');
Then can fetch reply comments via comment id.
I have been trying to define some Database schema to use the laravel framework. I want to model a Football match. The first step I wanted to do is to define the Entity Relationship diagram, but I found this (which I thought would be quite trivial) to be confusing in some aspects.
First, the obvious approach is to say that a Match is related with two Teams, and a Team is related to any number of Matches. So, we would have a "Many to Many" relationship.
But the implementation of a many to many relation is to have two tables and an intermediate table to relate both entities. I think this would be too much, when I know that a Match will always have two Teams and simply having two columns (local_id and visitant_id) with foreign keys to the Teams table would be enough. Plus, I want to be able to do:
Match::find(1)->local() or Match::find(1)->visitant();
So, thinking on this I am implementing a "One to Many" relation, but with this I have another issue. To retrieve all the matches a Team has played I would like to do:
Team::find(1)->matches();
But I cannot do this because I can only specify one key column when defining the matches() method in eloquent (by default it would be team_id, but it should be visitant_id and local_id).
After some more digging into the source code I found there is a way to actually keep my database schema as it is and achieve what I want (at least in Laravel 4). I posted my problem in github and Taylor Otwell (creator of the framework) gave me the correct answer: https://github.com/laravel/framework/issues/1272
Quoting him, it should be as easy as this:
class Team extends Eloquent {
public function allMatches()
{
return $this->hasMany('Match', 'visitant_id')->orWhere('local_id', $this->id);
}
}
And then...
$team = Team::find(2);
$matches = $team->allMatches;
This is one of those famous database design problems. Friendship relationships, for instance, suffer from that same difficulty. Since you are using Eloquent, I would suggest you to stick with many to many approach and have an extra boolean column local on your intermediate table
class Match extends Eloquent {
public $includes = array('team'); // Always eager load teams
public function teams() {
return $this->has_many_and_belongs_to('team')->with('local');
}
public function get_local() {
foreach ($this->teams as $team) {
if ($team->pivot->local) return $team;
}
}
public function get_visitant() {
foreach ($this->teams as $team) {
if (!$team->pivot->local) return $team;
}
}
}
class Team extends Eloquent {
public function matches() {
return $this->has_many_and_belongs_to('match')->with('local');
}
// I'm doing separate queries here because a team may have
// hundreds of matches and it's not worth looping through
// all of them to retrieve the local ones
public function matches_as_local() {
return $this->has_many_and_belongs_to('match')->with('local')
->where('pivot_local', '=', 1);
}
public function matches_as_visitant() {
return $this->has_many_and_belongs_to('match')->with('local')
->where('pivot_local', '=', 0);
}
}
Obs:
The method has_many_and_belongs_to(...)->with('field') has nothing to do with eager loading. It tells Eloquent to load the intermediate table column field and put that in the pivot.
Usage:
$match = Match::find(1);
$match->local; // returns local team
$match->visitant; // returns visitant team
$team = Team::find(1);
$team->matches; // returns all matches
$team->matches_as_local; // ...
$team->matches_as_visitant; // ...
foreach ($team->matches as $match) {
if ($match->pivot->local) {
// put nice local icon here
} else {
// put nice visitant icon here
}
}
I'm using app engine datastore so I have entity like this.
#PersistenceCapable
public class Author {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
#JsonProperty("id")
#JsonSerialize(using = JsonKeySerializer.class)
#JsonDeserialize(using = JsonKeyDeserializer.class)
private Key key;
....
}
When the model is sent to view, it will serialize the Key object as an Id value. Then, if I send data back from view I want to deserialize the Id back to Key object by using JsonKeyDeserializer class.
public class JsonKeyDeserializer extends JsonDeserializer<Key> {
#Override
public Key deserialize(JsonParser jsonParser, DeserializationContext deserializeContext)
throws IOException, JsonProcessingException {
String id = jsonParser.getText();
if (id.isEmpty()) {
return null;
}
// Here is the problem because I have several entities and I can't fix the Author class in this deserializer like this.
// I want to know what class is being deserialized at runtime.
// return KeyFactory.createKey(Author.class.getSimpleName(), Integer.parseInt(id))
}
}
I tried to debug the value in deserialize's parameters but I can't find the way to get the target deserialized class. How can I solve this?
You may have misunderstood the role of KeySerializer/KeyDeserializer: they are used for Java Map keys, and not as generic identifiers in database sense of term "key".
So you probably would need to use regular JsonSerializer/JsonDeserializer instead.
As to type: it is assumed that handlers are constructed for specific types, and no extra type information is passed during serialization or deserialization process: expected type (if handlers are used for different types) must be passed during construction.
When registering general serializers or deserializers, you can do this when implementing Module, as one of the arguments is type for which (de)serializer is requested.
When defining handlers directly for properties (like when using annotations), this information is available on createContextual() callback of interface ContextualSerializer (and -Deserializer), if your handler implements it: BeanProperty is passed to specify property (in this case field with annotation), and you can access its type. This information needs to be stored to be used during (de)serialization.
EDIT: as author pointed out, I actually misread the question: KeySerializer is the class name, not annotation.
As I'm a bit new to Grails, I'm wondering how I can iterate through the current data i have saved in the database to check if the information already exists.
For instance, lets say I have a domain class for Books and I create an action that automatically adds more books but I want to check if the book.title already exists so I don't add it again.
Side note
I'm just using the default database (whatever is used when the project is set to production mode)
Edit
I'll post my domains so it is a bit easier to understand. Instead of book.title, I changed it to where book belongs to author. So I only want the author added once but able to add many books for it. The issue happens in an action i created in the controller.
Author Domain:
class Author {
static hasMany = [books:Book]
String authorName
String notes
String age
String toString() { authorName }
static constraints = {
authorName()
notes(maxSize:500)
age()
}
}
Book Domain:
class Book {
static belongsTo = Author
String toString() { bookNumber }
Author bookAuthor
String title
String numberOfPages
static constraints = {
bookAuthor()
title()
numberOfPages()
}
}
Book Controller (this is where I'm having issues):
class BookController {
static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
//took out index, create, list, etc. to focus on the once that I'm concerned about
//this action will simple read a text file and add books and authors
def gather = {
def parseData = new parseClient() //parses text file line by line and puts it into a list
def dataHolder = parseData.information //dataHolder will hold data from text file
int linesOfData = dataHolder.size() //get size to iterate and add authors & books
linesOfData.times {
def _author = dataHolder.author[it] //get author - Author
def _age = dataHolder.age[it] //get age - Author
def _title = dataHolder.title[it] //get title - Book
def _pages = dataHolder.pages[it] //get pages - Book
def authorInstance //create new Author to add
authorInstance = new Author() //for some reason I have to create and save AuthorName (can't have other fields) before I can add my Book correctly
authorInstance.setAuthorName(_author)
authorInstance.save()
def bookInstance
bookInstance = new Book() //create a new Book to add
bookInstance.setBookAuthor(authorInstance)
bookInstance.setTitle(_title)
bookInstance.setNumberOfPages(_pages)
bookInstance.save() //has to have access to the authorInstance to add correctly which is why i was wondering how to access the database to grab it if it existed
authorInstance.setAge(_age) //add whatever data is left for Author
authorInstance.save() //save again because cant save it with this information before I add authorInstance to the Book
}
}
}
Text File Content:
//You'll notice that author _Scott Davis_ is in here twice.
//I don't want to add two instances of Scott Davis but need to access it to add the book
//the unique constraint makes the value come up as not null but can't be added
Scott Davis : Groovy Recipes
Bashar Abdul Jawad : Groovy and Grails Recipes
Fergal Dearle : Groovy for Domain-Specific Languages
Scott Davis : GIS for Web Developers: Adding 'Where' to Your Web Applications
So I'm basically looking for a way to add that information and haven't found a way that seems to work without running into random problems.
Hope this clears my question up a bit, as the original question was a bit broad
In this case, you can put a unique constraint on title and grails will do that for you.
You can iterate thru the data, but you probably don't want to load the db if you can avoid it. So you could write a custom query to select the number of books for the title, for example.
Edit: for your updates
You don't need to use setters in your controller. Grails adds setters/getters for you dynamically at runtime. If you want to put some logic in your setters, then you can define your own and use them in that case
Have you looked at the grails documentation? http://grails.org/doc/latest/
you have a static constraints block, but you haven't defined how you want each property to be constrained. for unique title it would be
title(unique:true)
if you want to get a list of author names, you can do
List names = Author.executeQuery('select authorName from Author')