-
Notifications
You must be signed in to change notification settings - Fork 276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: an ActiveLabel module for sharing ActiveNode logic #1453
Comments
I think I'd change the
|
@leehericks, what do you think? ^^^ Seperately, regarding:
Your ~ |
First, I don’t know why you opened another issue. There are enough open issues in this project and it cuts off a whole part of the discussion. Second, if_is is waaaaaaay too much domain specific to your modeled domain example. labels are concrete things added to the node, same as properties and associations. The functionality that can be associated with it is the optional domain-agnostic part |
I want to follow up that you are full of great ideas too and I’m just saying the framework should sit under you and allow you to design an if_is for your case. We just have to be sure what gets created and supported at the core is flexible and useful for everyone. I think that is why Brian introduced his thoughts with hesitation and the note that he wants to take his time on it. If we keep having good discourse we’ll nail something awesome. Also you raising the issue of the movie database is a great real world example! It’s definitely a situation where inheritance is not the answer. Gonna dwell on it!!! |
I'm fine with another issue, the other one was feeling a bit overwhelming ;). I'm just happy if we can agree on a proposal ;). I haven't read the other thread yet and I'll need to get back to work after this comment, but I thought I would start with this issue. What you're suggesting @thefliik is, I think, closer to what I was thinking. Some feedback: Regarding Totally makes sense to have
To me this also highlights the question of the difference between the All of that said, I like the idea from the other thread about calling some method and giving the module so that the entire module will only be included depending on what things are defined. Something like:
This also gives us the power to have a single method with which to implement both cases we've been talking about: One where the label is always there (the I think that this would also give us the ability to say |
Regarding
Ah, so I didn't quite see it before, but I approached this from a slightly different viewpoint. My thinking with the above was that you include a module in a class and with that module comes a label. Your thinking seems to be, you include a label in a class, and with that label comes functionality. The difference between the two approaches is that, in your approach (I think), every
Makes sense, especially given my new understanding of your approach 👍 .
This thought makes sense given
So in my DSL, I had an unstated, subconscious, expectation that anything inside an Fleshing my subconscious expectations out further, including a
This was intentional on my part, I was thinking that you would only want the ability to call |
So if my understanding of your In my app, I track node state through Do you envision this use case being supported in your ActiveLabel scheme? How? |
To pile on a bit more, I can see how my scheme could be extended to create a module that only applies if TWO labels are attached to a class. Something like this, highly contrived, example:
Where the
Now thats some polymorphism :) |
@thefliik Regarding tracking relationships, I think you're missing a very simple model. (p1:Person)-[:FRIENDSHIP]->(p2:Person) This article from Neo4j Blog: As much as we want to live in an ideal beautiful data modeling world, Neo4j has performance quirks to force us to optimize. class Person
include Neo4j::ActiveNode
has_many :both, :friends, type: :FRIENDSHIP, model_class: :Person
has_many :both, :destroyed_friends, type: :DESTROYED_FRIENDSHIP, model_class: :Person
# Might use ActiveRel for timestamps
# Add logic for destroying friendships
end |
before_save, if labels contains :A and :B then do something. |
Regarding the movie database, whether labeled an Actor, Director, or some combination thereof, first and foremost the nodes have a shared base label. They are a Person, but they might be multiple things. This is where inheritance fails. This is also why the things granted by the existence of a label can't be conditionally granted in terms of adding removing model functionality and one has to be careful about clashing names. A director has movies they directed The associations of each label can't all be named movies. This is why I caution that we add the ActiveLabel feature and you make a module to share which checks for the existence of multiple labels separately. That's a Ruby language feature that doesn't need any special Neo4j.rb additions. |
I was also going to suggest either the Regarding the last thing that you said @thefliik about polymorphism, it would be super cool for Animal.where_actor.where_living.new
I think we're getting close, but not quite. I'm not suggesting that models ever get the behavior from a module if that module wasn't explicitly defined on the model. I think it definitely makes sense that the designer of the app needs to call out specifically on what models an Where I'm coming from is that I think that I'm seeing two different use cases for
I think that I see the second case as basically multiple inheritance (which is really the point of modules in Ruby anyway). It would be as if the class had more than one parent class, meaning that the objects would always have the labels for the modules and searching on the module like I could say more, but since we have a couple of threads going I wanted to make sure we're on the same page with this point first. |
@cheerfulstoic @thefliik ActiveNode: # Fixed model labels
# Deprecate self.mapped_label_name, setting it calls self.model_labels = [mapped_label_name]
Model.model_labels=([:Label1, :Label2])
# Synthesizing optional labels
Model.optional_label(name, options)
# Manipulating labels
model.labels # Array of label names which includes ActiveNode model/inheritance labels and applied optional labels
# Querying
model.has_labels([:Label1, :Label2])
model.where(has_labels: [:Label1, :Label2])
Calling # Add or remove the label
model.labelled_[name]=(Boolean)
# Check Existence
model.labelled_[name]? # Returns boolean
# Querying
model.labelled_[name] # Query scope, Person.labelled_actor.labelled_director Labels should get persisted on call to Example: class Case
include Neo4j::ActiveNode
optional_label :Current, default: true
property :title, type: String
def archive
labelled_current = false
end
end
Case.all.labelled_current |
Remember that learning a framework is hard and this is on top of Rails, Ruby language, understanding Neo4j and how to data model for the graph, etc. I'm not a genius and I struggle a lot. Ambiguous naming can be really confusing. Clear context is everything. |
Well, I see add_labels, remove_labels, mapped_label_names, etc. already exist. ^^; |
I could respond, but I'd like to try something that I hope might be more effective. One idea that I've had for developing something in the past which I haven't really tried is "documentation driven design". That is, before writing any code, write the documentation for how it would work so that you're thinking of it from the user's perspective (kind of what @leehericks is doing). Once you're happy with that you can write the code and you should have really good documentation for it. So what about this: We have a PR which defines documentation for non-existent code. This way we can discuss things in the comments, but we can also comment on specific lines / sections via GitHub's awesome PR UI. Worth a shot? |
Yes, I have no experience with github PRs, but can we all edit the example code or does one person initiate it and then update the documentation according to comments and consensus? |
@cheerfulstoic At some point as the lead developer with a deeper understanding of the framework you do have to comment. 🤪 I assume if you see something that is impossible or misunderstood, you'll let me know. I don't want to labor too long under misunderstandings. |
P.S. I am trying to dig into some of the source code/documentation to get better acquainted with the project. :) |
Yeah, I definitely have comments, but I feel like we're three people pulling in three directions on a variety of issues and it's really hard to keep track of it. With GitHub PRs, one person makes a branch/fork and submits a PR for the patch of those changes. PRs are just issues in GitHub, but you can also see the different proposed file changes and click on individual lines to start a conversation about that line (or the block of code above that line). There's more, but that's the main thing I wanted to take advantage of. @thefliik ? |
So are we all going to create our own PR then? Like fork the project and add a markdown file or a ruby file and create a pull request for that? |
I was thinking of having one person create a PR and we can all comment on it (and maybe allow changing it too for efficiency since we're sort of spread out across the world). But we could also do separate PRs and then we could each comment on each others' and probably solidify on one. It would certainly be a nice way to be able to show where we are the same and different. Regarding what we would create, this repo has a docs directory which is where all of the documentation is kept. The files are in reStructedText format. I chose that because it's the default choice of readthedocs. RTD supports markdown, but it doesn't provide many features in that context (RTD uses Sphinx, a popular tool in the python community, to generate a documentation suite). So a PR would probably just need to include a new |
Ok, I've put together a bit of documentation: Definitely not complete! Definitely didn't get everything in there! Just trying to get a start on something where we can be more focused on how we have a conversation. |
We can discuss just on there or we can have other proposals too. Happy to go in whatever direction brings clarity |
Thank you Brian! Sorry, currently having a school IT crisis. FML lol |
No problem, I'll be busy in the upcoming days, so take your time ;) |
H'okey, back. Ahhh...again I find myself wishing that Github allowed nested replies. Regarding
You're totally right. Until I read that GraphAware article you shared, @leehericks, I don't think I realized how expensive filtering on a label was. I'm definitely going to update my tracked relations with Regarding
Maybe you know of a Ruby secret I don't, but conditionally adding a module to an instance of a class isn't as easy as you imply. Ya, testing for the condition is easy, but how do you include the module? I can use
Ya
I agree that we are currently three people pulling in different directions. Jumping over to look at the documentation though, I see some assumptions there that I disagree with. Most importantly, I'm still arguing that, at the core, what an A point against the documentation approach, is that it immediately mixes in all of these different concerns together in the conversation (e.g. your documentation touches on "how do we add additional mapped label names to a class?"). I like the idea of starting with the documentation, but, personally, I'd like to understand what My argument: If folks agree with this, personally I can move forward, but if folks don't agree with this, I'd like to hear their thoughts on why not? Looking at the documentation, you've put together @cheerfulstoic, it appears that your If your approach is the same as mine, then Alternatively, if you're thinking of the
A developer could add an optional label to a class that isn't associated with any ReflectionWhen I opened this thread, I made the mistake of mixing in a number of separate ideas based on unspoken assumptions I had. I should have started more focused, waited for thoughts, and moved out from there. |
Looking some more, it really looks the If the additional label is always present
If the additional label is optional
|
I think issue #1424 has started to bloom into multiple issues. I'm opening this up to continue the discussion of creating some sort of includeable ActiveLabel module functionality.
Preface
@cheerfulstoic began this conversation in #1424 but I didn't really "get it" (see the use case) until, unrelated, I just embarked on a side quest to benchmark the effect of label ordering on query time. As part of this, I downloaded the Neo4j example movie database to test against. Lo-and-behold, as I tried to use ActiveNode to model the data, I begun to see problems....
I initially thought to model the database like so:
If you check out the example movie database, you'll notice problems with the above though. This is because a node can have
:Person:Actor:User
labels associated with it (i.e. can be both an Actor and a User at the same time). So inheritance doesn't work, because then a node would be wrapped as either an Actor or a User, but would not be both (when it should be both).Adding multiple labels to
Person
(such as using modules) doesn't work either though, as thenPerson
will always have those labels (which is inappropriate). So:User
,:Actor
, and:Director
labels are truly optional to the person node, except their presence alter's the properties of the person node. In the case of:User
, it adds properties.The exciting part...
So after reading the ideas in #1424 and trying to solve the movie database problem, here's my take which, the more I think about, the more I like. This is definitely similar to what @leehericks wrote down in #1424 -- I just didn't understand it at the time.
(it's also possible that this is exactly what @cheerfulstoic and @leehericks where getting at over in #1424)So in the above we can see a few things going on. We have a base
Person
class. This person class includesUser
,Actor
, andDirector
ActiveModules. These ActiveModules are associated with a label of the same name. If a:Person
node is retrieved from the database, and it includes a label associated with one of the ActiveModules, lets say theUser
module, then the node gets wrapped as aPerson
object but gains the properties associated with aUser
.User
properties can be defined in theUser
module. They can also be defined in a specialif_is(User) do
block inside thePerson
class. This way, if aUser
module is included in several classes, those classes all gain the properties associated with theUser
modules. Additionally, a specific class could say that it's users gain properties / methods that other classes don't get.At this point, I should also mention that I'm not committed to any of the naming / DSL, but hopefully you can see what I'm going for here.
Things like
If you wanted a class to always have a ActiveModule's label / property, you could
The text was updated successfully, but these errors were encountered: